Oracle:本地时区,区分双时2

时间:2018-12-18 21:52:25

标签: sql oracle datetime

我有一个Oracle表(MYTAB),其中有一个用于日期时间(MYDT)和一个值(MYVAL)的列。

MYDT         NOT NULL TIMESTAMP(0) WITH LOCAL TIME ZONE
MYVAL        NOT NULL NUMBER

让我们看看时区CEST到CET的切换。十月的每个最后一个星期日都有一个本地时间的双小时2-第一个小时2在CEST中,第二个小时在CET中。

我的桌子上每小时都有一个条目。 假设第一个小时2的MYVAL为100,第二个小时2的MYVAL为200。

我可以在SELECT中正确寻址该值。

获得第一个小时2:

SQL> SELECT TO_TIMESTAMP_TZ(MYDT, 'DD.MM.YYYY HH24:MI TZH'), MYDT, MYVAL    
     FROM MYTAB 
     WHERE MYDT = TO_TIMESTAMP_TZ ('28.10.2018 00:00 00', 'DD.MM.YYYY HH24:MI TZH');

28.10.18 02:00:00 +00:00
28.10.18 02:00:00
111

获得第二个小时2:

SQL> SELECT  TO_TIMESTAMP_TZ(MYDT, 'DD.MM.YYYY HH24:MI TZH'), MYDT, MYVAL 
     FROM MYTAB 
     WHERE MYDT = TO_TIMESTAMP_TZ ('28.10.2018 01:00 00', 'DD.MM.YYYY HH24:MI TZH');

28.10.18 02:00:00 +00:00
28.10.18 02:00:00
222

所以我得到有关时间戳的正确值。

但是在两种情况下,时间戳都是相等的-认为这是因为它存储在本地时间。

如何获得唯一的时间戳?应该有可能,因为信息必须存在,否则SELECT不会为我提供有关WHERE中时区的正确值。

谢谢!

3 个答案:

答案 0 :(得分:2)

首先,“我可以在SELECT中正确寻址值”不是很正确。这些:

TO_TIMESTAMP_TZ(MYDT, 'DD.MM.YYYY HH24:MI TZH')
TO_TIMESTAMP_TZ ('28.10.2018 00:00 00', 'DD.MM.YYYY HH24:MI TZH');

没有按照您的想法做。在第一个脚本中,您将使用会话的mydt设置将NLS_TIMESTAMP_FORMAT值隐式转换为字符串,这将为您提供类似于“ 28.10.18 02:00:00”的信息;然后在它们两个中都使用提供的格式掩码转换字符串。但是,时区小时偏移量是由字符串中的 seconds 值提供的。

在两个示例中,这都意味着TZH设置为零,这在输出中可以看到。如果您的原始值为'28 .10.18 02:00:03',那么它将变为'2018-10-28 02:00:00 +03:00',这根本不是您想要的。并且,如果任何值的秒数都超过14,则它将失败,并显示“ ORA-01874:时区小时必须在-12到14之间”。

因此您的查询正在查找所需的行,但这几乎是偶然的。

一些示例数据和会话设置与我认为的相符:

create table mytab(
  mydt timestamp(0) with local time zone not null,
  myval number not null
);

insert into mytab (mydt, myval) values (timestamp '2018-10-28 01:59:59 CET CEST', 1);
insert into mytab (mydt, myval) values (timestamp '2018-10-28 02:00:00 CET CEST', 2);
insert into mytab (mydt, myval) values (timestamp '2018-10-28 02:00:00 CET CET', 3);
insert into mytab (mydt, myval) values (timestamp '2018-10-28 02:00:01 CET CET', 4);
insert into mytab (mydt, myval) values (timestamp '2018-10-28 02:00:02 CET CET', 5);
insert into mytab (mydt, myval) values (timestamp '2018-10-28 02:00:03 CET CET', 6);

alter session set time_zone = 'Europe/Berlin';
alter session set nls_timestamp_format = 'DD.MM.YYYY HH24:MI:SS';
alter session set nls_timestamp_tz_format = 'DD.MM.YYYY HH24:MI:SS TZH:TZM';

然后您可以更清楚地看到问题:

select mydt, to_timestamp_tz(mydt, 'DD.MM.YYYY HH24:MI TZH')
from mytab;

Error report -
ORA-01874: time zone hour must be between -12 and 14

