systimestamp 和 sysdate 的奇怪行为

Strange behavior of systimestamp and sysdate

今天我遇到了一个无法解释的情况,希望你能解释一下。

归结为:

假设 function sleep() return number; 循环 10 秒然后 returns 1.

一个sql查询喜欢

SELECT systimestamp s1, sleep(), systimestamp s2 from dual;

将导致 s1 和 s2 的两个相同值。所以这有点优化。

PLSQL 块

a_timestamp := systimestamp + 5sec;
IF systimestamp < a_timestamp and sleep() = 1 and systimestamp > a_timestamp THEN 
  [...] 
END IF;

将计算为真,因为表达式从左到右计算,并且由于 sleep(),第二个系统时间戳比第一个多 10 秒,a_timestamp 位于两者之间。 +5sec 语法是伪代码,但请耐心等待。所以这里的systimestamp没有优化。

但现在它变得时髦了:

IF systimestamp between systimestamp and systimestamp THEN [...]

总是假的,但是

IF systimestamp + 0 between systimestamp + 0 and systimestamp + 0 THEN [...]

总是正确的。

为什么?我很困惑...

sysdate 也会发生这种情况

如果将数字添加到时间戳,则不会得到时间戳...得到的是日期。因此,您已经去掉了小数秒部分,因此 看起来 应该相等的比较可能不是

SQL> select systimestamp, systimestamp+0 from dual;

SYSTIMESTAMP                                                                SYSTIMESTAMP+0
--------------------------------------------------------------------------- -------------------
18-JUN-20 11.57.28.350000 AM +08:00                                         18/06/2020 11:57:28

SQL> select * from dual where systimestamp > sysdate;

D
-
X

如果要将天数等添加到时间戳,请使用 INTERVAL 数据类型而不是 NUMBER。

当你这样做时:

if systimestamp between systimestamp and systimestamp then

非确定性 systimestamp 调用仅被评估三次,每次得到的结果都略有不同。

你可以看到与

相同的效果
if systimestamp >= systimestamp then

这也总是 return 错误。

除了,并非总是如此。如果服务器足够快 and/or 它所在的平台对于时间戳小数秒的精度足够低(即在 Windows 上,我相信它仍然将精度限制为毫秒)那么所有这些调用仍然可以有时或大部分时间获得相同的值。

SQL的情况有点不同;作为等价物:

select *
from dual
where systimestamp between systimestamp and systimestamp;

总是return一行,所以条件总是为真。即explicitly mentioned in the documentation:

All of the datetime functions that return current system datetime information, such as SYSDATE, SYSTIMESTAMP, CURRENT_TIMESTAMP, and so forth, are evaluated once for each SQL statement, regardless how many times they are referenced in that statement.

PL/SQL中没有这样的restriction/optimisation(看你怎么看)。当你使用 between it does say:

The value of the expression x BETWEEN a AND b is defined to be the same as the value of the expression (x>=a) AND (x<=b) . The expression x will only be evaluated once.

和 SQL 引用 also mentions that:

In SQL, it is possible that expr1 will be evaluated more than once. If the BETWEEN expression appears in PL/SQL, expr1 is guaranteed to be evaluated only once.

但它没有说明跳过 ab (或 expr2expr3) 即使对于 PL/SQL.

中的系统日期时间函数

所以你的 between 中的所有三个表达式都将被评估,它将对 systimestamp 进行三个单独的调用,并且它们都会(通常)得到略有不同的结果。您实际上得到了:

if initial_time between initial_time + 1 microsecond and initial_time + 2 microseconds then

或者换句话说

if (initial_time >= initial_time + 1 microsecond) and (initial_time <= initial_time + 2 microseconds) then

虽然 (initial_time <= initial_time + 2 microseconds) 始终为真,但 (initial_time >= initial_time + 1 microsecond) 必须为假 - 除非对于 platform/server/invocation,第一次和第三次评估之间的间隔实际上为零。当它为零时,条件评估为真;其余时间,当有任何可测量的延迟时,它将评估为 false。


您的其他示例都以删除小数秒的方式操纵时间戳,方法是将部分或全部结果转换为日期,如@Connor 所示(我在评论中提到)。这些与 if systimestamp between systimestamp and systimestamp then 为什么(通常)为假的核心问题并不相关。