Странное поведение systimestamp и sysdate - PullRequest
2 голосов
/ 17 июня 2020

Сегодня я столкнулся с ситуацией, которую не могу объяснить и надеюсь, что сможете.

Это сводится к следующему:

Допустим, function sleep() return number; повторяется в течение 10 секунд, а затем возвращает 1.

A sql запрос, например

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

Получит два идентичных значения для s1 и s2. Так что это несколько оптимизировано.

PL SQL -Block с

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

будет оцениваться как true, потому что выражение вычисляется слева направо, а из-за sleep () вторая systimestamp равна 10se c больше, чем первое, и a_timestamp находится между ними. Синтаксис + 5se c - это псевдокод, но терпите меня. Итак, здесь systimestamp не оптимизирован.

Но теперь это становится напуганным:

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

Всегда ложно, но

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

Всегда верно.

Почему? Я сбит с толку ...

Это происходит и с sysdate

Ответы [ 2 ]

1 голос
/ 18 июня 2020

Когда вы делаете:

if systimestamp between systimestamp and systimestamp then

недетерминированный c systimestamp вызов просто оценивается три раза и каждый раз дает очень немного разные результаты.

Вы можно увидеть тот же эффект с

if systimestamp >= systimestamp then

, который также всегда возвращает false.

За исключением того, что это не всегда. Если сервер достаточно быстрый и / или платформа, на которой он работает, имеет достаточно низкую точность для дробных секунд временной метки (т.е. на Windows, что, как я считаю, по-прежнему ограничивает точность до миллисекунд), тогда все эти вызовы могут получить то же значение иногда или большую часть времени.

В SQL все немного иначе; как эквивалент:

select *
from dual
where systimestamp between systimestamp and systimestamp;

всегда будет возвращать строку, поэтому условие всегда истинно. Это явно упоминается в документации :

Все функции datetime, которые возвращают текущую системную информацию о дате и времени, такую ​​как SYSDATE, SYSTIMESTAMP, CURRENT_TIMESTAMP и т. Д., Оцениваются один раз. для каждого оператора SQL, независимо от того, сколько раз они упоминаются в этом операторе.

В PL / SQL нет такого ограничения / оптимизации (в зависимости от того, как вы на это смотрите). Когда вы используете between , он говорит :

Значение выражения x BETWEEN a AND b определяется как такое же, как значение выражения (x> = a) И (x <= b). Выражение x будет вычислено только один раз. </p>

и SQL ссылка также упоминает, что :

В SQL, это возможно, что expr1 будет оцениваться более одного раза. Если выражение BETWEEN появляется в PL / SQL, expr1 гарантированно оценивается только один раз.

, но ничего не говорит о пропуске вычисления a и b (или expr2 или expr3 ) даже для системных функций datetime в 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) должно быть ложным - если только интервал между первой и третьей оценками фактически равен нулю для этой платформы / сервера / вызова. Когда он равен нулю, условие оценивается как истинное; в остальное время, когда есть какая-либо измеримая задержка, она будет оцениваться как ложь.


Все ваши другие примеры манипулируют меткой времени таким образом, что удаляют доли секунды, поворачивая некоторые или все результаты в даты, как показал @Connor (и я упоминал в комментариях). Это на самом деле не имеет отношения к вашему основному вопросу о том, почему if systimestamp between systimestamp and systimestamp then (обычно) ложно.

1 голос
/ 18 июня 2020

Если вы добавите число к отметке времени, вы не получите отметку времени ... вы получите дату. Таким образом, вы отрезали дробную часть секунд, и, таким образом, сравнение, которое выглядит так, как будто они должны быть равны, может не быть

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

Если вы хотите добавить дни et c к отметка времени, используйте тип данных ИНТЕРВАЛ, а не ЧИСЛО.

...