如何正确地将 pandas NaT 日期时间值插入到我的 postgresql table
How to properly insert pandas NaT datetime values to my postgresql table
我想将一个数据帧批量插入到我的 postgres DB 中。我的数据框中的某些列是 NaT
为空值的日期类型。 PostgreSQL 不支持,我尝试用其他 NULL 类型标识替换 NaT
(使用 pandas),但在我的插入过程中没有用。
我用 df = df.where(pd.notnull(df), 'None')
替换了所有 NaT
s,由于数据类型问题不断出现的错误示例。
Error: invalid input syntax for type date: "None"
LINE 1: ...0,1.68757,'2022-11-30T00:29:59.679000'::timestamp,'None','20...
我的驱动程序和 insert 语句到 postgresql DB:
def execute_values(conn, df, table):
"""
Using psycopg2.extras.execute_values() to insert the dataframe
"""
# Create a list of tupples from the dataframe values
tuples = [tuple(x) for x in df.to_numpy()]
# Comma-separated dataframe columns
cols = ','.join(list(df.columns))
# SQL quert to execute
query = "INSERT INTO %s(%s) VALUES %%s" % (table, cols)
cursor = conn.cursor()
try:
extras.execute_values(cursor, query, tuples)
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print("Error: %s" % error)
conn.rollback()
cursor.close()
return 1
print("execute_values() done")
cursor.close()
关于我的数据框的信息:对于这种情况,罪魁祸首只是日期时间列。
这个一般是怎么解决的?
您是 re-inventing 的掌舵人。只需使用 pandas' to_sql 方法,它将
- 匹配列名,
- 注意
NaT
值。
使用 method="multi"
可获得与 psycopg2 的 execute_values
相同的效果。
from pprint import pprint
import pandas as pd
import sqlalchemy as sa
table_name = "so64435497"
engine = sa.create_engine("postgresql://scott:tiger@192.168.0.199/test")
with engine.begin() as conn:
# set up test environment
conn.exec_driver_sql(f"DROP TABLE IF EXISTS {table_name}")
conn.exec_driver_sql(
f"CREATE TABLE {table_name} ("
"id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, "
"txt varchar(50), "
"txt2 varchar(50), "
"dt timestamp)"
)
df = pd.read_csv(r"C:\Users\Gord\Desktop\so64435497.csv")
df["dt"] = pd.to_datetime(df["dt"])
print(df)
"""console output:
dt txt2 txt
0 2020-01-01 00:00:00 foo2 foo
1 NaT bar2 bar
2 2020-01-02 03:04:05 baz2 baz
"""
# run test
df.to_sql(
table_name, conn, index=False, if_exists="append", method="multi"
)
pprint(
conn.exec_driver_sql(
f"SELECT id, txt, txt2, dt FROM {table_name}"
).all()
)
"""console output:
[(1, 'foo', 'foo2', datetime.datetime(2020, 1, 1, 0, 0)),
(2, 'baz', 'baz2', None),
(3, 'bar', 'bar2', datetime.datetime(2020, 1, 2, 3, 4, 5))]
"""
关于您的原始更新声明:
df = df.where(pd.notnull(df), 'None')
这里发生的事情是您将值替换为字符串 'None' 而不是特殊的 Python 对象 None。然后在下面的插入语句中,它尝试将字符串 'None' 插入时间戳字段并抛出错误。
有趣的是,您期望的版本是:
df = df.where(pd.notnull(df), None)
由于我不完全理解的原因,对于 NaT 值实际上似乎没有按预期工作。 (见下面的例子)
但是这条语句似乎起作用了(假设您将 numpy 导入为 np):
df = df.replace({np.NaN: None})
因此,如果您这样做,则 NaN 和 NaT 值全部转换为 Python None 然后 psycopg2(或可能任何其他数据库连接器)将正确地将这些值视为 SQL 插入时的空值.
下面是一些示例代码来说明:
import datetime as dt
import pandas as pd
import numpy as np
data = [
['one', 1.0, pd.NaT],
['two', np.NaN, dt.datetime(2019, 2, 2)],
[None, 3.0, dt.datetime(2019, 3, 3)]
]
df = pd.DataFrame(data, columns=["Name", "Value", "Event_date"])
得到我们的基本数据框:
>>> df
Name Value Event_date
0 one 1.0 NaT
1 two NaN 2019-02-02
2 None 3.0 2019-03-03
如上所述,此更新出于某种原因在其中保留了 NaT:
>>> df.where(pd.notnull(df), None)
Name Value Event_date
0 one 1.0 NaT
1 two None 2019-02-02
2 None 3.0 2019-03-03
但是这个版本得到了 NaNs 和 NaTs 并留下了预期的 Nones:
>>> df.replace({np.NaN: None})
Name Value Event_date
0 one 1.0 None
1 two None 2019-02-02 00:00:00
2 None 3.0 2019-03-03 00:00:00
如果你可以使用 sqlalchemy 来做你想做的事情,那么公认的答案可能是“更好”的方法,但如果你必须以艰难的方式去做,这对我有用。
H/T 到 discussion in this pandas issue 以获得此答案的大部分细节。
如果您不能使用 pandas
的 to_sql
方法,您可以 register an adapter 和 psycopg
代替:
import pandas as pd
from psycopg2.extensions import register_adapter, AsIs
# Register adapter for pandas NA type (e.g. null datetime or integer values)
# NOTE: Must use protected member, rather than pd.NA, as pd.NA is just defined as None
register_adapter(pd._libs.missing.NAType, lambda i: AsIs('NULL'))
当您随后调用 psycopg
的任何 execute
方法时,它会自动将任何 pd.NA
值转换为 PostgreSQL NULL
值。
请注意,同样的原则也可以用于 numpy
NaN
值:
import numpy as np
from psycopg2.extensions import register_adapter, AsIs, Float
# Register adapter for np.nan
register_adapter(float, lambda f: AsIs('NULL') if np.isnan(f) else Float(f))
我想将一个数据帧批量插入到我的 postgres DB 中。我的数据框中的某些列是 NaT
为空值的日期类型。 PostgreSQL 不支持,我尝试用其他 NULL 类型标识替换 NaT
(使用 pandas),但在我的插入过程中没有用。
我用 df = df.where(pd.notnull(df), 'None')
替换了所有 NaT
s,由于数据类型问题不断出现的错误示例。
Error: invalid input syntax for type date: "None"
LINE 1: ...0,1.68757,'2022-11-30T00:29:59.679000'::timestamp,'None','20...
我的驱动程序和 insert 语句到 postgresql DB:
def execute_values(conn, df, table):
"""
Using psycopg2.extras.execute_values() to insert the dataframe
"""
# Create a list of tupples from the dataframe values
tuples = [tuple(x) for x in df.to_numpy()]
# Comma-separated dataframe columns
cols = ','.join(list(df.columns))
# SQL quert to execute
query = "INSERT INTO %s(%s) VALUES %%s" % (table, cols)
cursor = conn.cursor()
try:
extras.execute_values(cursor, query, tuples)
conn.commit()
except (Exception, psycopg2.DatabaseError) as error:
print("Error: %s" % error)
conn.rollback()
cursor.close()
return 1
print("execute_values() done")
cursor.close()
关于我的数据框的信息:对于这种情况,罪魁祸首只是日期时间列。
这个一般是怎么解决的?
您是 re-inventing 的掌舵人。只需使用 pandas' to_sql 方法,它将
- 匹配列名,
- 注意
NaT
值。
使用 method="multi"
可获得与 psycopg2 的 execute_values
相同的效果。
from pprint import pprint
import pandas as pd
import sqlalchemy as sa
table_name = "so64435497"
engine = sa.create_engine("postgresql://scott:tiger@192.168.0.199/test")
with engine.begin() as conn:
# set up test environment
conn.exec_driver_sql(f"DROP TABLE IF EXISTS {table_name}")
conn.exec_driver_sql(
f"CREATE TABLE {table_name} ("
"id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, "
"txt varchar(50), "
"txt2 varchar(50), "
"dt timestamp)"
)
df = pd.read_csv(r"C:\Users\Gord\Desktop\so64435497.csv")
df["dt"] = pd.to_datetime(df["dt"])
print(df)
"""console output:
dt txt2 txt
0 2020-01-01 00:00:00 foo2 foo
1 NaT bar2 bar
2 2020-01-02 03:04:05 baz2 baz
"""
# run test
df.to_sql(
table_name, conn, index=False, if_exists="append", method="multi"
)
pprint(
conn.exec_driver_sql(
f"SELECT id, txt, txt2, dt FROM {table_name}"
).all()
)
"""console output:
[(1, 'foo', 'foo2', datetime.datetime(2020, 1, 1, 0, 0)),
(2, 'baz', 'baz2', None),
(3, 'bar', 'bar2', datetime.datetime(2020, 1, 2, 3, 4, 5))]
"""
关于您的原始更新声明:
df = df.where(pd.notnull(df), 'None')
这里发生的事情是您将值替换为字符串 'None' 而不是特殊的 Python 对象 None。然后在下面的插入语句中,它尝试将字符串 'None' 插入时间戳字段并抛出错误。
有趣的是,您期望的版本是:
df = df.where(pd.notnull(df), None)
由于我不完全理解的原因,对于 NaT 值实际上似乎没有按预期工作。 (见下面的例子)
但是这条语句似乎起作用了(假设您将 numpy 导入为 np):
df = df.replace({np.NaN: None})
因此,如果您这样做,则 NaN 和 NaT 值全部转换为 Python None 然后 psycopg2(或可能任何其他数据库连接器)将正确地将这些值视为 SQL 插入时的空值.
下面是一些示例代码来说明:
import datetime as dt
import pandas as pd
import numpy as np
data = [
['one', 1.0, pd.NaT],
['two', np.NaN, dt.datetime(2019, 2, 2)],
[None, 3.0, dt.datetime(2019, 3, 3)]
]
df = pd.DataFrame(data, columns=["Name", "Value", "Event_date"])
得到我们的基本数据框:
>>> df
Name Value Event_date
0 one 1.0 NaT
1 two NaN 2019-02-02
2 None 3.0 2019-03-03
如上所述,此更新出于某种原因在其中保留了 NaT:
>>> df.where(pd.notnull(df), None)
Name Value Event_date
0 one 1.0 NaT
1 two None 2019-02-02
2 None 3.0 2019-03-03
但是这个版本得到了 NaNs 和 NaTs 并留下了预期的 Nones:
>>> df.replace({np.NaN: None})
Name Value Event_date
0 one 1.0 None
1 two None 2019-02-02 00:00:00
2 None 3.0 2019-03-03 00:00:00
如果你可以使用 sqlalchemy 来做你想做的事情,那么公认的答案可能是“更好”的方法,但如果你必须以艰难的方式去做,这对我有用。
H/T 到 discussion in this pandas issue 以获得此答案的大部分细节。
如果您不能使用 pandas
的 to_sql
方法,您可以 register an adapter 和 psycopg
代替:
import pandas as pd
from psycopg2.extensions import register_adapter, AsIs
# Register adapter for pandas NA type (e.g. null datetime or integer values)
# NOTE: Must use protected member, rather than pd.NA, as pd.NA is just defined as None
register_adapter(pd._libs.missing.NAType, lambda i: AsIs('NULL'))
当您随后调用 psycopg
的任何 execute
方法时,它会自动将任何 pd.NA
值转换为 PostgreSQL NULL
值。
请注意,同样的原则也可以用于 numpy
NaN
值:
import numpy as np
from psycopg2.extensions import register_adapter, AsIs, Float
# Register adapter for np.nan
register_adapter(float, lambda f: AsIs('NULL') if np.isnan(f) else Float(f))