【问题标题】:Subtracting Dates in Oracle - Number or Interval Datatype?在 Oracle 中减去日期 - 数字或间隔数据类型?
【发布时间】:2012-03-08 12:56:16
【问题描述】:

我对 Oracle DATE 和 INTERVAL 数据类型的一些内部工作有疑问。根据Oracle 11.2 SQL Reference,当您减去 2 个 DATE 数据类型时,结果将是 NUMBER 数据类型。

经过粗略的测试,这似乎是真的:

CREATE TABLE test (start_date DATE);
INSERT INTO test (start_date) VALUES (date'2004-08-08');
SELECT (SYSDATE - start_date) from test;

将返回一个 NUMBER 数据类型。

但是现在如果你这样做:

SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;

你得到一个 INTERVAL 数据类型。换句话说,Oracle 可以将 DATE 减法中的 NUMBER 转换为 INTERVAL 类型。

所以现在我想我可以尝试直接在括号中输入一个 NUMBER 数据类型(而不是执行 'SYSDATE - start_date' 无论如何都会导致一个 NUMBER):

SELECT (1242.12423) DAY(5) TO SECOND from test;

但这会导致错误:

ORA-30083: syntax error was found in interval value expression

所以我的问题是:这里发生了什么?似乎减去日期应该导致一个数字(如 SELECT 语句 #1 所示),它不能自动转换为 INTERVAL 类型(如 SELECT 语句 #3 所示)。但是,如果您使用 DATE 减法表达式而不是输入原始 NUMBER(SELECT 语句 #2),Oracle 似乎能够以某种方式做到这一点。

谢谢

【问题讨论】:

  • 当您使用 to_date('20120216', 'yyyymmdd') 代替 sysdate 时会发生这种情况吗?
  • 您好,抱歉,直到现在才看到您的评论:S.. 首先回答您的问题,是的,即使它使用 'to_date()' 而不是 SYSDATE,它也会这样做。不过,在另一张纸条上,我设法弄清楚 DATE 减法是如何存储的,所以我将其发布为我的答案:D .. 因为互联网上似乎还没有其他人写过这个,我想我会写下我的发现了。
  • @BYS2 - 为什么要在 () 中输入一个数字,然后尝试从数字中减去天数并将其转换为秒数:SELECT (1242.12423) DAY(5) TO SECOND from test?这是你需要问的。

标签: sql oracle date plsql oracle11g


【解决方案1】:

您收到语法错误,因为日期数学不返回 NUMBER,但返回 INTERVAL:

SQL> SELECT DUMP(SYSDATE - start_date) from test;

DUMP(SYSDATE-START_DATE)
-------------------------------------- 
Typ=14 Len=8: 188,10,0,0,223,65,1,0

您需要先使用 NUMTODSINTERVAL Function 将示例中的数字转换为 INTERVAL

例如:

SQL> SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;

(SYSDATE-START_DATE)DAY(5)TOSECOND
----------------------------------
+02748 22:50:04.000000

SQL> SELECT (SYSDATE - start_date) from test;

(SYSDATE-START_DATE)
--------------------
           2748.9515

SQL> select NUMTODSINTERVAL(2748.9515, 'day') from dual;

NUMTODSINTERVAL(2748.9515,'DAY')
--------------------------------
+000002748 22:50:09.600000000

SQL>

根据使用 NUMTODSINTERVAL() 函数的反向转换,似乎在翻译中丢失了一些舍入。

【讨论】:

  • 啊,额外的 5 秒可能是挂钟,因为 SYSDATE 是参考点。我会冒险猜测没有四舍五入。
  • hmm 感谢您的回复,但这很奇怪,因为 dump() 函数返回的类型为 '14'。但是 INTERVAL YEAR TO MONTH 是类型 182,DAY TO SECOND 是类型 183 (docs.oracle.com/cd/B10500_01/appdev.920/a96584/…)...此外,如果日期减法确实返回一个间隔,那么当您减去日期时,为什么 SQL*Plus 会显示“2748.9515”?以及为什么官方参考手册指出日期减法结果为 NUMBER?
  • DATE 减法显然会产生 FLOAT。 (有人有一些非常古老的 Oracle 文档表明这可能曾经是这样吗?)试试SELECT * FROM DBA_TYPES WHERE TYPE_OID = '0000000000000000000000000000000E'。另请查看 SYS.STANDARD 包,其中 FLOAT 看起来与 NUMBER 实际上相同(即subtype FLOAT is NUMBER)。分享和享受。
  • @BobJarvis 从 Oracle 11g DUMP() 函数中可以清楚地看出,DATE 减法返回 Typ=14。旧文档的价值不大。 Google for Oracle Date Interval "Typ=14" 并且有一些关于 Type 14 的讨论,但在任何地方都没有准确记录。
  • 我想通了! :D。阅读我发布的解决方案。感谢您的意见,非常感谢
【解决方案2】:

好的,我通常不会回答自己的问题,但经过一番修改后,我已经明确地弄清楚 Oracle 如何存储 DATE 减法的结果。

当您减去 2 个日期时,该值不是 NUMBER 数据类型(正如 Oracle 11.2 SQL Reference manual 会让您相信的那样)。 DATE 减法的内部数据类型编号为 14,这是一个未记录的内部数据类型(NUMBER 为 internal datatype number 2)。但是,它实际上存储为 2 个单独的二进制补码符号数,前 4 个字节用于表示天数,后 4 个字节用于表示秒数。

导致正整数差的 DATE 减法示例:

select date '2009-08-07' - date '2008-08-08' from dual;

结果:

DATE'2009-08-07'-DATE'2008-08-08'
---------------------------------
                              364

select dump(date '2009-08-07' - date '2008-08-08') from dual;

DUMP(DATE'2009-08-07'-DATE'2008
-------------------------------
Typ=14 Len=8: 108,1,0,0,0,0,0,0

回想一下,结果表示为 2 个单独的二进制补码符号 4 字节数字。由于在这种情况下没有小数(正好是 364 天和 0 小时),所以最后 4 个字节都是 0,可以忽略。前 4 个字节,因为我的 CPU 是 little-endian 架构,所以字节是颠倒的,应该读为 1,108 或 0x16c,即十进制 364。

导致负整数差的 DATE 减法示例:

select date '1000-08-07' - date '2008-08-08' from dual;

结果:

DATE'1000-08-07'-DATE'2008-08-08'
---------------------------------
                          -368160

select dump(date '1000-08-07' - date '2008-08-08') from dual;

DUMP(DATE'1000-08-07'-DATE'2008-08-0
------------------------------------
Typ=14 Len=8: 224,97,250,255,0,0,0,0

再次,由于我使用的是 little-endian 机器,因此字节是颠倒的,应该被读取为 255,250,97,224,对应于 11111111 11111010 01100001 11011111。现在因为这是二进制补码符号二进制数字编码,我们知道该数字为负数,因为最左边的二进制数字是 1。要将其转换为十进制数,我们必须反转 2 的补码(减去 1 然后做一个的补码),结果是:00000000 00000101 10011110 00100000 等于 -368160 怀疑.

导致小数差异的 DATE 减法示例:

select to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS'
 - to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS') from dual;

TO_DATE('08/AUG/200414:00:00','DD/MON/YYYYHH24:MI:SS')-TO_DATE('08/AUG/20048:00:
--------------------------------------------------------------------------------
                                                                             .25

这两个日期之间的差是 0.25 天或 6 小时。

select dump(to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS')
 - to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS')) from dual;

DUMP(TO_DATE('08/AUG/200414:00:
-------------------------------
Typ=14 Len=8: 0,0,0,0,96,84,0,0

现在这一次,因为相差0天6小时,所以预计前4个字节是0。对于后4个字节,我们可以倒过来(因为CPU是little-endian)得到84, 96 = 01010100 01100000 基数 2 = 21600 十进制。将 21600 秒转换为小时后,您可以得到 6 小时,这是我们预期的差异。

希望这对想知道如何实际存储 DATE 减法的任何人有所帮助。

【讨论】:

  • 我尽量避免使用日期,将它们转换为时间戳并将差异存储/保持为间隔。当然,比您在此处看到的隐式/内部数字类型更详细。但它更一致,记录更完善。
【解决方案3】:

几点:

  • 从另一个日期中减去一个日期得到一个数字;从另一个时间戳中减去一个时间戳会产生一个间隔。

  • Oracle 在执行时间戳算术时在内部将时间戳转换为日期。

  • 不能在日期或时间戳算术中使用间隔常量。

Oracle 11gR2 SQL Reference Datetime Matrix

【讨论】:

  • 嘿,感谢您的输入,但请参阅我的回答。在这种情况下,Oracle 文档实际上是不正确/不完整的
【解决方案4】:

选择 TIMEDIFF (STR_TO_DATE('07:15 PM', '%h:%i %p') , STR_TO_DATE('9:58 AM', ' %h:%i %p'))

【讨论】:

  • 这不会试图回答这个问题,并且不是 Oracle 的有效语法;您是否错过了标题和问题中的 Oracle 标记和引用?
【解决方案5】:

使用 extract() 函数从间隔值中检索小时/分钟/秒。请参见下面的示例,如何从两个时间戳列中获取小时数。希望这会有所帮助!

选择 INS_TS,MAIL_SENT_TS,从 MAIL_NTFCTN 中提取(小时从(INS_TS - MAIL_SENT_TS))小时差异;

【讨论】:

    猜你喜欢
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多