Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Oracle-Datumsvergleich wegen Sommerzeit unterbrochen

Um diesen Fehler zu vermeiden, erwägen Sie die Verwendung einer expliziten Umwandlung des Ausdrucks in der where-Klausel in einen Zeitstempeltyp (Zeitstempel ohne Zeitzone) auf diese Weise:

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

Alternativ können Sie die Sitzungszeitzone explizit auf beispielsweise '-05:00' setzen - für die New Yorker Standardzeit (Winterzeit),
mit ALTER SESSION time_zone = '-05:00' , oder indem Sie die Umgebungsvariable ORA_SDTZ in allen Client-Umgebungen setzen,
siehe diesen Link für Details:http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

Aber es kommt auch darauf an, was wirklich wird in der Zeitstempelspalte in der Tabelle gespeichert, was zum Beispiel ein Zeitstempel 2014-07-01 15:00:00 ist stellt tatsächlich dar, ist es eine "Winterzeit" oder eine "Sommerzeit" ?

CURRENT_TIMESTAMP Die Funktion gibt einen Wert vom Datentyp TIMESTAMP WITH TIME ZONE zurück
siehe diesen Link:http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions037.htm

While Beim Vergleich von Zeitstempeln und Daten konvertiert Oracle die Daten implizit in den genaueren Datentyp unter Verwendung der Sitzungszeitzone !
Siehe diesen Link --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

In unserem speziellen Fall wirft Oracle timestamp Spalte zum timestamp with time zone type.

Oracle ermittelt eine Sitzungszeitzone aus der Clientumgebung.
Sie können die aktuelle Sitzungszeitzone mit dieser Abfrage ermitteln:

select sessiontimezone from dual;

Wenn zum Beispiel auf meinem PC (Win 7) die Option „Uhr automatisch an die Sommerzeit anpassen“ aktiviert ist, gibt diese Abfrage (unter SQLDeveloper) Folgendes zurück:

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


Wenn ich diese Option in Windows deaktiviere und dann SQLDeveloper neu starte, gibt es:

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

Die frühere Sitzungszeitzone ist eine Zeitzone mit einem Regionsnamen, für die Oracle die Sommerzeitregeln für diese Region in Datumsberechnungen verwendet:

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       


Die letztere Zeitzone verwendet einen festen Offset "+01:00" (immer die "Winterzeit"), und Oracle wendet keine DST-Regeln darauf an, es fügt einfach den festen Offset hinzu.

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  

Bitte beachten Sie aus Neugier, dass Y Die obigen Ergebnisse stellen zwei verschiedene Zeiten dar !!!
014-05-29 01:30:00 EUROPE/BELGRADE ist nicht dasselbe wie:2014-05-29 01:30:00 +01:00

aber eigentlich das:
014-05-29 01:30:00 EUROPE/BELGRADE ist gleich:2014-05-29 01:30:00 +02:00

Das Obige soll Sie nur darauf aufmerksam machen, wie sich das einfache "Deaktivieren von Kästchen" auf Ihre Abfragen auswirken kann und wo Sie nach einem Grund suchen können, wenn sich Benutzer beschweren:"Diese Abfrage hat im Januar gut funktioniert, aber nicht funktioniert falsche Ergebnisse im Juli".

Und noch zum Thema ORA-01878 - sagen wir, meine Session ist EUROPE/Warsaw und meine Tabelle enthält diesen Zeitstempel (ohne Zeitzone)

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

Beachten Sie, dass in meiner Region die Sommerzeitumstellung im Jahr 2014 am 30. März um 2:00 Uhr stattfindet.
Das bedeutet einfach, dass ich am 30. März um 2:00 Uhr nachts aufwachen und meine Uhr umstellen muss vorwärts von 2:00 bis 3:00;)

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 weiß, dass dieser Zeitstempel in meiner Region nicht gültig ist gemäß den DST-Regeln, weil es am 30. März keine Zeit 2:30 gibt - um 2:00 wird die Uhr auf 3:00 verschoben, und es gibt keine Zeit 2:30. Daher wirft Oracle den Fehler ORA-01878.

Diese Abfrage funktioniert jedoch einwandfrei:

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 

Und das ist ein Grund für diesen Fehler – Ihre Tabelle enthält Zeitstempel wie 2014-03-09 2:30 oder so (für New York, wo Sommerzeitverschiebungen am 9. März und 2. November auftreten), und Oracle weiß nicht, wie man sie von Zeitstempel (ohne TZ) in Zeitstempel mit TZ umwandelt.

Die letzte Frage - warum die Abfrage mit >= funktioniert nicht, aber die Abfrage mit <= funktioniert gut?

Sie funktionieren/funktionieren nicht, weil SQLDeveloper nur die ersten 50 Zeilen zurückgibt (vielleicht 100 ? Es hängt von den Einstellungen ab). Die Abfrage liest nicht die gesamte Tabelle, sie stoppt, wenn die ersten 50(100) Zeilen abgerufen wurden.
Ändern Sie die "funktionierende" Abfrage beispielsweise in:

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

Dadurch wird die Abfrage gezwungen, alle Zeilen in der Tabelle zu lesen, und der Fehler wird angezeigt, da bin ich mir zu 100 % sicher.