Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Comparação de data do Oracle quebrada por causa do DST


Para evitar esse erro, considere usar uma conversão explícita da expressão na cláusula where para um tipo timestamp (timestamp sem fuso horário), desta forma:
select * 
from MY_TABLE T
where T.MY_TIMESTAMP >= cast(CURRENT_TIMESTAMP - interval '1' hour As timestamp );

Como alternativa, você pode definir explicitamente o fuso horário da sessão para, por exemplo, '-05:00' - para o horário padrão de Nova York (inverno),
usando ALTER SESSION time_zone = '-05:00' , ou definindo a variável de ambiente ORA_SDTZ em todos os ambientes do cliente,
veja este link para detalhes:http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

Mas também depende do que realmente é armazenado na coluna timestamp na tabela, por exemplo, o timestamp 2014-07-01 15:00:00 representa de fato, é um "horário de inverno" ou um "horário de verão"?

CURRENT_TIMESTAMP função retorna um valor do tipo de dados TIMESTAMP WITH TIME ZONE
veja este link:http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions037.htm

While comparando carimbos de data/hora e datas, o Oracle converte implicitamente os dados para o tipo de dados mais preciso usando o fuso horário da sessão!
Veja este link --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

Em nosso caso particular, o Oracle converte timestamp coluna para o timestamp with time zone type.

O Oracle determina o fuso horário da sessão a partir do ambiente do cliente.
Você pode determinar o fuso horário da sessão atual usando esta consulta:
select sessiontimezone from dual;

Por exemplo, no meu PC (Win 7), quando a opção ""Ajustar automaticamente o relógio para o horário de verão" está marcada, esta consulta retorna (em SQLDeveloper):
SESSIONTIMEZONE                                                           
---------------
Europe/Belgrade 


Quando desmarco esta opção no Windows e reinicio o SQLDeveloper, dá:
SESSIONTIMEZONE                                                           
---------------
+01:00     

O antigo fuso horário da sessão é um fuso horário com um nome de região, para o qual a Oracle usa as regras de horário de verão para esta região nos cálculos de data:
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       


O último fuso horário usa um deslocamento fixo "+01:00" (sempre o "Horário de inverno"), e o Oracle não aplica nenhuma regra de horário de verão para ele, simplesmente adiciona o deslocamento fixo.
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  

Observe, por curiosidade, que Y os resultados acima representam dois horários diferentes !!!
014-05-29 01:30:00 EUROPE/BELGRADE não é o mesmo que:2014-05-29 01:30:00 +01:00

mas na verdade isso:
014-05-29 01:30:00 EUROPE/BELGRADE é igual a:2014-05-29 01:30:00 +02:00

O acima é apenas para que você saiba como a simples "desmarcação da caixa" pode afetar suas consultas e onde procurar um motivo quando os usuários reclamarem "esta consulta funcionou bem em janeiro, mas deu resultados errados em julho".

E ainda no tópico ORA-01878 - digamos que minha sessão seja EUROPE/Warsaw e minha tabela contém este timestamp (sem fuso horário)

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

Observe que na minha região a mudança do horário de verão, no ano de 2014, ocorre no dia 30 de março às 2h.
Significa simplesmente que no dia 30 de março, às 2h da noite, devo acordar e mudar meu relógio em frente das 14:00 às 15: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.

A Oracle sabe que este carimbo de data/hora não é válido na minha região de acordo com as regras do horário de verão, porque não há hora 2:30 em 30 de março - às 2:00 o relógio é movido para 3:00 e não há hora 2:30. Portanto, o Oracle lança o erro ORA-01878.

No entanto, esta consulta funciona perfeitamente:
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 

E esta é a razão deste erro - sua tabela contém carimbos de data/hora como 2014-03-09 2:30 ou assim (para Nova York, onde as mudanças de horário de verão ocorrem em 9 de março e 2 de novembro), e a Oracle não sabe como convertê-los de timestamp (sem TZ) para timestamp com TZ.

A última pergunta - por que a consulta com >= não funciona, mas a consulta com <= funciona bem ?

Eles funcionam/não funcionam, porque SQLDeveloper retorna apenas as primeiras 50 linhas (talvez 100 ? Depende das configurações). A consulta não lê a tabela inteira, ela para quando as primeiras 50 (100) linhas são buscadas.
Altere a consulta "funcionando" para, por exemplo:
select sum( EXTRACT(HOUR from MY_TIMESTAMP) ) from MY_TABLE 
where MY_TIMESTAMP <= (CURRENT_TIMESTAMP - interval '1' hour );

Isso força a consulta a ler todas as linhas da tabela, e o erro aparecerá, tenho 100% de certeza.