select mydt, to_timestamp_tz(mydt, 'DD.MM.YYYY HH24:MI TZH')
from mytab
where myval > 1;

MYDT                TO_TIMESTAMP_TZ(MYDT,'DD.M
------------------- --------------------------
28.10.2018 02:00:00 28.10.2018 02:00:00 +00:00
28.10.2018 02:00:00 28.10.2018 02:00:00 +00:00
28.10.2018 02:00:01 28.10.2018 02:00:00 +01:00
28.10.2018 02:00:02 28.10.2018 02:00:00 +02:00
28.10.2018 02:00:03 28.10.2018 02:00:00 +03:00

您可能一直期望像to_char(mydt, 'DD.MM.YYYY HH24:MI TZH')之类的东西,但是得到的是“ ORA-01821:日期格式无法识别”。但是,您可以改为查看区域和夏令时标志:

select mydt, to_char(mydt, 'DD.MM.YYYY HH24:MI TZR TZD')
from mytab;

MYDT                TO_CHAR(MYDT,'DD.MM.YYYYHH24:MITZRTZD')                 
------------------- --------------------------------------------------------
28.10.2018 01:59:59 28.10.2018 01:59 EUROPE/BERLIN CEST                     
28.10.2018 02:00:00 28.10.2018 02:00 EUROPE/BERLIN CEST                     
28.10.2018 02:00:00 28.10.2018 02:00 EUROPE/BERLIN CET                      
28.10.2018 02:00:01 28.10.2018 02:00 EUROPE/BERLIN CET                      
28.10.2018 02:00:02 28.10.2018 02:00 EUROPE/BERLIN CET                      
28.10.2018 02:00:03 28.10.2018 02:00 EUROPE/BERLIN CET                      

但是,它仍将使用会话时区显示字符串。

类似地,您的过滤器可能类似于:

select myval,
  mydt,
  sys_extract_utc(mydt) as mydt_utc,
  to_char(mydt, 'DD.MM.YYYY HH24:MI TZR TZD') as mydt_full
from mytab
where mydt = to_timestamp_tz('28.10.2018 02:00:00 CET CEST', 'DD.MM.YYYY HH24:MI:SS TZR TZD');

     MYVAL MYDT                MYDT_UTC            MYDT_FULL                          
---------- ------------------- ------------------- -----------------------------------
         2 28.10.2018 02:00:00 28.10.2018 00:00:00 28.10.2018 02:00 EUROPE/BERLIN CEST

select myval,
  mydt,
  sys_extract_utc(mydt) as mydt_utc,
  to_char(mydt, 'DD.MM.YYYY HH24:MI TZR TZD') as mydt_full
from mytab
where mydt = to_timestamp_tz('28.10.2018 02:00:00 CET CET', 'DD.MM.YYYY HH24:MI:SS TZR TZD');

     MYVAL MYDT                MYDT_UTC            MYDT_FULL                          
---------- ------------------- ------------------- -----------------------------------
         3 28.10.2018 02:00:00 28.10.2018 01:00:00 28.10.2018 02:00 EUROPE/BERLIN CET 

我已经将sys_extract_utc()输出作为cast()的替代。您也可以使用mydt at time zone 'UTC',而无需强制转换,这会为您提供带有时区值的时间戳。

同样,更改会话时区将更改mydt_full值的显示方式,但该过滤器仍将起作用,因为在那里已明确提供了正确的时区。并且根据过滤器值的来源,您可以使用timestamp literal,就像我在插入语句中所做的那样。

Read more about how the timestamp with local time zone data type.

答案 1 :(得分:2)

时间戳值未存储在“ localtime”中,而是存储在DBTIMEZONE中。但是,这更多是Oracle内部的话题,并不真正相关。

您也可以使用cast(MYDT at time zone 'UTC' as date)代替SYS_EXTRACT_UTC(MYDT)

为了使其更加清晰,请尝试

SELECT 
    SYS_EXTRACT_UTC(MYDT), 
    TO_CHAR(MYDT, 'dd.mm.yyyy hh24:mi:ss tzd'),
    MYDT, MYVAL 
FROM MYTAB;

然后您应该会看到整个图片。

答案 2 :(得分:0)

我发现了

SELECT cast(MYDT at time zone 'UTC' as date) FROM ...

符合我的要求。现在一切都在UTC中,我可以区分。