2012-02-17 312 views
17

Tengo una pregunta sobre algunos de los trabajos internos para los tipos de datos Oracle DATE e INTERVAL. De acuerdo con el Oracle 11.2 SQL Reference, al restar 2 tipos de datos DATE, el resultado será un tipo de datos NUMBER.Restar fechas en Oracle - Número o tipo de datos de intervalo?

En la prueba superficial, esto parece ser cierto:

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

devolverá un tipo de datos NUMBER.

Pero ahora si lo hace:

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

se obtiene un tipo de datos de intervalo. En otras palabras, Oracle puede convertir el NÚMERO de la resta DATE en un tipo INTERVALO.

Así que ahora me imaginaba que podría intentar poner en un tipo de datos número directamente en los soportes (en vez de hacer 'SYSDATE - fecha_inicial' que se traduce en un número de todos modos):

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

Pero esto resulta en el error :

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

Así que mi pregunta es: ¿qué está pasando aquí? Parece que restar fechas debería llevar a un NÚMERO (como se demuestra en la instrucción SELECT # 1), que NO PUEDE ser lanzado automáticamente al tipo de INTERVALO (como se demuestra en la instrucción SELECT # 3). Pero Oracle parece ser capaz de hacer eso de alguna manera si usa la expresión de resta FECHA en lugar de ingresar un NUMBER en bruto (instrucción SELECT # 2).

Gracias

+0

¿Esto sucede cuando pones en lugar de sysdate algo como to_date (' 20120216 ',' aaaammdd ')? –

+0

Hola, lo siento, no he visto su comentario hasta ahora: S ... Primero, para responder a su pregunta, sí lo hace incluso si utiliza 'to_date()' en lugar de SYSDATE. Sin embargo, en otra nota, logré averiguar cómo se almacena la resta DATE, así que la publiqué como mi respuesta: D ... ya que nadie más en Internet parece haber escrito sobre esto todavía, pensé que escribiría lo que descubierto. – BYS2

+0

@ BYS2 - ¿Por qué debería poner un número en() y luego tratar de restar días de un número y convertirlo a segundos: SELECCIONAR (1242.12423) DÍA (5) A SEGUNDO de la prueba? Esto es lo que debes preguntar. – Art

Respuesta

27

Bueno, normalmente no respondo a mis propias preguntas, pero después de un poco de retoque, he descubierto definitivamente cómo Oracle almacena el resultado de una resta DATE.

Al restar 2 fechas, el valor no es un tipo de datos NUMBER (como lo haría creer Oracle 11.2 SQL Reference manual). El número de tipo de datos interno de una resta DATE es 14, que es un tipo de datos interno no documentado (NUMBER es internal datatype number 2). Sin embargo, en realidad se almacena como 2 números de complemento del complemento de dos separados, con los primeros 4 bytes utilizados para representar el número de días y los últimos 4 bytes utilizados para representar el número de segundos.

Un ejemplo de una resta FECHA que resulta en una diferencia de número entero positivo:

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

Resultados en:

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 

Recordemos que el resultado se representa como una particular complemento 2 de dos firmaron 4 números de bytes . Como no hay decimales en este caso (364 días y 0 horas exactamente), los últimos 4 bytes son todos ceros y se pueden ignorar. Para los primeros 4 bytes, dado que mi CPU tiene una arquitectura little-endian, los bytes se invierten y deben leerse como 1,108 o 0x16c, que es el decimal 364.

Un ejemplo de una resta DATE que da como resultado una diferencia entera negativa :

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

resultados en:

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 

vez más, ya que estoy usando una máquina ascendente hacia la izquierda, los bytes se invierten y deben ser leídas como 255,250,97,224 que corresponde a 11111111 11111010 01100001 11011111. Ahora que esto está en el complemento de dos código numérico binario con signo, sabemos que el número es negativo porque el dígito binario más a la izquierda es 1. Para convertir esto en un número decimal, tendríamos que invertir el complemento de 2 (restar 1 luego hacer el complemento) resultando en: 00000000 00000101 10011110 00100000 que es igual a -368160 como se sospecha.

Un ejemplo de una resta FECHA resultando en una diferencia decimal:

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 

La diferencia entre esos 2 fechas es de 0,25 días o 6 horas.

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 

Ahora esta vez, ya que la diferencia es de 0 días y 6 horas, se espera que los primeros 4 bytes son 0. Durante los últimos 4 bytes, que ellos pueden invertir (porque la CPU es ascendente hacia la izquierda) y obtenga 84,96 = 01010100 01100000 base 2 = 21600 en decimal. La conversión de 21600 segundos a horas te da 6 horas, que es la diferencia que esperábamos.

Espero que esto ayude a cualquiera que se pregunte cómo se almacena realmente una resta DATE.

5

Se obtiene el error de sintaxis, porque las matemáticas fecha no devuelve un número, pero devuelve un intervalo:

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

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

es necesario convertir el número en su ejemplo en un primer intervalo usando el NUMTODSINTERVAL Function

Por ejemplo:

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> 

Basado en el molde inverso con la función NUMTODSINTERVAL(), parece que se pierde algo de redondeo en la traducción.

+0

Ah, los 5 segundos extra probablemente sean un reloj de pared ya que SYSDATE fue el punto de referencia. Me arriesgaría a adivinar que no hubo redondeo. – tawman

+0

hmm gracias por su respuesta, pero esto es extraño, porque la función dump() devolvió el tipo como '14'. Pero INTERVAL AÑO AL MES es tipo 182 y DÍA A SEGUNDO es tipo 183 (http://docs.oracle.com/cd/B10500_01/appdev.920/a96584/oci03typ.htm#421773) ... Además, si resta la fecha realmente devuelve un intervalo, ¿por qué SQL * Plus muestra '2748.9515' cuando resta las fechas? y ¿por qué el manual de referencia oficial establece que la resta de fecha da como resultado un NÚMERO? – BYS2

+0

La resta de DATE aparentemente produce un FLOAT. (¿Alguien tiene algunos documentos de Oracle realmente antiguos que indican que esto podría haber sido alguna vez?) Pruebe 'SELECT * FROM DBA_TYPES WHERE TYPE_OID = '0000000000000000000000000000E''. También eche un vistazo al paquete SYS.STANDARD, donde parece que FLOAT es efectivamente el mismo que NUMBER (es decir, 'subtipo FLOAT es NUMBER'). Comparte y Disfruta. –

1

Unos pocos puntos:

  • restar una fecha de otro resultado en un número; restar una marca de tiempo de otro resultados en un intervalo.

  • Oracle convierte las marcas de tiempo en fechas internamente al realizar la aritmética de marca de tiempo.

  • Las constantes de intervalo no se pueden utilizar en aritmética de fecha ni de indicación de fecha y hora.

Oracle 11gR2 SQL Reference Datetime Matrix

+0

Hola gracias por su entrada pero vea mi respuesta. La documentación de Oracle es realmente incorrecta/incompleta en este caso – BYS2

-3

seleccione TIMEDIFF (STR_TO_DATE ('19:15', '% h:% i% p'), STR_TO_DATE ('09:58 AM', '% h:% i% p '))

+1

Esto no intenta responder a la pregunta, y no es una sintaxis válida para Oracle; ¿Echas de menos las etiquetas y referencias de Oracle en el título y la pregunta? –

Cuestiones relacionadas