具有本地时区值的Oracle时间戳透明转换

时间:2017-03-15 16:33:19

标签: oracle session timezone timestamp-with-timezone

据我所知,TIMESTAMP WITH LOCAL TIME ZONE值是透明的 转换为用户的会话时区。但我的价值观从中读取 数据库与先前插入的数据库不同。是否有数据库或会话 参数我可以调整来解决这个问题吗?

这是我的测试用例:

select systimestamp(0) from dual;

-- SYSTIMESTAMP   15/03/2017 19:01:13 +03:00

select dbtimezone from dual;

-- DBTIMEZONE     -07:00

create table test_timestamps
(
    id number generated by default on null as identity,
    systimestamp_col timestamp(0) with local time zone default on null systimestamp,
    sysdate_col timestamp(0) with local time zone default on null sysdate,
    current_timestamp_col timestamp(0) with local time zone default on null current_timestamp(0),
    date_col timestamp(0) with local time zone
);

alter session set time_zone='0:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:02:00');

select * from test_timestamps;

-- ID                                 1
-- SYSTIMESTAMP_COL                   15/03/2017 9:02:19
-- SYSDATE_COL                        15/03/2017 12:02:18
-- CURRENT_TIMESTAMP_COL              15/03/2017 9:02:19
-- DATE_COL                           15/03/2017 12:02:00

delete from test_timestamps;

alter session set time_zone='+3:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:05:00');

select * from test_timestamps;

-- ID                                 2
-- SYSTIMESTAMP_COL                   15/03/2017 12:05:43
-- SYSDATE_COL                        15/03/2017 12:05:43
-- CURRENT_TIMESTAMP_COL              15/03/2017 12:05:43
-- DATE_COL                           15/03/2017 12:05:00

我对DATE_COL值感到特别困惑。据我所知,我从DATE_COL读取的值应该与我插入的值相同,无论当前会话的TIME_ZONE是什么(只要它在我的插入和选择之间没有改变)。 / p>

我也对SYSTIMESTAMP默认值感到困惑。

无论当前用户会话的时区如何,

SELECT SYSTIMESTAMP FROM DUAL始终以“+3:00”时区返回服务器的时间戳。但是如果我使用SYSTIMESTAMP作为列的默认值,它就会被翻译。

我希望看到的是:

-- for a user in my time zone
alter session set time_zone='+3:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:15:00');

select id, systimestamp_col, date_col from test_timestamps;

-- ID                                 3
-- SYSTIMESTAMP_COL                   15/03/2017 19:15:00
-- DATE_COL                           15/03/2017 19:15:00

-- same data from a GMT user's point of view
alter session set time_zone='+0:00';

select id, systimestamp_col, date_col from test_timestamps;

-- ID                                 3
-- SYSTIMESTAMP_COL                   15/03/2017 16:15:00
-- DATE_COL                           15/03/2017 16:15:00

这可能还是我错过了什么?

UPD。这是我的LiveSQL script。它看起来应该像我描述的那样工作,所以我想我的数据库设置可能有问题。

2 个答案:

答案 0 :(得分:3)

TIMESTAMP WITH LOCAL TIME ZONE的工作方式如下:当您需要在应用程序中处理时区时,常见的方法是

  

在内部存储所有时间作为UTC,并在应用程序级别将它们转换为当前用户本地时区。

这正是TIMESTAMP WITH LOCAL TIME ZONE的工作方式 - 唯一的区别是

  

在内部存储所有时间 DBTIMEZONE ,并在应用程序级别将其转换为当前用户本地时区。

因此,如果数据库包含一个包含DBTIMEZONE列且该列包含数据的表,则无法再更改数据库中的ALTER DATABASE SET TIME_ZONE='...';TIMESTAMP WITH LOCAL TIME ZONE)。

SYSTIMESTAMP在数据库服务器的操作系统的时区中被撤消。 DBTIMEZONE <{1}}或SYSTIMESTAMP的时区。

SYSDATE定义DBTIMEZONE数据类型列的内部存储格式。忘记这一点,我无法想象你需要它的任何用例。

实际上你的表等同于这个选择:

TIMESTAMP WITH LOCAL TIME ZONE

当您进行select CAST(systimestamp AS timestamp(0) with local time zone) as SYSTIMESTAMP_COL, CAST(sysdate AS timestamp(0) with local time zone) as SYSDATE_COL, CAST(current_timestamp AS timestamp(0) with local time zone) as CURRENT_TIMESTAMP_COL, CAST(timestamp '2017-03-15 19:02:00' AS timestamp(0) with local time zone) as DATE_COL from dual; 时,您尝试将没有任何时区信息的日期/时间值转换为带时区的日期/时间值。原则上这是不可能的,因为Oracle缺少时区信息,因此Oracle假定为时区。如果您进行此类转换,则Oracle始终会考虑CAST({time without time zone} with local time zone)中给出的{time without time zone}(转换时刻)。

所以SESSIONTIMEZONE等同于

CAST(sysdate AS timestamp(0) with local time zone)

RESP。 CAST(FROM_TZ(TO_TIMESTAMP(SYSDATE), SESSIONTIMEZONE) AS TIMESTAMP(0) WITH LOCAL TIME ZONE)` 表示

CAST(timestamp '2017-03-15 19:02:00' AS timestamp(0) with local time zone)

对于CAST(FROM_TZ(TIMESTAMP '2017-03-15 19:02:00', SESSIONTIMEZONE) AS TIMESTAMP(0) WITH LOCAL TIME ZONE) ,这实际上是错误的,因为SYSDATE是在数据库服务器操作系统的时区中而不是在SESSIONTIMEZONE中给出的。对于第二个,它取决于你的意图结果是否正确。

SYSDATE返回值SYSTIMESTAMP,它始终与您当前的TIMESTAMP WITH TIME ZONE无关。但是,如果转换为SESSIONTIMEZONE,它当然会转换为您当前的本地时区。您也可以使用TIMESTAMP WITH LOCAL TIME ZONECURRENT_TIMESTAMP或多或少相同的内容。

此代码

SYSTIMESTAMP AT LOCAL

似乎是错的。结果应该是

select systimestamp(0) from dual;

-- SYSTIMESTAMP   15/03/2017 19:01:13 +03:00

alter session set time_zone='0:00';

insert into test_timestamps(date_col)
values (timestamp '2017-03-15 19:02:00');

select * from test_timestamps;

-- ID                                 1
-- SYSTIMESTAMP_COL                   15/03/2017 9:02:19
-- SYSDATE_COL                        15/03/2017 12:02:18
-- CURRENT_TIMESTAMP_COL              15/03/2017 9:02:19
-- DATE_COL                           15/03/2017 12:02:00

差异看起来应该是,但绝对值似乎是“伪造的”(或者数据库存在真正的问题)。

答案 1 :(得分:0)

我们的DBA已找到此行为的原因。 我们使用多租户容器数据库(Oracle 12c CDB)。 当根数据库的DBTIMEZONE与可插拔数据库(PDB)DBTIMEZONE不同时,会出现此问题。

在我们的案例中,我们有:

  • root DBTIMEZONE - &#34; 0:00&#34;
  • PDB DBTIMEZONE - &#34; -7:00&#34;

一旦DBA将所有DBTIMEZONE设置为相同的值,问题就消失了。 据我了解,他将根DBTIMEZONE更改为&#34; -7:00&#34;。 现在,我的测试用例与livesql.oracle.com沙箱上的测试用例完全相同,所选时间戳与插入的时间戳相同。