时区结果因systimestamp而异

时间:2017-06-14 14:25:13

标签: java oracle spring-jdbc

我有一个Oracle表,其中包含以下列定义:

    COLUMN_NAME : RESERVATIONDATE
    DATA_TYPE   : TIMESTAMP(6)
    NULLABLE    : Yes
    DATA_DEFAULT: null

然后,从Java中执行以下命令:

    insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp)

然后在其他地方执行以下命令,目的是返回少于X秒前添加的行。

    select * from my_table where reservationDate >= systimestamp - NUMTODSINTERVAL( :seconds, 'SECOND' )

但是,尽管预订日期晚于确定的点,但不会返回任何行。

因此,我也从同一个应用程序运行以下命令:

    select col1,
           col2,
           reservationdate,
           systimestamp as b,
           systimestamp - NUMTODSINTERVAL( 5, 'SECOND' ) as c
    from my_table

其中给出了以下输出:

    col1: value1
    col2: value2
    reservationdate:2017-06-14 14:31:00.746173
    b              :2017-06-14 15:31:00.905617
    c              :2017-06-14 15:30:55.905617

请注意,bc返回的值基本上比reservationdate提前一小时。

在与Java应用程序在同一台机器上运行的SQL Developer上运行相同的请求会得到正确的值:

    reservationdate:14-JUN-17 02.31.00.746173000 PM
    b              :14-JUN-17 02.58.32.863300000 PM +00:00
    c              :14-JUN-17 02.58.27.863300000 PM +00:00

Oracle正在一台虚拟机上运行,​​其中Unix日期的输出为:

    Wed Jun 14 14:18:11 UTC 2017

在运行Java的机器上:

    Wed Jun 14 15:21:24 BST 2017

显然这是一个时区问题,但我不确切地知道它来自哪里。毕竟我一直在使用systimestamp,目的是在数据库服务器上进行所有时间戳计算。

我的请求通过Spring的NamedParameterJdbcTemplate,我正在使用Oracle Database 11g Express Edition 11.2.0.2.0

3 个答案:

答案 0 :(得分:3)

不是完全合格的答案,但这里有一些信息:

数据类型SYSTIMESTAMP不存储任何时区信息。当您插入带时区的时间戳(例如SYSTIMESTAMP)时,时区信息将被切断。

TIMESTAMP WITH TIME ZONE数据库服务器的时区中返回UTC数据类型'操作系统时区(在您的情况下为TIMESTAMP

每当您将TIMESTAMP WITH TIME ZONE(没有时区)转换/转换为SESSIONTIMEZONE时,Oracle会考虑a,1,5 a,2,6 a,3,7 - 除非您使用FROM_TZ或{{3}}明确设置时区类似。

答案 1 :(得分:2)

尝试TIMESTAMP WITH TIMEZONETIMESTAMP WITH LOCAL TIMEZONE简单TIMEZONE ...

答案 2 :(得分:1)

我认为你看到了两件事;当您按照提取方式执行调试查询时,格式化和显示bc值会导致Java转换时区。但我认为这是一个红色的鲱鱼,因为它不会直接影响你的原始查询来查找最近更改的行。它可能与您的Java语言环境有关,因此与主要查询问题有关。

如果您跟踪最初运行的查询,您应该看到它实际使用的过滤器是:

SYS_EXTRACT_UTC(INTERNAL_FUNCTION(RESERVATIONDATE))
  >=SYS_EXTRACT_UTC(SYSTIMESTAMP(6)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND'))

函数调用是因为"During SELECT FROM operations, Oracle converts the data from the column to the type of the target variable.";所以将列值转换为带有时区的时间戳,以便与systimestamp进行比较,然后将两个时间戳与区域值进行比较,将它们转换为UTC。

然后这又回到了@WernfriedDomscheit所说的话。 sys_extract_utc() functiondatetime_with_timezone作为参数,因此当传入普通timestamp时,会使用SESSIONTIMEZONE隐式转换它。您看到的查询结果不同,因为Java代码和SQL Developer之间的会话时区不同。

您可以select sessiontimezone from dual同时查看这些内容,或查看时间戳的实际转换方式。

您可以通过将系统时间强制转换回无时区的时间戳来避免此问题:

... where reservationDate >= cast(systimestamp as timestamp) - NUMTODSINTERVAL( :seconds, 'SECOND' );

跟踪显示更简单:

RESERVATIONDATE
  >=CAST(SYSTIMESTAMP(6) AS timestamp)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND') 

将列定义从timestamp更改为timestamp with [local] time zone也会有效;根据我的追踪,使用local转换为UTC仍然会发生,而没有本地的转换。 This is an interesting read too

如果您查看如何使用不同的会话时区解析sys_extract_utc()来电,您可以看到差异,使用新插入的行并省略日期元素以简化:

insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp);

alter session set nls_timestamp_format = 'HH24:MI:SS.FF1';
alter session set nls_timestamp_tz_format = 'HH24:MI:SS.FF1 TZR';

alter session set time_zone = 'Europe/London';

select reservationdate a,
  systimestamp b,
  systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
  cast(reservationdate as timestamp with time zone) d,
  sys_extract_utc(reservationdate) e,
  sys_extract_utc(systimestamp) f,
  sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;

A           B                 C                 D                        E           F           G          
----------- ----------------- ----------------- ------------------------ ----------- ----------- -----------
17:12:13.9  17:12:15.0 +01:00 17:12:10.0 +01:00 17:12:13.9 EUROPE/LONDON 16:12:13.9  16:12:15.0  16:12:10.0 

alter session set time_zone = 'UTC';

select reservationdate a,
  systimestamp(6) b,
  systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
  cast(reservationdate as timestamp with time zone) d,
  sys_extract_utc(reservationdate) e,
  sys_extract_utc(systimestamp) f,
  sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;

A           B                 C                 D              E           F           G          
----------- ----------------- ----------------- -------------- ----------- ----------- -----------
17:12:13.9  17:12:15.4 +01:00 17:12:10.4 +01:00 17:12:13.9 UTC 17:12:13.9  16:12:15.4  16:12:10.4 

我的设置似乎与您的设置不同 - 我的数据库服务器在英国时间,但DBTIMEZONE是+00:00 - 但即便如此,请注意{{1}中的两个值之间的差异列。在我的情况下,它看起来会发现太多数据而不是太少 - 因为e是真的。在您的情况下,我相信如果您从SQL Developer和Java运行那些,您将看到相反的差异,因为Java应用程序的会话时区(基于其语言环境)导致了另一种方式的转换。 / p>