Oracle PL / SQL: чем SYSDATE отличается от 'DD-MMM-YY'? - PullRequest
0 голосов
/ 27 апреля 2018

Мне интересно, почему SYSDATE отличается от, скажем, 28-APR-18 (при условии, что SYSDATE - 28 апреля 2018 года).

Я отлаживал небольшой скрипт, который сделал, и получил много ошибок. Через некоторое время мне удалось сузить его до того, как таблицы были заполнены (они были заполнены с использованием буквального метода 'DD-MMM-YYYY', и я сравнивал с SYSDATE).

Чтобы понять, я написал следующее, чтобы увидеть, как каждый сравнивает:

declare var1 DATE; var2 DATE;
BEGIN
   var1 := SYSDATE;
   var2 := '27-APR-18';

   if var1 = var2 then
   DBMS_OUTPUT.PUT_LINE('oh yeah');
   else DBMS_OUTPUT.PUT_LINE('WTF?'); 
       DBMS_OUTPUT.PUT_LINE(SYSDATE);DBMS_OUTPUT.PUT_LINE('27-APR-18');
   end if;
END;

Если все вышеперечисленное выполняется, я получаю следующее (что меня озадачивает):

WTF?
27-APR-18
27-APR-18


PL/SQL procedure successfully completed.

Поскольку они оба объявлены как тип DATE, разве они не должны быть равны?

Спасибо за ваше время и помощь!

Ответы [ 3 ]

0 голосов
/ 27 апреля 2018

Помимо того, что объяснил @horse, вы можете использовать TRUNC в sysdate для var1 и литерал даты в var2 в YYYY-MM-DD формате, это будет соответствовать.

declare 
var1 DATE; 
var2 DATE;
BEGIN
   var1 := TRUNC(SYSDATE);
   var2 := DATE '2018-04-27';

   if var1 = var2 then
        DBMS_OUTPUT.PUT_LINE('oh yeah');
   else 
     DBMS_OUTPUT.PUT_LINE('WTF?'); 
     DBMS_OUTPUT.PUT_LINE(SYSDATE ||','||'27-APR-18');
   end if;
END;

oh yeah
0 голосов
/ 27 апреля 2018

Просмотр вашего кода (с номерами строк)

 1 DECLARE
 2   var1 DATE;
 3   var2 DATE;
 4 BEGIN
 5   var1 := SYSDATE;
 6   var2 := '27-APR-18';
 7 
 8   if var1 = var2 then
 9     DBMS_OUTPUT.PUT_LINE('oh yeah');
10   END
11     DBMS_OUTPUT.PUT_LINE('WTF?'); 
12     DBMS_OUTPUT.PUT_LINE(SYSDATE);
13     DBMS_OUTPUT.PUT_LINE('27-APR-18');
14   END IF;
15 END;

В строке 5 : '27-APR-18' - это текстовый литерал (не тип данных DATE), и при попытке присвоить его переменному DATE Oracle попытается быть полезным и неявным приведите его к DATE, используя функцию TO_DATE( date_string, format_model ). Поскольку это неявное приведение, Oracle будет использовать свою модель формата по умолчанию, которая является параметром сеанса пользователя NLS_DATE_FORMAT.

Вы можете увидеть формат параметра сеанса NLS_DATE_FORMAT, используя запрос:

SELECT VALUE
FROM   NLS_SESSION_PARAMETERS
WHERE  PARAMETER = 'NLS_DATE_FORMAT';

И строка 5 эквивалентна:

var2 := TO_DATE(
          '27-APR-18',
          ( SELECT VALUE
            FROM   NLS_SESSION_PARAMETERS
            WHERE  PARAMETER = 'NLS_DATE_FORMAT' )
        );

Если модель формата по умолчанию соответствует строке, то это неявное приведение будет работать - в противном случае ваш код вызовет исключение.

Другая возможная проблема заключается в том, что NLS_DATE_FORMAT равен DD-MON-YYYY (или эквивалентному), а затем 27-APR-18 будет приведен к дате 0018-04-27 00:00:00 и будет иметь (вероятно) неожиданный и неправильный век!

(Примечание: NLS_DATE_FORMAT - это параметр сеанса, поэтому каждый пользователь может установить свое собственное значение, поэтому НИКОГДА не следует полагаться на неявное приведение, в противном случае вы обнаружите, что тот же код, который работает для самостоятельно происходит сбой других пользователей или даже может произойти сбой одного и того же пользователя в разных сеансах - без изменения кода.)

