【问题标题】:How to convert Unix timestamp to local date in Oracle?如何在 Oracle 中将 Unix 时间戳转换为本地日期?
【发布时间】:2021-06-25 18:11:53
【问题描述】:

互联网,请帮忙!

我使用的是 Oracle DB v 12c,其中发票开具时间存储为 NUMBER 类型列中的 unix 时间戳 + 还有一个 VARCHAR2(128) 列,它定义了开具发票的时区,例如“欧洲/伦敦”。

所以在本地日期“2020-10-10”开具的发票可以这样查询:

SELECT inv.invoiceID
FROM invoices inv
WHERE TO_CHAR(
                  FROM_TZ(timestamp '1970-01-01 00:00:00'
                              + NUMTODSINTERVAL(inv.INVOICETIME, 'SECOND'), 'UTC') AT TIME ZONE inv.timezoneName,
                  'YYYY-MM-DD') = '2020-10-10';

但不幸的是,此查询不可索引。

问题是,这不起作用:

CREATE INDEX Invoices_InvoiceDate ON Invoices (
        TO_CHAR(
                  FROM_TZ(timestamp '1970-01-01 00:00:00'
                              + NUMTODSINTERVAL(INVOICETIME, 'SECOND'), 'UTC') AT TIME ZONE timezoneName,
                  'YYYY-MM-DD')
                   );

错误: ORA-01743: 只能索引纯函数

由于使用了“AT TIME ZONE”,这个算子的“纯版”是什么?或者换句话说 - 如何在 Oracle 中将 Unix 时间戳属性转换为本地日期?

【问题讨论】:

  • 以您目前收集和存储数据的方式,我不相信该函数有任何“纯版本”,或者“正确的方式将”从 UTC 转换为“本地”日期(发票开具的“本地”,即;请注意,您对该术语的非标准使用会使读者感到困惑,因为“本地”一词通常表示“数据库会话本地”,而不是“时区本地”保存在表格中”)。
  • 所以:没有“纯函数”,因为要转换日期,Oracle 必须查阅时区偏移表。如果一个函数必须在表中查找,它会自动被认为是不确定的:表中的数据可能会随着时间而改变,因此该函数不会总是产生相同的结果。你和我可能相信这个特定的查找表永远不会改变,但是查询解析器并没有那么详细;该函数使用表查找,因此它是不确定的、不可索引的周期。因此,如果您必须有索引,则需要找到一种不同的方式来存储输入数据。
  • 感谢@mathguy 确认在这种情况下,由于时区查找表,基于函数的索引是不可能的。所以我可能会通过将问题日期存储在单独的列中来解决它。v
  • 对我来说,不清楚您在寻找什么。将日期/时间值与 strings 进行比较通常不是明智的选择。并且在没有任何时间的情况下对日期值使用时区也没有多大意义——至少它会让你更难理解你的意图。我假设您实际上正在寻找这个WHERE TIMESTAMP '1970-01-01 00:00:00 UTC' + NUMTODSINTERVAL(INVOICETIME, 'SECOND') = FROM_TZ(TIMESTAMP '2020-10-10 00:00:00', timezoneName)

标签: oracle timezone unix-timestamp


【解决方案1】:

您不能像 @mathguy 解释的那样在特定时区创建索引:

CREATE INDEX IND_INVOICETIME_LOCAL ON INVOICE (
    (TIMESTAMP '1970-01-01 00:00:00 UTC' + NUMTODSINTERVAL(INVOICETIME, 'SECOND')) AT TIME ZONE timezoneName
);

ORA-01743: only pure functions can be indexed

您可以做的是在 UTC 时间上创建一个索引:

CREATE INDEX IND_INVOICETIME_UTC ON INVOICE (
    TIMESTAMP '1970-01-01 00:00:00 UTC' + NUMTODSINTERVAL(INVOICETIME, 'SECOND')
);

那么条件是这样的:

WHERE TIMESTAMP '1970-01-01 00:00:00 UTC' + NUMTODSINTERVAL(INVOICETIME, 'SECOND') 
   BETWEEN FROM_TZ(TIMESTAMP '2020-10-10 00:00:00', timezoneName) 
       AND FROM_TZ(TIMESTAMP '2020-10-10 23:59:59', timezoneName)

Oracle 总是在 UTC 时间比较 TIMESTAMP WITH TIME ZONE 值。 注意,当您有TIMESTAMP WITH TIME ZONE 列并在其上创建索引时,Oracle 会添加SYS_EXTRACT_UTC(...) 的虚拟列,并在此虚拟​​列上创建索引。正确使用这样的索引/列可能具有挑战性,请参阅https://tonyhasler.wordpress.com/2010/09/04/tonys-tirade-against-timestamp-with-time-zone/

我认为正确的用法是:

WHERE SYS_EXTRACT_UTC(TIMESTAMP '1970-01-01 00:00:00 UTC' + NUMTODSINTERVAL(INVOICETIME, 'SECOND') ) 
    BETWEEN SYS_EXTRACT_UTC(FROM_TZ(TIMESTAMP '2020-10-10 00:00:00', inv.timezoneName)) 
        AND SYS_EXTRACT_UTC(FROM_TZ(TIMESTAMP '2020-10-10 23:59:59', inv.timezoneName))

【讨论】:

  • 谢谢!这是一个很好的建议,但它有一个问题,即索引没有帮助,因为条件与inv.timezoneName 匹配。因此,使用这种数据结构似乎仍然无法创建帮助索引。
猜你喜欢
  • 2019-03-24
  • 2016-06-07
  • 1970-01-01
  • 1970-01-01
  • 2020-10-19
  • 2018-09-03
  • 1970-01-01
  • 2020-12-23
  • 2016-04-24
相关资源
最近更新 更多