在提出这个问题时,pandas 0.23.0 刚刚发布。该版本更改了默认行为.to_sql()
从调用 DBAPI.executemany()
构造表值构造函数 (TVC) 的方法,该构造函数通过使用单个值插入多行来提高上传速度.execute()
调用 INSERT 语句。不幸的是,这种方法经常超出 T-SQL 对存储过程 2100 个参数值的限制,从而导致问题中引用的错误。
此后不久,随后发布的 pandas 添加了method=
论证.to_sql()
。默认值 –method=None
– 恢复了之前的使用行为.executemany()
,同时指定method="multi"
会告诉.to_sql()
使用更新的 TVC 方法。
大约在同一时间,SQLAlchemy 1.3 发布了,它添加了fast_executemany=True
论证create_engine()
使用 Microsoft SQL Server 的 ODBC 驱动程序,大大提高了上传速度。有了这样的增强,method=None
事实证明至少和method="multi"
同时避免 2100 个参数的限制。
因此,对于当前版本的 pandas、SQLAlchemy 和 pyodbc,使用的最佳方法.to_sql()
与 Microsoft 的 SQL Server ODBC 驱动程序一起使用fast_executemany=True
和默认行为.to_sql()
, i.e.,
connection_uri = (
"mssql+pyodbc://scott:tiger^[email protected]/db_name"
"?driver=ODBC+Driver+17+for+SQL+Server"
)
engine = create_engine(connection_uri, fast_executemany=True)
df.to_sql("table_name", engine, index=False, if_exists="append")
对于在 Microsoft 支持其 ODBC 驱动程序的 Windows、macOS 和 Linux 变体上运行的应用程序,建议使用此方法。如果您需要使用 FreeTDS ODBC,那么.to_sql()
可以调用method="multi"
and chunksize=
如下所述。
(原答案)
在 pandas 0.23.0 版本之前,to_sql
将为 DataTable 中的每一行生成一个单独的 INSERT:
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
0,N'row000'
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
1,N'row001'
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2)',
2,N'row002'
大概是为了提高性能,pandas 0.23.0 现在生成一个表值构造函数来每次调用插入多行
exec sp_prepexec @p1 output,N'@P1 int,@P2 nvarchar(6),@P3 int,@P4 nvarchar(6),@P5 int,@P6 nvarchar(6)',
N'INSERT INTO df_to_sql_test (id, txt) VALUES (@P1, @P2), (@P3, @P4), (@P5, @P6)',
0,N'row000',1,N'row001',2,N'row002'
问题在于 SQL Server 存储过程(包括系统存储过程,例如sp_prepexec
)仅限于 2100 个参数,因此如果 DataFrame 有 100 列,则to_sql
一次只能插入大约20行。
我们可以计算出所需的chunksize
using
# df is an existing DataFrame
#
# limit based on sp_prepexec parameter count
tsql_chunksize = 2097 // len(df.columns)
# cap at 1000 (limit for number of rows inserted by table-value constructor)
tsql_chunksize = 1000 if tsql_chunksize > 1000 else tsql_chunksize
#
df.to_sql('tablename', engine, index=False, if_exists='replace',
method='multi', chunksize=tsql_chunksize)
然而,最快的方法仍然可能是: