我们一直在调试通过 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/