【问题标题】:How to get pandas dataframe.to_sql to use default values defined by database DDL for NaN?如何让 pandas dataframe.to_sql 使用数据库 DDL 为 NaN 定义的默认值?
【发布时间】:2021-07-08 11:39:26
【问题描述】:

在我的数据库 ddl (SQL Server) 中,我的字段具有默认值:

my_database_field varchar(255)  NOT NULL 
     DEFAULT  'NVT'

但是,当我尝试使用 DataFrame.to_sql() 将数据插入此表时,我收到以下错误:

Cannot insert the value NULL into column 'my_database_field', table 'MYDB.dbo.my_table'; column does not allow nulls. INSERT fails. 

代码如下:

with engine.begin() as conn:
    dataframe.to_sql(table_name, conn, index=False, if_exists='append')

“my_database_field”列具有 NaN 值:

data['my_database_field']
0      NaN
1      NaN
2      NaN
3      NaN
4      NaN
        ..
4154   NaN
4155   NaN
4156   NaN
4157   NaN
4158   NaN

这似乎是因为 to_sql 方法正在用 NULL 替换 NaN 值(可能应该是这样)

我想要插入 NaN 值的选项,并让数据库使用架构中定义的 DEFAULT 值,而不是在使用 to_sql 方法时直接插入 NULL。

我尝试用空字符串替换数据框中的所有 NaN,但是这也遇到了同样的问题。它将那些空字符串传播到数据库,这就是显示的内容,而不是 DDL 中指定的默认值。

我可以让它工作的唯一方法是根本不使用 pandas dataframe.to_sql 方法,而是从 csv 文件加载数据并使用基本的 sqlalchemy SQL 表达式逐行写入数据库。

我在 pandas github 中打开了一个问题:https://github.com/pandas-dev/pandas/issues/42408

编辑:根据@Larnu 的评论,我缺少的关键信息是:

仅当您未在 INSERT 中指定列时才使用 DEFAULT 值。如果您(或应用程序)显式提供 NULL 值,则将插入 NULL 值。 DEFAULT 不是“在提供 NULL 时使用此值”属性,而是“在省略列时使用此值”

现在的问题变成了如何优雅地处理插入一些列包含一些 NaN 的大型数据框。我想避免不得不逐行插入数据,并检查该行中的任何值是否为 NaN。

编辑 2:我通过使用 ORM 解决了这个问题,如下所示:

Base = declarative_base()


class MyModel(Base):
    __tablename__ = 'my_table'

my_id = Column(String, primary_key=True)
my_integer_field = Column(Integer)
my_database_field = Column(String, server_default='NVT')

@staticmethod
def from_dict(input_dict: dict):
    for k, v in input_dict.items():
        logging.debug(f"Key: {k} - Value: {v}")
        if type(v) is str:
            continue
        try:
            if np.isnan(v):
                input_dict[k] = None
        except TypeError as e:
            logging.debug(e)
            logging.debug(f"Could not test if: {type(v)}:{v} was NaN")

    return __class__(**input_dict)

如您所见,我添加了一个名为“from_dict”的自定义静态方法,它根据字典构建我的模型。对于每个值,我检查它是否是 np.nan,如果是,我将其设置为 None。这似乎使 ORM 在幕后的 insert 方法中不包含该字段,因此让数据库使用默认值。 np.isnan() 方法只适用于数字而不是字符串,所以对于那些我只是捕捉到异常的人,注销一些信息并继续前进。

对于字符串值,我似乎必须在模型本身中设置“server_default”。如果有人知道更多关于这里发生的事情,请随时分享。

【问题讨论】:

  • DEFAULT 值仅在您未在 INSERT 中指定列时使用。如果您(或应用程序)明确提供 NULL 值,则将插入 NULL 值。 DEFAULT 不是“在提供 NULL 时使用此值”属性,而是“在省略列时使用此值”属性。
  • @Larnu 说得好。我不知道的东西。关于如何处理这个问题的任何建议?我需要批量插入这些数据,其中一些有值,其余的是 NaN。我想避免逐行检查给定行是否具有 NaN 值,如果有,则删除列标题。
  • 对不起,如果我错过了这个,但是“my_database_field”是您唯一关心的使用一些默认值的列吗?
  • 让 pandas 输入“默认”值有什么问题吗? .fillna('NVT')
  • 看来您需要先在Python端处理数据框,即:用数据库中指定的列默认值替换NaN值。

标签: python sql sql-server pandas


【解决方案1】:

正如@Larnu 在他的评论中提到的,只有当insert 语句中没有说明该列时才会使用默认值。 Pandas 在数据框中看到该列名并插入。因此,您需要拆分数据并删除需要默认值的列。

对于小数据,您可以遍历行,在列上使用dropna,然后使用to_sql

对于较大的数据集,您可以拆分数据,以便只上传没有 NaN 的行。然后为每个列组合创建集合,在所有这些列值为空的行上,删除列并仅上传确实有数据的列。例如具有AB 列的数据框,上传两个值都不为空的所有行,仅在B 中的值为空时上传A 列的数据框,反之亦然。

import numpy as np
import pandas as pd
import itertools

#create random dataframe of integers
df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), 
    columns=['A','B','C','D'])
#turn ~20% of them into NaN's
df = df.mask(np.random.random(df.shape) < .2)

#upload the no nulls data first
df.dropna().to_sql(table_name, conn, index=False, if_exists='append')

#get column names into list
cols = list(df.columns)
#create powerset of column names
p_set = itertools.chain.from_iterable(
    itertools.combinations(cols,r) for r in range(1,len(cols)))

#iterate through all combinations of column names
for null_cols in p_set:
    #determine what the columns are where we DO want values
    not_null_cols = list(set(cols) - set(null_cols))

    #filter original dataframe to rows where ALL the null_cols are 
    # null AND ALL the other cols have a non-null value
    sub_df = df[df[list(null_cols)].isnull().all(1) &
                df[list(not_null_cols)].notnull().all(1)]

    #if this dataframe has values, send it to database
    if len(sub_df) > 0:
        #sub_df still has all columns at this point, only keep the
        #  ones with values
        sub_df = sub_df[not_null_cols]
        sub_df.to_sql(table_name, conn, index=False, if_exists='append')

【讨论】:

  • 我考虑过这个想法,但只删除了所有带有 NaN 的行。我发现在 4000 行的数据集中,只有大约 150 行在任何列中没有 NaN。我实际上无法弄清楚如何分解除此之外的数据。因此,请您解决这个问题。最后,我使用了 ORM,请参阅编辑后的帖子。感谢您的回答!
猜你喜欢
  • 1970-01-01
  • 2016-05-15
  • 2015-03-18
  • 2011-10-06
  • 1970-01-01
  • 2019-07-29
  • 1970-01-01
  • 2020-05-29
  • 2017-02-05
相关资源
最近更新 更多