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.
但它没有说明跳过 a 和 b (或 expr2 或expr3) 即使对于 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
为什么(通常)为假的核心问题并不相关。
今天我遇到了一个无法解释的情况,希望你能解释一下。
归结为:
假设 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.
但它没有说明跳过 a 和 b (或 expr2 或expr3) 即使对于 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
为什么(通常)为假的核心问题并不相关。