В строке 5 вы должны либо использовать литерал DATE, либо явно преобразовать текстовый литерал, используя функцию TO_DATE с моделью формата:

var2 := DATE '2018-04-27';
var2 := TO_DATE( '27-APR-18', 'DD-MON-RR' );

В строке 8 : Вы сравниваете две даты; однако тип данных DATE всегда имеет компонент времени, и если вы не запустите код в 2018-04-27T00: 00: 00, тогда SYSDATE будет иметь «ненулевой» компонент времени, тогда как var2 не имел явно указанный компонент времени, поэтому неявное приведение даст ему компонент времени 00:00:00 (полночь). Это означает, что, за исключением ровно полуночи, эти два никогда не будут равны, и сравнение вернет FALSE.

Если вы хотите обнулить временные компоненты DATE при сравнении двух дат, используйте функцию TRUNC() или укажите диапазон, например:

IF TRUNC( var1 ) = var2 THEN
IF var2 <= var1 AND var1 < var2 + INTERVAL '1' DAY THEN

В строке 12 : Это обратная строка 5 - DBMS_OUTPUT.PUT_LINE( string ) принимает строковый аргумент - не DATE - поэтому Oracle неявно попытается привести DATE к строке (используя NLS_DATE_FORMAT снова). Таким образом, строка 12 является эффективной;

DBMS_OUTPUT.PUT_LINE(
  TO_CHAR(
    SYSDATE,
    ( SELECT VALUE
      FROM   NLS_SESSION_PARAMETERS
      WHERE  PARAMETER = 'NLS_DATE_FORMAT' )
  )
)

Поскольку ваш NLS_DATE_FORMAT равен DD-MON-RR, он не отображает компоненты времени и не показывает, почему переменные отличаются.

Если вы делаете:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';

DECLARE
  var1 DATE;
  var2 DATE;
BEGIN
  var1 := SYSDATE;
  var2 := DATE '2018-04-27';
  -- or var2 := TO_DATE( '2018-04-27 00:00:00', 'YYYY-MM-DD HH24:MI:SS' );
  -- or var2 := TIMESTAMP '2018-04-27 00:00:00';

  if var1 = var2 then
    DBMS_OUTPUT.PUT_LINE('oh yeah');
  END
    DBMS_OUTPUT.PUT_LINE('WTF?'); 
    DBMS_OUTPUT.PUT_LINE( var1 );
    -- or DBMS_OUTPUT.PUT_LINE( TO_CHAR( var1, 'YYYY-MM-DD HH24:MI:SS' ) );
    DBMS_OUTPUT.PUT_LINE( var2 );
    -- or DBMS_OUTPUT.PUT_LINE( TO_CHAR( var2, 'YYYY-MM-DD HH24:MI:SS' ) );
  END IF;
END;
/

Тогда вывод будет:

2018-04-27 10:28:59
2018-04-27 00:00:00

и вы можете увидеть разницу.

0 голосов
/ 27 апреля 2018

В Oracle значение DATE - несмотря на имя - также содержит часть времени. SYSDATE содержит текущую дату и текущее время (до секунд).

Инструменты Oracle по умолчанию (тупо) скрывают временную часть значения DATE. Если вы запустите:

select to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss') as sysdate
from dual;

Вы можете видеть это.

Так что SYSDATE может быть 2018-04-27 09:15:42, тогда как строковая (!) Константа '27-APR-18' тихо преобразуется в значение DATE в полночь: 2017-04-28 00:00:00

Подробнее в главе Основные элементы Oracle SQL в руководстве


Если вам не важна часть времени, вы можете использовать trunc(), чтобы установить время в полночь, trunc(sysdate) дает 2018-04-27 00:00:00 (если сегодня 2018-04-27). Обратите внимание, что trunc() не «удаляет» время, оно только устанавливает его на 00: 00: 00


Не связано, но:

Вы никогда не должны полагаться на неявное приведение между строками и другими не символьными типами, что var2 := '27-APR-18' делает - это будет, например, Ошибка на моем компьютере, так как мой формат даты по умолчанию NLS отличается.

Если вам нужно значение DATE, укажите правильный литерал даты:

var2 := DATE '2018-04-27'; 

или

var2 := to_date('27-APR-18', 'dd-mon-rr');

или

var2 := to_date('27-APR-18 00:00:00', 'dd-mon-rr hh24:mi:ss');
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...