在Oracle中减去日期 - 数字或间隔数据类型?

BYS*_*YS2 21 sql oracle plsql date oracle11g

我对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;
Run Code Online (Sandbox Code Playgroud)

将返回NUMBER数据类型.

但现在如果你这样做:

SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;
Run Code Online (Sandbox Code Playgroud)

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

所以现在我想我可以尝试直接在括号中输入NUMBER数据类型(而不是做'SYSDATE - start_date',这会导致NUMBER反正):

SELECT (1242.12423) DAY(5) TO SECOND from test;
Run Code Online (Sandbox Code Playgroud)

但这会导致错误:

ORA-30083: syntax error was found in interval value expression
Run Code Online (Sandbox Code Playgroud)

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

谢谢

BYS*_*YS2 34

好吧,我通常不回答我自己的问题,但经过一些修补,我明确地想出了Oracle如何存储DATE减法的结果.

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

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

select date '2009-08-07' - date '2008-08-08' from dual;
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

回想一下,结果表示为2个单独的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;
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

再次,由于我使用的是小端机器,字节相反,应该读取为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
Run Code Online (Sandbox Code Playgroud)

这两个日期之间的差异是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
Run Code Online (Sandbox Code Playgroud)

这一次,由于差异是0天和6小时,预计前4个字节为0.对于最后4个字节,我们可以反转它们(因为CPU是小端)并得到84,96 = 01010100 01100000 base 2 = 21600十进制.将21600秒转换为小时为您提供6小时,这是我们预期的差异.

希望这有助于任何想知道如何实际存储DATE减法的人.


taw*_*man 6

您得到语法错误,因为日期数学不返回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
Run Code Online (Sandbox Code Playgroud)

您需要首先使用NUMTODSINTERVAL函数将示例中的数字转换为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>
Run Code Online (Sandbox Code Playgroud)

基于使用NUMTODSINTERVAL()函数的反向投射,在翻译中会出现一些舍入丢失.