web-dev-qa-db-fra.com

Soustraire des dates dans Oracle - Nombre ou type de données d'intervalle?

J'ai une question sur certains des fonctionnements internes pour les types de données Oracle DATE et INTERVAL. Selon le Oracle 11.2 SQL Reference , lorsque vous soustrayez 2 types de données DATE, le résultat sera un type de données NUMBER.

Sur les tests superficiels, cela semble être vrai:

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

renverra un type de données NUMBER.

Mais maintenant, si vous le faites:

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

vous obtenez un type de données INTERVAL. En d'autres termes, Oracle peut convertir le NUMBER de la soustraction DATE en un type INTERVALLE.

Alors maintenant, je me suis dit que je pouvais essayer de mettre un type de données NUMBER directement entre les crochets (au lieu de faire 'SYSDATE - start_date' qui donne de toute façon un NUMBER):

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

Mais cela se traduit par l'erreur:

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

Ma question est donc la suivante: que se passe-t-il ici? Il semble que la soustraction de dates devrait conduire à un NUMBER (comme démontré dans l'instruction SELECT # 1), qui NE PEUT PAS être automatiquement converti en type INTERVAL (comme démontré dans l'instruction SELECT # 3). Mais Oracle semble être capable de le faire d'une manière ou d'une autre si vous utilisez l'expression de soustraction DATE au lieu de mettre un NUMBER brut (instruction SELECT # 2).

Merci

21
BYS2

Ok, je ne réponds pas normalement à mes propres questions mais après un peu de bricolage, j'ai compris définitivement comment Oracle stocke le résultat d'une soustraction DATE.

Lorsque vous soustrayez 2 dates, la valeur n'est pas un type de données NUMBER (comme le voudrait le croire manuel de référence SQL Oracle 11.2 ). Le numéro de type de données interne d'une soustraction DATE est 14, qui est un type de données interne non documenté (NUMBER est numéro de type de données interne 2 ). Cependant, il est en fait stocké sous la forme de 2 numéros signés complémentaires à deux, les 4 premiers octets représentant le nombre de jours et les 4 derniers octets représentant le nombre de secondes.

Un exemple de soustraction DATE résultant en une différence entière positive:

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

Résulte 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

Rappelez-vous que le résultat est représenté par un complément à deux deux séparés, signé par des nombres de 4 octets. Puisqu'il n'y a pas de décimales dans ce cas (364 jours et 0 heures exactement), les 4 derniers octets sont tous des 0 et peuvent être ignorés. Pour les 4 premiers octets, car mon processeur a une architecture peu endienne, les octets sont inversés et doivent être lus comme 1 108 ou 0x16c, ce qui est décimal 364.

Un exemple de soustraction DATE entraînant une différence entière négative:

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

Résulte 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

Encore une fois, puisque j'utilise une machine peu endienne, les octets sont inversés et doivent être lus comme 255,250,97,224 ce qui correspond à 11111111 11111010 01100001 11011111. Maintenant, puisque c'est en codage numérique binaire signé en complément à deux, nous savons que le nombre est négatif car le chiffre binaire le plus à gauche est un 1. Pour le convertir en nombre décimal, nous devons inverser le complément à 2 (soustraire 1 puis faire le complément à un), ce qui donne: 00000000 00000101 10011110 00100000, ce qui équivaut à -368160 comme suspect.

Un exemple de soustraction DATE entraînant une différence décimale:

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 différence entre ces 2 dates est de 0,25 jour ou 6 heures.

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

Cette fois-ci, la différence étant de 0 jour et 6 heures, il est prévu que les 4 premiers octets soient 0. Pour les 4 derniers octets, nous pouvons les inverser (car le CPU est peu endian) et obtenir 84,96 = 01010100 01100000 base 2 = 21600 en décimal. La conversion de 21600 secondes en heures vous donne 6 heures, ce qui est la différence à laquelle nous nous attendions.

J'espère que cela aide toute personne qui se demandait comment une soustraction DATE est réellement stockée.

33
BYS2

Vous obtenez l'erreur de syntaxe car la date mathématique ne renvoie pas de NUMBER, mais elle renvoie un 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

Vous devez d'abord convertir le nombre de votre exemple en INTERVAL en utilisant la fonction NUMTODSINTERVAL

Par exemple:

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>

Sur la base de la conversion inverse avec la fonction NUMTODSINTERVAL (), il semble que certains arrondis soient perdus lors de la traduction.

6
tawman

Quelques points:

  • Soustraire une date d'une autre donne un nombre; la soustraction d'un horodatage d'un autre entraîne un intervalle.

  • Oracle convertit les horodatages en dates en interne lors de l'exécution de l'arithmétique d'horodatage.

  • Les constantes d'intervalle ne peuvent pas être utilisées dans l'arithmétique de date ou d'horodatage.

Oracle 11gR2 SQL Reference Datetime Matrix

1
Michael Milligan

Utilisez la fonction extract () pour récupérer les heures/minutes/secondes de la valeur d'intervalle. Voir l'exemple ci-dessous, comment obtenir des heures à partir de deux colonnes d'horodatage. J'espère que cela t'aides!

sélectionner IHB_INS_TS, MAIL_SENT_TS, extraire (heure de (IHB_INS_TS - MAIL_SENT_TS)) hourDiff de IHB_ADJSMT_WKFL_NTFCTN;

0
Sandeep Raul