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

yal*_*lie 4 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
Run Code Online (Sandbox Code Playgroud)

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

我也对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
Run Code Online (Sandbox Code Playgroud)

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

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

Wer*_*eit 5

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不是的时区SYSTIMESTAMPSYSDATE

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;
Run Code Online (Sandbox Code Playgroud)

当您制作时,CAST({time without time zone} with local time zone)您尝试将没有任何时区信息的日期/时间值转换为带时区的日期/时间值。原则上这是不可能的,因为 Oracle 缺少时区信息,所以 Oracle 假定一个时区。如果您进行此类转换,则 Oracle 始终将 {time without time zone} 视为SESSIONTIMEZONE(在转换时)。

所以CAST(sysdate AS timestamp(0) with local time zone)相当于

CAST(FROM_TZ(TO_TIMESTAMP(SYSDATE), SESSIONTIMEZONE) AS TIMESTAMP(0) WITH LOCAL TIME ZONE)` 
Run Code Online (Sandbox Code Playgroud)

分别 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)
Run Code Online (Sandbox Code Playgroud)

因为SYSDATE这实际上是错误的,因为它SYSDATE是在数据库服务器操作系统的时区中给出的,而不是在 SESSIONTIMEZONE 中。对于第二个,结果是否正确取决于您的意图。

SYSTIMESTAMP返回值TIMESTAMP WITH TIME ZONE,它始终独立于您当前的SESSIONTIMEZONE. 但是,如果您转换为TIMESTAMP WITH LOCAL TIME ZONE它,它当然会转换为您当前的本地时区。您也可以使用CURRENT_TIMESTAMPor SYSTIMESTAMP AT LOCALwhich 或多或少相同。

这段代码

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
Run Code Online (Sandbox Code Playgroud)

似乎是错误的。结果应该是

-- SYSTIMESTAMP_COL                   15/03/2017 16:01:14
-- SYSDATE_COL                        15/03/2017 19:01:14
-- CURRENT_TIMESTAMP_COL              15/03/2017 16:01:14
-- DATE_COL                           15/03/2017 19:02:00
Run Code Online (Sandbox Code Playgroud)

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


yal*_*lie 4

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

\n\n

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

\n\n
    \n
  • DBTIMEZONE\xe2\x80\x94“0:00”
  • \n
  • PDB DBTIMEZONE\xe2\x80\x94“-7:00”
  • \n
\n\n

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

\n