Oracle 日期比较因 DST 而损坏

标签 oracle timezone timestamp dst

我们一直在调试通过 Hibernate 从运行 Java 的应用服务器执行 SQL 查询的问题。错误:

[3/10/14 10:52:07:143 EDT] 0000a984 JDBCException W org.hibernate.util.JDBCExceptionReporter logExceptions SQL Error: 1878, SQLState: 22008
[3/10/14 10:52:07:144 EDT] 0000a984 JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions ORA-01878: specified field not found in datetime or interval

我们已经能够将范围缩小到下面的简单 SQL。

select * 
from MY_TABLE T
where T.MY_TIMESTAMP >= (CURRENT_TIMESTAMP - interval '1' hour );

当我们在同一个数据库中运行它时,我们收到错误:

ORA-01878: specified field not found in datetime or interval
01878. 00000 -  "specified field not found in datetime or interval"
*Cause:    The specified field was not found in the datetime or interval.
*Action:   Make sure that the specified field is in the datetime or interval.

MY_TIMESTAMP列定义为 TIMESTAMP(6) .

FWIW,如果我们将上面 SQL 中的比较从 >= 更改为至<= ,查询有效。

我们假设这与时间变化有关(我们在美国/纽约),但我们在尝试弄清楚调试时从这里到哪里时遇到了问题。

此外,我们已经通过 MyBatis 运行的类似查询看到了此问题,错误如下所示:

### Error querying database.  Cause: java.sql.SQLException: ORA-01878: specified field not found in datetime or interval

### The error may involve defaultParameterMap
### The error occurred while setting parameters
### Cause: java.sql.SQLException: ORA-01878: specified field not found in datetime or interval

更新:Windows 上的一位队友通过取消选中“自动调整夏令时时钟”来更改她的 Windows 日期和时间设置,然后打开一个新的 SQLDeveloper 实例。第二个实例能够毫无问题地运行查询,但第一个实例(使用旧的 DST 设置)仍然失败。

最佳答案

要避免此错误,请考虑使用将 where 子句中的表达式显式转换为时间戳类型(不带时区的时间戳),如下所示:

select * 
from MY_TABLE T
where T.MY_TIMESTAMP >= cast(CURRENT_TIMESTAMP - interval '1' hour As timestamp );

或者,您可以使用 ALTER SESSION time_zone = '-05:00' 将 session 时区显式设置为“-05:00” - 纽约标准(冬季)时间
,或者通过在所有客户端环境中设置 ORA_SDTZ 环境变量,
有关详细信息,请参阅此链接:http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263

但这也取决于表中时间戳列中真正存储的内容,例如时间戳2014-07-01 15:00:00是什么。实际上代表的是“冬季”还是“夏季”?

<小时/>

CURRENT_TIMESTAMP函数返回数据类型 TIMESTAMP WITH TIME ZONE 的值
请参阅此链接:http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions037.htm

在比较时间戳和日期时,Oracle 使用 session 时区将数据隐式转换为更精确的数据类型!
请参阅此链接 --> http://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG251

在我们的特定情况下,Oracle 强制转换 timestamp列至timestamp with time zone类型。

Oracle 根据客户端环境确定 session 时区。
您可以使用以下查询确定当前 session 时区:

select sessiontimezone from dual;

例如,在我的 PC (Win 7) 上,当选中“自动调整夏令时时钟”选项时,此查询返回(在 SQLDeveloper 下):

SESSIONTIMEZONE                                                           
---------------
Europe/Belgrade 


当我在 Windows 中取消选中此选项然后重新启动 SQLDeveloper 时,它会给出:

SESSIONTIMEZONE                                                           
---------------
+01:00     

以前的 session 时区是带有区域名称的时区,Oracle 在日期计算中使用该区域的夏令时规则:

alter session set time_zone = 'Europe/Belgrade';
select cast( timestamp '2014-01-29 01:30:00' as timestamp with time zone ) As x,
       cast( timestamp '2014-05-29 01:30:00' as timestamp with time zone ) As y
from dual;

session SET altered.
X                            Y                          
---------------------------- ----------------------------
2014-01-29 01:30:00 EUROPE/B 2014-05-29 01:30:00 EUROPE/B 
ELGRADE                      ELGRADE       


