sql >> Base de Datos >  >> RDS >> Oracle

Comparación de fecha de Oracle rota debido a DST

Para evitar este error, considere usar una conversión explícita de la expresión en la cláusula where a un tipo de marca de tiempo (marca de tiempo sin zona horaria), de esta manera:

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

Alternativamente, puede establecer explícitamente la zona horaria de la sesión, por ejemplo, '-05:00' - para el horario estándar de Nueva York (invierno),
usando ALTER SESSION time_zone = '-05:00' , o configurando la variable de entorno ORA_SDTZ en todos los entornos del cliente,
consulte este enlace para obtener detalles:http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

Pero también depende de lo que realmente se almacena en la columna de marca de tiempo en la tabla, por ejemplo, qué marca de tiempo 2014-07-01 15:00:00 representa de hecho, ¿es un "horario de invierno" o un "horario de verano"?

CURRENT_TIMESTAMP función devuelve un valor de tipo de datos TIMESTAMP CON ZONA HORARIA al comparar marcas de tiempo y fechas, Oracle convierte implícitamente los datos al tipo de datos más preciso usando la zona horaria de la sesión!
Vea este enlace --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

En nuestro caso particular, Oracle lanza timestamp columna a la timestamp with time zone type.

Oracle determina la zona horaria de una sesión a partir del entorno del cliente.
Puede determinar la zona horaria de la sesión actual mediante esta consulta:

select sessiontimezone from dual;

Por ejemplo, en mi PC (Win 7), cuando la opción ""Ajustar automáticamente el reloj para el horario de verano" está marcada, esta consulta devuelve (en SQLDeveloper):

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


Cuando desmarco esta opción en Windows y luego reinicio SQLDeveloper, aparece:

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

La zona horaria de la sesión anterior es una zona horaria con un nombre de región, para la cual Oracle usa las reglas del horario de verano para esta región en los cálculos de fecha:

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       


La última zona horaria utiliza un desplazamiento fijo "+01:00" (siempre el "Horario de invierno"), y Oracle no aplica ninguna regla de horario de verano, simplemente agrega el desplazamiento fijo.

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  

Tenga en cuenta, por curiosidad, que Y ¡los resultados de arriba representan dos momentos diferentes!
014-05-29 01:30:00 EUROPE/BELGRADE no es lo mismo que:2014-05-29 01:30:00 +01:00

pero en realidad esto:
014-05-29 01:30:00 EUROPE/BELGRADE es igual a:2014-05-29 01:30:00 +02:00

Lo anterior es solo para que sepa cómo la simple "desmarcación de casillas" podría afectar sus consultas, y dónde buscar un motivo cuando los usuarios se quejan de que "esta consulta funcionó bien en enero, pero dio resultados erróneos en julio".

Y aún sobre el tema de ORA-01878, digamos que mi sesión es EUROPE/Warsaw y mi tabla contiene esta marca de tiempo (sin zona horaria)

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

Tenga en cuenta que en mi región el cambio de horario de verano, en el año 2014, ocurre el 30 de marzo a las 2:00 a. m.
Simplemente significa que el 30 de marzo, a las 2:00 de la noche, debo despertar y cambiar mi reloj adelante de 2:00 a 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 sabe que esta marca de tiempo no es válida en mi región de acuerdo con las reglas de DST, porque no hay hora 2:30 el 30 de marzo - a las 2:00 el reloj se mueve a las 3:00, y no hay hora 2:30. Por lo tanto, Oracle arroja el error ORA-01878.

Sin embargo, esta consulta funciona perfectamente bien:

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 

Y esta es la razón de este error:su tabla contiene marcas de tiempo como 2014-03-09 2:30 más o menos (para Nueva York, donde los cambios de DST ocurren el 9 de marzo y el 2 de noviembre), y Oracle no sabe cómo convertirlos de marca de tiempo (sin TZ) a marca de tiempo con TZ.

La última pregunta:por qué la consulta con >= no funciona, pero la consulta con <= funciona bien?

Funcionan/no funcionan, porque SQLDeveloper devuelve solo las primeras 50 filas (¿quizás 100? Depende de la configuración). La consulta no lee toda la tabla, se detiene cuando se recuperan las primeras 50(100) filas.
Cambie la consulta "en funcionamiento", por ejemplo:

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

Esto obliga a la consulta a leer todas las filas de la tabla y aparecerá el error, estoy 100 % seguro.