SQLAlchemy 无法仅在临时 table 上插入小数

SQLAlchemy fails to insert decimal only on temp table

尝试使用 SQLAlchemy(插入 PYODBC/sql 服务器)插入临时 tables 并插入多行十进制值和 fast_executemany=True 抛出:

ProgrammingError("(pyodbc.ProgrammingError) ('Converting decimal loses precision', 'HY000')")

这只发生在临时 table 中 fast_executemany=True 并且一次插入多行,其中一列是小数。一次插入一个,关闭 fast_executemany 或插入常规 table 效果很好。

我构建了一个简单的示例:

CONNSTR = "mssql+pyodbc://user:PASSWORD@SERVER?driver=ODBC Driver 17 for SQL Server&trusted_connection=yes"

def test():
    data = [(1, Decimal('41763.9907359278'), Decimal('227367.1749095026')), (1027, Decimal('3117.1592020142'), Decimal('16970.1139430488'))]
    engine = sqlalchemy.create_engine(CONNSTR, fast_executemany=True, connect_args={'connect_timeout': 10})
    #this will fail
    insert(engine, data, "#temp_table_test")
    #this will work
    insert(engine, data, "regular_table_test") 

def insert(engine, data, table_name):
    try:
        with engine.begin() as con:
            con.execute(f"""DROP TABLE IF EXISTS {table_name};""")
            con.execute(f"""
                CREATE TABLE {table_name} (
                    [id_column] INT NULL UNIQUE,
                    [usd_price] DECIMAL(38,20) NULL,
                    [brl_price] DECIMAL(38,20) NULL,
                )
            """)
            sql_insert_prices = f"INSERT INTO {table_name} VALUES (?,?,?)"
            con.execute(sql_insert_prices, data)
            print(f"Insert em {table_name} worked!")
    except Exception as e:
        print(f"{e!r}")
        print(f"Insert em {table_name} failed!")
        

虽然显然与快速执行完成的最小转换机制有关,但我无法找出为什么它会根据 table 的类型运行不同。这里引用这个特殊异常的每个其他问题都是由这里不存在的其他因素引起的,我认为所以我真的很茫然。

编辑:所以只有一个小数列的原始测试 运行 很好(我假设减少列数不会改变输出),但是添加另一个小数列让我回到正方形同样的错误信息

fast_executemany=True 询问 ODBC 驱动程序列类型是什么,Microsoft 的 ODBC 驱动程序用于 SQL 服务器的默认机制是调用名为 sp_describe_undeclared_parameters 的系统存储过程。该存储过程对于#local_temp tables 有一些困难,而这些困难不会出现在常规 tables 或 ##global_temp tables 中。 this GitHub issue.

中的详细信息

如相关 wiki entry 中所述,解决方法包括

  • 使用 Cursor.setinputsizes() 显式声明列类型,
  • 使用##global_temp table 代替#local_temp table,或
  • UseFMTONLY=Yes 添加到 ODBC 连接字符串。

使用 SQLAlchemy 启用 UseFMTONLY 的最简单方法是使用 pass-through pyodbc connection string,例如

from sqlalchemy.engine import URL

connection_string = (
    "DRIVER=ODBC Driver 17 for SQL Server;"
    "SERVER=192.168.0.199;"
    "DATABASE=test;"
    "UID=scott;PWD=tiger^5HHH;"
    "UseFMTONLY=Yes;"
)

connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string})

engine = create_engine(connection_url, fast_executemany=True)