to_sql pyodbc 计数字段不正确或语法错误

问题描述 投票:0回答:5

我正在从 api 网站下载 Json 数据,并使用 sqlalchemy、pyodbc 和 pandas 的 to_sql 函数将该数据插入到 MSSQL 服务器中。

我最多可以下载 10000 行,但是我必须将块大小限制为 10,否则会出现以下错误:

DBAPIError: (pyodbc.Error) ('07002', '[07002] [Microsoft][SQL Server Native Client 11.0]COUNT 字段不正确或语法错误 (0) (SQLExecDirectW)') [SQL: '插入 [TEMP_生产实体_详细信息]

大约有 5 亿行需要下载,它只是以这个速度爬行。关于解决方法有什么建议吗?

谢谢,

python sql-server pandas pyodbc
5个回答
57
投票

提出这个问题时,pandas 0.23.0 刚刚发布。该版本将

.to_sql()
的默认行为从调用 DBAPI
.executemany()
方法更改为构造表值构造函数 (TVC),该构造函数通过使用 INSERT 语句的单个
.execute()
调用插入多行来提高上传速度。不幸的是,这种方法经常超出 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()
的默认行为,即

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"
调用
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

# 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)

但是,最快的方法仍然可能是:

  • 将 DataFrame 转储到 CSV 文件(或类似文件),然后

  • 让 Python 调用 SQL Server

    bcp
    实用程序将该文件上传到表中。


2
投票

对我来说,解决方案是不要使用:

engine = create_engine(connection_uri, fast_executemany=True)

我只是玩:

df.to_sql('tablename', engine, index=False, if_exists='replace',
          method='multi', chunksize=100)

这里代替了

chunksize=100
,我放了
chunksize=90
,它开始工作了。显然,因为以前的表较小,并且对于较大数量的列,您可能需要较小的数字。如果您不想进行可能因各种原因而出错的计算,请尝试一下。


1
投票

根据 Gord Thompson 的回答做了一些修改。这将自动计算块大小并将其保持为适合 2100 个参数限制的最小最接近整数值:

import math
df_num_of_cols=len(df.columns)
chunknum=math.floor(2100/df_num_of_cols)
df.to_sql('MY_TABLE',con=engine,schema='myschema',chunksize=chunknum,if_exists='append',method='multi',index=False )

1
投票

没有声誉,所以我无法评论 Amit S。 我只是尝试了这种方式,用设置为“multi”的方法计算 chuknum 仍然显示错误:

[Microsoft][SQL Server Native Client 11.0][SQL Server]
传入请求参数过多。服务器最多支持2100个参数。减少参数数量并重新发送请求

所以我修改了:

chunknum=math.floor(2100/df_num_of_cols) 

chunknum=math.floor(2100/df_num_of_cols) - 1

现在看来工作完美。 我认为应该是一些边缘问题......


0
投票

我什至尝试过 chunksize=50。我所看到的只是 sql server 中的 100 行数据,而不是来自 dataframe 的 2100 行数据。有人可以帮忙吗

© www.soinside.com 2019 - 2024. All rights reserved.