You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

无法抑制DataFrame中的科学计数法导致pyodbc插入SQL Server时出现nvarchar转numeric错误

无法抑制DataFrame中的科学计数法导致pyodbc插入SQL Server时出现nvarchar转numeric错误

看起来你遇到的核心问题是:把浮点数转成字符串时,极小值会自动触发科学计数法,而SQL Server的DECIMAL类型无法识别这种格式的字符串。你之前尝试的pd.options.display.float_formatround之所以没用,是因为前者只控制显示效果、后者没法彻底避免科学计数法的触发,而且你最后把整个DataFrame转成字符串的操作,才是把科学计数法固化下来的关键。

下面给你几个针对性的解决方案,从简单处理到更推荐的优化方案都有:


方案1:转字符串时强制用非科学计数法格式

不要直接用df.astype(str),而是对数值列单独做格式化处理,确保转成字符串时是普通小数格式:

修改你的数据清洗代码:

# bigint list contains column names that I can treat as numeric
for col in bigint_list:
    df[col] = pd.to_numeric(df[col], errors='coerce')
    df[col] = df[col].fillna(0.0)

# 逐个处理列,避免科学计数法
for col in df.columns:
    if col in bigint_list:
        # 对数值列:格式化字符串,保留足够小数位,去掉末尾多余的0和小数点
        df[col] = df[col].apply(
            lambda x: f"{x:.10f}".rstrip('0').rstrip('.') if x != 0 else '0'
        )
    else:
        # 非数值列转字符串,处理空值
        df[col] = df[col].astype(str).replace({'nan': None})

# 最后统一处理空值为None
df = df.where(pd.notnull(df), None)

这个方法通过f"{x:.10f}"强制把浮点数转成10位小数的普通字符串,再用rstrip去掉末尾多余的0和小数点,既保证不会出现科学计数法,又让格式更干净。


方案2:用pandas内置的to_sql方法(更推荐)

其实你完全可以跳过手动拼SQL插入语句的步骤,用pandas的to_sql方法,它会自动处理数据类型映射,根本不需要你手动转字符串,自然就避免了科学计数法的问题:

首先需要安装SQLAlchemy(因为to_sql依赖它):

pip install sqlalchemy

然后修改插入代码:

from sqlalchemy import create_engine

# 构建SQLAlchemy连接引擎(替换成你的数据库信息)
conn_str = (
    f"mssql+pyodbc://{your_username}:{your_password}@{your_server}/{your_db}"
    "?driver=ODBC+Driver+17+for+SQL+Server"
)
engine = create_engine(conn_str)

# 插入数据:if_exists='append'表示追加到表中,index=False不插入DataFrame的索引列
df.to_sql(
    name=table_name,
    con=engine,
    if_exists='append',
    index=False,
    chunksize=1000  # 分批次插入,避免一次性传太多数据
)

这个方法的优势非常明显:不需要手动处理占位符、列名、数据类型转换,pandas会自动匹配SQL Server的DECIMAL列,把数值直接传进去,不会有科学计数法的麻烦,而且效率也更高。


方案3:保留数值类型,不转成字符串

你之前把整个DataFrame转成字符串是多余的操作,其实可以保留数值列的数值类型,只处理空值,直接传给pyodbc:

修改清洗代码:

# bigint list contains column names that I can treat as numeric
for col in bigint_list:
    df[col] = pd.to_numeric(df[col], errors='coerce')
    df[col] = df[col].fillna(0.0)

# 只处理非数值列的空值,数值列保持数值类型
for col in df.columns:
    if col not in bigint_list:
        df[col] = df[col].where(pd.notnull(df[col]), None)

# 把DataFrame转成tuple列表时,数值列还是数值类型
rows = [tuple(x) for x in df.where(pd.notnull(df), None).values]

这样pyodbc在执行executemany时,会把数值类型直接传给SQL Server的DECIMAL列,SQL Server能正确识别,不会因为字符串格式报错。


为什么之前的尝试没效果?

  • pd.options.display.float_format:这个设置只控制DataFrame在显示时的格式(比如print(df)或者在Notebook里查看的样子),不会改变把浮点数转成字符串时的实际结果,所以对astype(str)无效。
  • df[col].round(10):即使你把数值四舍五入到10位小数,当数值极小(比如0.0000000001),转成字符串时pandas还是会自动用科学计数法表示,所以这个方法没法彻底解决问题。

备注:内容来源于stack exchange,提问作者sdawar

火山引擎 最新活动