后一个时区使用固定偏移量“+01:00”(始终是“冬令时”),并且 Oracle 不对其应用任何 DST 规则,它只是添加固定偏移量。

alter session set time_zone = '+01:00';
select cast( timestamp '2014-01-29 01:30:00' as timestamp with time zone ) As x,
       cast( timestamp '2014-05-29 01:30:00' as timestamp with time zone ) As y
from dual;

session SET altered.
X                            Y                          
---------------------------- ----------------------------
2014-01-29 01:30:00 +01:00   2014-05-29 01:30:00 +01:00  
<小时/>

出于好奇,请注意 Y上面的结果代表两个不同的时间!!!
014-05-29 01:30:00 EUROPE/BELGRADE不等于:2014-05-29 01:30:00 +01:00

但实际上是这样的:
014-05-29 01:30:00 EUROPE/BELGRADE等于:2014-05-29 01:30:00 +02:00

上面的内容只是为了让您了解简单的“取消选中框”可能会影响您的查询,以及当用户提示“此查询在 1 月份工作正常,但在 7 月份给出错误结果”时应该在哪里挖掘原因。

<小时/>

仍然是 ORA-01878 的主题 - 假设我的 session 是 EUROPE/Warsaw我的表包含此时间戳(不含时区)

'TIMESTAMP'2014-03-30 2:30:00'

请注意,在我所在地区,2014 年 DST 更改发生在 3 月 30 日凌晨 2:00
它只是意味着在 3 月 30 日晚上 2:00,我必须醒来并将 watch 从 2:00 拨到 3:00 ;)

alter session set time_zone = 'Europe/Warsaw';
select cast( TIMESTAMP'2014-03-30 2:30:00' as timestamp with time zone ) As x
from dual;

SQL Error: ORA-01878: podane pole nie zostało znalezione w dacie-godzinie ani w interwale
01878. 00000 -  "specified field not found in datetime or interval"
*Cause:    The specified field was not found in the datetime or interval.
*Action:   Make sure that the specified field is in the datetime or interval.

Oracle 知道,根据 DST 规则,此时间戳在我所在的地区无效,因为 3 月 30 日没有时间 2:30 - 2:00 时钟移至 3 :00,没有时间2:30。因此Oracle抛出错误ORA-01878。

然而这个查询工作得很好:

alter session set time_zone = '+01:00';
select cast( TIMESTAMP'2014-03-30 2:30:00' as timestamp with time zone ) As x
from dual;

session SET altered.
X                          
----------------------------
2014-03-30 02:30:00 +01:00 

这就是此错误的原因 - 您的表包含类似 2014-03-09 2:30 的时间戳大约如此(对于纽约,DST 转换发生在 3 月 9 日和 11 月 2 日),并且 Oracle 不知道如何将它们从时间戳(不带 TZ)转换为带 TZ 的时间戳。

<小时/>

最后一个问题 - 为什么使用 >= 进行查询不起作用,但使用 <= 进行查询工作正常吗?

它们有效/无效,因为 SQLDeveloper 仅返回前 50 行(也许 100 行?这取决于设置)。查询不会读取整个表,它会在获取前 50(100) 行时停止。
将“工作”查询更改为,例如:

select sum( EXTRACT(HOUR from MY_TIMESTAMP) ) from MY_TABLE 
where MY_TIMESTAMP <= (CURRENT_TIMESTAMP - interval '1' hour );

这会强制查询读取表中的所有行,并且会出现错误,我 100% 确定。

关于Oracle 日期比较因 DST 而损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22305466/

相关文章:

Oracle - oracle_unqname 的启动错误

java - Oracle jdbc 驱动程序类之间的区别?

asp.net - 如何为我的 ASP.NET (MVC) 应用程序设置时区

java - java.time.ZoneId 是否有原因不包括 ZoneIds 的枚举?

java - 将时间戳过滤器添加到范围

mysql - 为什么mysql时间戳列使用时间戳来搜索得到更大的结果?

sql - 如何在 Oracle Pro*C 中显示包含主机变量值的 SQL 语句?

sql - 数字和逗号的正则表达式

google-maps-api-3 - 育空省的时区 API 返回不正确的结果

youtube - 从嵌入的 YouTube 视频中获取当前时间戳