【问题标题】:Pandas to_sql gives ValueError with timezone-aware columnPandas to_sql 为 ValueError 提供时区感知列
【发布时间】:2016-05-27 21:37:50
【问题描述】:

我想使用 pandas to_sql 在 postgresql 中存储一个时区感知列。

当时间不是时区感知时,它可以工作

times = ['201510100222', '201510110333']
df = pd.DataFrame()
df['time'] = pd.to_datetime(times)
df.time.to_sql('test', engine, if_exists='replace', index=False)

但是当我指定 UTC 时

times = ['201510100222', '201510110333']
df = pd.DataFrame()
df['time'] = pd.to_datetime(times, utc=True)
df.time.to_sql('test', engine, if_exists='replace', index=False)

我有以下错误:

ValueError: Cannot cast DatetimeIndex to dtype datetime64[us]

我正在使用 python 3.4.3、postgresql 9.4、pandas 0.17.1、sqlalchemy 1.0.5

【问题讨论】:

  • 目前还不支持,见github.com/pydata/pandas/issues/9086
  • 谢谢@joris,你知道有什么快速的解决方法吗?
  • 将您的日期时间列转换为天真的日期时间并插入那些?

标签: python postgresql pandas sqlalchemy


【解决方案1】:

我发现这对我有用(甚至允许 tz 转换):

from sqlalchemy.types import TIMESTAMP as typeTIMESTAMP
df = pd.DataFrame({
    'datetime': ['2000-10-29 00:00', '2000-10-29 01:00', '2000-10-29 02:00', '2000-10-29 03:00'],
    'a': range(4),
})
df.datetime = [pd.Timestamp(i, tz='UTC').tz_convert(tz='Europe/London') for i in df.datetime]
df.datetime = df.datetime.astype(pd.Timestamp)  # needs to re-convert to Timestamp
df.to_sql(name=TEST_TABLE_NAME, con=conn, index=False, dtype={'datetime': typeTIMESTAMP(timezone=True)})

# if you want to set it as primary key
conn.execute("""ALTER TABLE "{}" ADD PRIMARY KEY (datetime);""".format(TABLE_NAME)) 

【讨论】:

    【解决方案2】:

    您必须将其作为 pd.Timestamp 存储在 PostgreSQL 中。下面的代码对我有用:

    times = ['201510100222', '201510110333']
    df = pd.DataFrame()
    df['time'] = pd.to_datetime(times, utc=True)
    df['time'] = df['time'].astype(pd.Timestamp)
    df.time.to_sql('test', engine, if_exists='replace', index=False)
    

    但不要忘记正确创建数据类型为 TIMESTAMP WITH TIME ZONE 的数据库表。如果您直接从 to_sql 命令构建表,则必须明确指定它:

    from sqlalchemy.types import TIMESTAMP as typeTIMESTAMP
    df.time.to_sql('test', engine, if_exists='replace', index=False,dtype=typeTIMESTAMP(timezone=True))
    

    【讨论】:

    • 不幸的是,这不再适用于 pandas 1.0.3。为df['time'] = df['time'].astype(pd.Timestamp) 引发类型错误TypeError: dtype '<class 'pandas._libs.tslibs.timestamps.Timestamp'>' not understood
    【解决方案3】:

    这适用于 pandas 0.16.2 ,因此您可以简单地降级 pandas 以避免错误:

    conda remove pandas
    conda install pandas=0.16.2
    

    在数据库中:

    (1) 在 postgresq.conf 中设置时区 = 'UTC'。这使得 UTC 成为所有连接到您的数据库的默认时区

    (2) 对数据库中的所有时间戳列使用带时区的时间戳(又名 timestamptz)。它们将值存储为 UTC,但在选择时将它们转换为您的时区设置。

    在 Python 中:

    (3) 始终使用 UTC 时区创建时间戳:

    def get_now_in_utc():
        now = datetime.now(tz=pytz.utc)
        return now
    

    (4) 并用 pandas to_sql 持久化它们

    结果:

    (5) 这将使您的持久性具有时区意识和准确性。

    (6) 当从数据库查询时(在查询中使用 AT TIME ZONE 表达式)或在代码中(python 中的时区转换),您始终可以获取 UTC 时间并将其转换为您喜欢的任何时间。

    【讨论】:

    • Pandas 0.16.2 还不支持时区感知列,所以没有那么简单。
    • @joris : (1) 在 postgresq.conf 中设置时区 = 'UTC'。这使得 UTC 成为与数据库的所有连接的默认时区 (2) 对数据库中的所有时间戳列使用带时区的时间戳(又名 timestamptz)。它们将值存储为 UTC,但在选择时将它们转换为您的时区设置。
    【解决方案4】:

    您可以将日期时间转换为字符串:

    times = pd.DatetimeIndex(['201510100222', '201510110333'], tz="UTC")
    df = pd.DataFrame()
    df['time'] = [time.strftime(format="%Y-%m-%d %H:%M:%S%z") for time in times]
    

    然后将它们作为日期时间插入数据库:

    from sqlalchemy import TIMESTAMP
    df.to_sql('test', engine, if_exists='replace', index=False,
              dtype={'time': TIMESTAMP(timezone=True)})
    

    这是一个非常丑陋的解决方案,但在我的设置中,它可以工作。

    请注意,postgres 将显示您当前时区的日期时间。我的是欧洲/巴黎,所以这是我查询它们时得到的结果(psql):

    test=# select * from test;
              time          
    ------------------------
     2015-10-10 04:22:00+02
     2015-10-11 05:33:00+02
    (2 rows)
    

    而不是类似的东西

              time          
    ------------------------
     2015-10-10 02:22:00+00
     2015-10-11 03:33:00+00
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-03
      • 2019-03-29
      • 2016-03-26
      • 2015-06-03
      • 2013-07-08
      • 2018-04-22
      • 2019-12-21
      相关资源
      最近更新 更多