sql >> Databasteknik >  >> RDS >> Oracle

Oracles datumjämförelse har gått sönder på grund av sommartid

För att undvika detta fel, överväg att använda en explicit cast av uttrycket i where-satsen till en tidsstämpeltyp (tidsstämpel utan tidszon), på detta sätt:

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

Alternativt kan du uttryckligen ställa in sessionens tidszon till, till exempel '-05:00' - för New York standard (vinter) tid,
med ALTER SESSION time_zone = '-05:00' , eller genom att ställa in miljövariabeln ORA_SDTZ i alla klientmiljöer,
se denna länk för detaljer:http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

Men det beror också på vad egentligen lagras i tidsstämpelkolumnen i tabellen, till exempel vad en tidsstämpel 2014-07-01 15:00:00 representerar i själva verket, är det en "vintertid" eller en "sommartid"?

CURRENT_TIMESTAMP funktion returnerar ett värde av datatyp TIMESTAMP WITH TIME ZONE
se denna länk:http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions037.htm

Medan Genom att jämföra tidsstämplar och datum konverterar Oracle implicit data till den mer exakta datatypen med sessionens tidszon !
Se den här länken --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

I vårt speciella fall castar Oracle timestamp kolumnen till timestamp with time zone typ.

Oracle bestämmer en sessionstidszon från klientmiljön.
Du kan bestämma aktuell sessionstidszon med den här frågan:

select sessiontimezone from dual;

Till exempel på min PC (Win 7), när alternativet ""Justera klockan automatiskt för sommartid" är markerat, returnerar denna fråga (under SQLDeveloper):

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


När jag avmarkerar det här alternativet i Windows och sedan startar om SQLDeveloper, ger det:

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

Den tidigare sessionens tidszon är en tidszon med ett regionnamn, för vilken Oracle använder reglerna för sommartid för denna region i datumberäkningar:

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       


Den senare tidszonen använder en fast offset "+01:00" (alltid "vintertid"), och Oracle tillämpar inga sommartidsregler för den, det lägger helt enkelt till den fasta offseten.

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  

Vänligen notera, för nyfikenhetens skull, att Y resultaten i ovanstående representerar två olika tidpunkter !!!
014-05-29 01:30:00 EUROPE/BELGRADE är inte detsamma som:2014-05-29 01:30:00 +01:00

men faktiskt detta:
014-05-29 01:30:00 EUROPE/BELGRADE är lika med:2014-05-29 01:30:00 +02:00

Ovanstående är bara för att göra dig medveten om hur enkel "avmarkering av ruta" kan påverka dina frågor, och var man kan gräva av en anledning när användare klagar "den här frågan fungerade bra i januari, men gav fel resultat i juli".

Och fortfarande på ämnet ORA-01878 - låt säga att min session är EUROPE/Warsaw och min tabell innehåller denna tidsstämpel (utan tidszon)

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

Observera att i min region inträffar ändringen sommartid, år 2014, den 30 mars kl. 02.00.
Det betyder helt enkelt att den 30 mars, kl. 02.00 på natten, måste jag vakna och flytta klockan. framåt från 2:00 till 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 vet att denna tidsstämpel inte är giltig i min region enligt sommarreglerna, eftersom det inte finns någon tid 2:30 den 30 mars - klockan 2:00 flyttas klockan till 3:00, och det finns ingen tid 2:30. Därför skickar Oracle felet ORA-01878.

Men den här frågan fungerar perfekt:

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 

Och detta är orsaken till detta fel - din tabell innehåller tidsstämplar som 2014-03-09 2:30 eller så (för New York, där sommarväxlingar sker den 9 mars och 2 november), och Oracle vet inte hur man konverterar dem från tidsstämpel (utan TZ) till tidsstämpel med TZ.

Den sista frågan - varför frågan med >= fungerar inte, men frågan med <= fungerar bra ?

De fungerar/fungerar inte, eftersom SQLDeveloper endast returnerar de första 50 raderna (kanske 100? Det beror på inställningarna). Frågan läser inte hela tabellen, den stannar när de första 50(100) raderna hämtas.
Ändra "fungerande" frågan till till exempel:

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

Detta tvingar frågan att läsa alla rader i tabellen, och felet kommer att visas, jag är 100 % säker.



  1. Förstå Lob-segment (SYS_LOB) i Oracle?

  2. Bygg ett nyhetsbrevssystem med PHP och MySQL

  3. Enkelt citat, dubbelt citat och backticks i MySQL-frågor

  4. Hur du skyddar din MySQL- och MariaDB-databas mot cyberattacker när du är på ett offentligt nätverk