当值在夏令时边界上时按 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)
。
我对带有 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)
。