当值在夏令时边界上时按 TIMESTAMP 字段分组时出现 Oracle 奇怪行为

Oracle strange behavior when grouping by a TIMESTAMP field when the values are on the daylight saving border

我对带有 TIMESTAMP 和夏令时的 Oracle DB 有一个奇怪的行为。

以下查询在按不同列分组时产生不同的结果,原因尚不清楚。

当按 my_date_ny_ts 分组时,它 returns 两个不同的行,当按 my_date_ny_ts_tz - 结果只有一行(对我来说正确的一行)。

请注意,这些值是针对 11/04/2018 00:00:00 -05:00 和 11/04/2018 00:00:00 -06:00 转换为 New_York 时区到 11-04-2018 01:00 和 02:00 EDT 实际上是 01:00 EDT 和 01:00 EST。

我理解为什么这些值不同,但在将它们转换为不带时区数据类型的 TIMESTAMP 后它们应该相等(my_date_ny_ts 列),因为此类型不包含有关时区和夏令时状态的任何信息(请参阅 tdz_ny_ts 值)。只有在我将值转换回 TIMESTAMP WITH TIMEZONE 类型(my_date_ny_ts_tz)后,它们才变得相等。 我不需要解决方法(已经有了),只是想知道这种行为是 Oracle 错误还是误解:

select count(*), my_date_ny_ts_tz from (

SELECT
 mydate, -- timestamp with timezone
 to_char( mydate, 'TZD') as tdz, -- daylight savings flag - VALUES are NULL because timezone is an offset
 mydate AT TIME ZONE 'America/New_York' AS mydate_ny, -- timestamp with timezone in EST timezone
 to_char( mydate AT TIME ZONE 'America/New_York', 'TZD') as tdz_ny, --timestamp with timezone in EST timezone -  daylight savings flag - RETURNS EDT FOR ONE EST FOR SECOND - RIGHT
cast(mydate AT TIME ZONE 'America/New_York' as timestamp)  as my_date_ny_ts,--cast to timestamp without timezone - GROUP BY RETURNS TWO ROWS - BUG?
to_char( cast(mydate AT TIME ZONE 'America/New_York' as timestamp) , 'TZD') as tdz_ny_ts,--cast to timestamp without timezone -  daylight savings flag - both values are null so why the group by on the prev field doesn't work?
 cast(mydate AT TIME ZONE 'America/New_York' as timestamp) AT TIME ZONE 'America/New_York' as my_date_ny_ts_tz,--cast back to timestamp with timezone in EST timezone - NOW GROUP BY RETURNS ONE ROW
 to_char( cast(mydate AT TIME ZONE 'America/New_York' as timestamp) AT TIME ZONE 'America/New_York', 'TZD') as tdz_ny_ts_tz--daylight savings flag of the prev field - both are EST - RIGHT
 FROM
     (
         SELECT
         to_timestamp_tz('11/04/2018 00:00:00 -05:00','mm/dd/yyyy hh24:mi:ss TZH:TZM') AS mydate
     FROM
         dual
     UNION
     SELECT
         to_timestamp_tz('11/04/2018 00:00:00 -06:00','mm/dd/yyyy hh24:mi:ss TZH:TZM') AS mydate
     FROM
         dual
 )
 ) group by my_date_ny_ts_tz

我的版本如下,但它也发生在 12c 中:

Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
"CORE   11.2.0.4.0  Production"
TNS for 64-bit Windows: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

谢谢

可能是 Oracle 中的错误。您可以将查询缩短为:

SELECT 
    TO_CHAR(CAST(TIMESTAMP '2018-11-04 00:00:00 -05:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP), 'yyyy-mm-dd hh24:mi:ss') AS my_date_ny_ts,
    DUMP(CAST(TIMESTAMP '2018-11-04 00:00:00 -05:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP)) AS my_date_ny_ts_dump
FROM DUAL 
UNION ALL
SELECT
    TO_CHAR(CAST(TIMESTAMP '2018-11-04 00:00:00 -06:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP), 'yyyy-mm-dd hh24:mi:ss'),
    DUMP(CAST(TIMESTAMP '2018-11-04 00:00:00 -06:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP))
FROM DUAL;


+--------------------------------------------------------------------------------------+
|MY_DATE_NY_TS      |MY_DATE_NY_TS_DUMP                                                |
+--------------------------------------------------------------------------------------+
|2018-11-04 01:00:00|Typ=187 Len=20: 226,7,11,4,1,0,0,0,0,0,0,0,252,0,3,0,100,0,11,3   |
|2018-11-04 01:00:00|Typ=187 Len=20: 226,7,11,4,1,0,0,48,0,0,0,0,251,0,3,44,100,0,48,44|
+--------------------------------------------------------------------------------------+

如您所见,时间戳值是相同的,但是 DUMP() 值不同,即如果您 GROUP BY.

则得到两行

您可以 运行 它略有不同。实际上我希望得到与上面相同的结果(无论你认为正确与否)但它是不同的:

WITH t AS
    (SELECT 
        CAST(TIMESTAMP '2018-11-04 00:00:00 -05:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP) AS my_date_ny_ts
    FROM DUAL 
    UNION ALL
    SELECT
        CAST(TIMESTAMP '2018-11-04 00:00:00 -06:00' AT TIME ZONE 'America/New_York' AS TIMESTAMP)
    FROM DUAL)
SELECT TO_CHAR(my_date_ny_ts, 'yyyy-mm-dd hh24:mi:ss') AS my_date_ny_ts,
    DUMP(my_date_ny_ts) AS my_date_ny_ts_dump
FROM t;

    +--------------------------------------------------------------------------------------+
|MY_DATE_NY_TS      |MY_DATE_NY_TS_DUMP                                                |
+--------------------------------------------------------------------------------------+
|2018-11-04 01:00:00|Typ=180 Len=7: 120,118,11,4,2,1,1                                 |
|2018-11-04 01:00:00|Typ=180 Len=7: 120,118,11,4,2,1,1                                 |
+--------------------------------------------------------------------------------------+

我觉得这很奇怪。虽然我为两者都做了 CAST(... AS TIMESTAMP),一旦我得到 Typ=187 并且一旦我在 DUMP 中得到 Typ=180

看起来 SQL 类型 TIMESTAMP 180(参见 Oracle Built-In Data Types)与 PL/SQL 类型 TIMESTAMP 187(参见 PACKAGE SYS.dbms_types) - 但我不知道为什么。

对于解决方法,我建议使用函数 SYS_EXTRACT_UTC(...) 而不是 CAST(... AS TIMESTAMP)