Oracle
 sql >> Base de données >  >> RDS >> Oracle

Comparaison de dates Oracle interrompue à cause de l'heure d'été

Pour éviter cette erreur, envisagez d'utiliser une conversion explicite de l'expression dans la clause where en un type d'horodatage (horodatage sans fuseau horaire), de cette manière :

select * 
from MY_TABLE T
where T.MY_TIMESTAMP >= cast(CURRENT_TIMESTAMP - interval '1' hour As timestamp );

Vous pouvez également définir explicitement le fuseau horaire de la session, par exemple '-05:00' - pour l'heure standard (d'hiver) de New York,
en utilisant ALTER SESSION time_zone = '-05:00' , ou en définissant la variable d'environnement ORA_SDTZ dans tous les environnements du client,
consultez ce lien pour plus de détails :http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

Mais cela dépend aussi de ce qui est vraiment est stocké dans la colonne d'horodatage de la table, par exemple quel horodatage 2014-07-01 15:00:00 représente en fait, est-ce une "heure d'hiver" ou une "heure d'été" ?

CURRENT_TIMESTAMP renvoie une valeur de type de données TIMESTAMP WITH TIME ZONE
voir ce lien :http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions037.htm

Tant en comparant les horodatages et les dates, Oracle convertit implicitement les données dans le type de données le plus précis en utilisant le fuseau horaire de la session !
Voir ce lien --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

Dans notre cas particulier, Oracle jette timestamp colonne vers timestamp with time zone type.

Oracle détermine un fuseau horaire de session à partir de l'environnement client.
Vous pouvez déterminer le fuseau horaire de la session actuelle à l'aide de cette requête :

select sessiontimezone from dual;

Par exemple sur mon PC (Win 7), lorsque l'option ""Ajuster automatiquement l'horloge à l'heure d'été" est cochée, cette requête renvoie (sous SQLDeveloper) :

SESSIONTIMEZONE                                                           
---------------
Europe/Belgrade 


Lorsque je décoche cette option dans Windows, puis que je redémarre SQLDeveloper, cela donne :

SESSIONTIMEZONE                                                           
---------------
+01:00     

L'ancien fuseau horaire de session est un fuseau horaire avec un nom de région, pour lequel Oracle utilise les règles d'heure d'été pour cette région dans les calculs de date :

alter session set time_zone = 'Europe/Belgrade';
select cast( timestamp '2014-01-29 01:30:00' as timestamp with time zone ) As x,
       cast( timestamp '2014-05-29 01:30:00' as timestamp with time zone ) As y
from dual;

session SET altered.
X                            Y                          
---------------------------- ----------------------------
2014-01-29 01:30:00 EUROPE/B 2014-05-29 01:30:00 EUROPE/B 
ELGRADE                      ELGRADE       


Ce dernier fuseau horaire utilise un décalage fixe "+01:00" (toujours "l'heure d'hiver"), et Oracle n'applique aucune règle d'heure d'été pour lui, il ajoute simplement le décalage fixe.

alter session set time_zone = '+01:00';
select cast( timestamp '2014-01-29 01:30:00' as timestamp with time zone ) As x,
       cast( timestamp '2014-05-29 01:30:00' as timestamp with time zone ) As y
from dual;

session SET altered.
X                            Y                          
---------------------------- ----------------------------
2014-01-29 01:30:00 +01:00   2014-05-29 01:30:00 +01:00  

Veuillez noter, par curiosité, que Y les résultats ci-dessus représentent deux heures différentes !!!
014-05-29 01:30:00 EUROPE/BELGRADE n'est pas le même que :2014-05-29 01:30:00 +01:00

mais en fait ceci :
014-05-29 01:30:00 EUROPE/BELGRADE est égal à :2014-05-29 01:30:00 +02:00

Ce qui précède est uniquement destiné à vous faire prendre conscience de la façon dont la simple "décocher une case" peut affecter vos requêtes, et où creuser pour une raison lorsque les utilisateurs se plaignent "cette requête a bien fonctionné en janvier, mais a donné mauvais résultats en juillet".

Et toujours sur le sujet ORA-01878 - disons que ma session est EUROPE/Warsaw et ma table contient cet horodatage (sans fuseau horaire)

'TIMESTAMP'2014-03-30 2:30:00'

Notez que dans ma région, le changement d'heure d'été, en 2014, a lieu le 30 mars à 2h00.
Cela signifie simplement que le 30 mars, à 2h00 du soir, je dois me réveiller et changer ma montre avance de 2h00 à 3h00;)

alter session set time_zone = 'Europe/Warsaw';
select cast( TIMESTAMP'2014-03-30 2:30:00' as timestamp with time zone ) As x
from dual;

SQL Error: ORA-01878: podane pole nie zostało znalezione w dacie-godzinie ani w interwale
01878. 00000 -  "specified field not found in datetime or interval"
*Cause:    The specified field was not found in the datetime or interval.
*Action:   Make sure that the specified field is in the datetime or interval.

Oracle sait que cet horodatage n'est pas valide dans ma région selon les règles de l'heure d'été, car il n'y a pas d'heure 2h30 le 30 mars - à 2h00 l'horloge est déplacée à 3h00, et il n'y a pas d'heure 2h30. Par conséquent, Oracle renvoie l'erreur ORA-01878.

Cependant, cette requête fonctionne parfaitement :

alter session set time_zone = '+01:00';
select cast( TIMESTAMP'2014-03-30 2:30:00' as timestamp with time zone ) As x
from dual;

session SET altered.
X                          
----------------------------
2014-03-30 02:30:00 +01:00 

Et c'est une raison de cette erreur - votre table contient des horodatages comme 2014-03-09 2:30 environ (pour New York, où les changements d'heure d'été se produisent le 9 mars et le 2 novembre), et Oracle ne sait pas comment les convertir de l'horodatage (sans TZ) à l'horodatage avec TZ.

La dernière question - pourquoi la requête avec >= ne fonctionne pas, mais la requête avec <= fonctionne bien ?

Ils fonctionnent/ne fonctionnent pas, car SQLDeveloper ne renvoie que les 50 premières lignes (peut-être 100 ? Cela dépend des paramètres). La requête ne lit pas toute la table, elle s'arrête lorsque les 50 (100) premières lignes sont récupérées.
Changez la requête "de travail" en, par exemple :

select sum( EXTRACT(HOUR from MY_TIMESTAMP) ) from MY_TABLE 
where MY_TIMESTAMP <= (CURRENT_TIMESTAMP - interval '1' hour );

Cela force la requête à lire toutes les lignes de la table, et l'erreur apparaîtra, j'en suis sûr à 100 %.