【问题标题】:to_sql pyodbc count field incorrect or syntax errorto_sql pyodbc 计数字段不正确或语法错误
【发布时间】:2021-07-08 11:33:06
【问题描述】:

我正在从 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: 'INSERT INTO [TEMP_produce_entity_details]

大约有 5 亿行可供下载,它只是以这种速度爬行。关于解决方法的任何建议?

谢谢,

【问题讨论】:

  • 10个有效的逻辑和11个无效的逻辑有什么不同?
  • 10 或 100 是它在给定时间插入数据库的行数。
  • 您可以尝试运行 SQL Profiler 来查看服务器尝试处理的内容吗?如果 pandas 尝试调用具有超过 2100 个参数的系统存储过程,则可能会导致错误。
  • 这似乎是问题所在。它试图插入 2400 个参数。有没有办法解决这个问题?
  • @GordThompson 这似乎是问题所在。它试图插入 2400 个参数。有没有解决的办法?有没有更有效的方法将熊猫的数据框插入到 SQL 表中?我想它以前可以工作,然后我升级了 pyodbc 包,但它停止了。谢谢

标签: python sql-server pandas pyodbc


【解决方案1】:

在提出这个问题时,pandas 0.23.0 刚刚发布。该版本将 .to_sql() 的默认行为从调用 DBAPI .executemany() 方法更改为构造一个表值构造函数 (TVC),该构造函数将通过使用 INSERT 语句的单个 .execute() 调用插入多行来提高上传速度。不幸的是,这种方法经常超过 T-SQL 对存储过程的 2100 个参数值的限制,从而导致问题中引用的错误。

此后不久,pandas 的后续版本在.to_sql() 中添加了一个method= 参数。默认值 - method=None - 恢复了之前使用 .executemany() 的行为,而指定 method="multi" 将告诉 .to_sql() 使用更新的 TVC 方法。

大约在同一时间,SQLAlchemy 1.3 发布,它向create_engine() 添加了一个fast_executemany=True 参数,这大大提高了使用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^5HHH@192.168.0.199/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")

对于在 Windows、macOS 和 Microsoft 支持其 ODBC 驱动程序的 Linux 变体上运行的应用程序,这是推荐的方法。如果您需要使用 FreeTDS ODBC,则可以使用method="multi"chunksize= 调用.to_sql(),如下所述。


(原答案)

在 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 实用程序将该文件上传到表中。

【讨论】:

  • 似乎是一个奇怪的更新?我想很大一部分用户会想要插入包含超过 2100 个参数的数据帧...为大型数据集构建的 SQL Server...
  • @user3725021 - 我同意 pandas 更新应该考虑到 T-SQL 限制,至少对于 SQL Server 方言。我们可以使用 to_sqlchunksize 参数来解决这些问题,但如果 pandas 自己处理会更好。
  • 我认为他们再次更改了它并添加了一个默认为Nonemethod 参数,或者换句话说,旧的逐行/executemany 行为。如果通过"multi",它将使用多值插入。如果传递一个可调用对象,它还可以实现更复杂的处理——例如允许使用 Postgresql 的COPY
  • 我发现使用method='multi', chunksize=tsql_chunksize 的 .to_sql 性能更好,正如这里推荐的那样。谢谢!
  • 这个 2100 限制是否也适用于删除语句?
【解决方案2】:

根据 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 )

【讨论】:

  • btw python 有一个 buitlin 地板除法运算符:2100 // df_num_of_col
【解决方案3】:

没有声望,所以我无法评论 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

现在看来运行良好。 我认为应该是一些边缘问题...

【讨论】:

    【解决方案4】:

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

    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,它开始工作了。显然,因为前一个表较小并且列数较多,您可能需要较小的列数。如果您不想使用由于各种原因可能出错的计算,请尝试使用它。

    【讨论】:

      猜你喜欢
      • 2016-03-09
      • 1970-01-01
      • 2022-01-09
      • 2021-06-23
      • 1970-01-01
      • 1970-01-01
      • 2016-07-09
      • 1970-01-01
      相关资源
      最近更新 更多