Почему Oracle не обрабатывается как 'NULL в МЕЖДУ со сравнением NVL - PullRequest
0 голосов
/ 24 апреля 2020

У меня есть этот запрос, не заполняющий данные в качестве переменных. Ожидая ряда; Я ничего не получаю.

select 1 from dual
where  SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)

Я пытаюсь это сделать, ожидая строки, и я получаю строку.

select 1 from dual
where  SYSDATE BETWEEN SYSDATE  AND  SYSDATE

Что-то не работает, как ожидалось. NVL должен обрабатывать '' как NULL.

Я пытаюсь это сделать, ожидая строки, и получаю строку.

select 1 from dual
where '' is null

If '' NULL, что мы сейчас доказали, это должно вернуть строку. Но это не так.

select 1 from dual
where SYSDATE BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)

Ответы [ 4 ]

2 голосов
/ 25 апреля 2020

Здесь есть несколько интересных ответов, но я не уверен, что кто-то еще объяснил, почему ваш запрос не возвращает строк. (Извиняюсь, если они есть, а я пропустил это - просто игнорируйте меня тогда!)

Это потому, что NVL('', SYSDATE) возвращает символьную строку , так как первый аргумент '' является символом strng , Так что NVL('', SYSDATE) эквивалентно NVL('', TO_CHAR(SYSDATE)). И поскольку мы не указали формат для TO_CHAR, Oracle будет использовать значение по умолчанию, которое обычно не включает компонент времени.

Итак, это:

where SYSDATE BETWEEN NVL(:START_DATE, SYSDATE) AND NVL(:END_DATE, SYSDATE)

Рассматривается примерно так:

where SYSDATE BETWEEN '25-APR-2020' AND '25-APR-2020'

Oracle затем преобразует эти строки обратно в даты, чтобы выполнить BETWEEN, поэтому примет 00:00:00 для времени суток. Поэтому, если вы не запустите его ровно в полночь, он не вернет строку.

Однако, если вы установите формат даты по умолчанию, включающий время, подобное этому:

alter session set nls_date_format = 'DD-MON-YYYY HH24:MI:SS';

Вы найдете теперь он работает, потому что компонент времени даты больше не теряется в неявных преобразованиях.

Вероятно, лучший способ справиться с этим, хотя бы убедиться, что вы имеете дело с датами, а не со строками вообще:

select 1 from dual
where  SYSDATE BETWEEN NVL(TO_DATE(:START_DATE), SYSDATE)
                   AND NVL(TO_DATE(:END_DATE), SYSDATE)
1 голос
/ 25 апреля 2020

Это не то, что вы не знаете, что убьет вас. Это «то, что вы знаете», что просто не так ...

Причина, по которой Oracle не рассматривает '' как NULL, заключается в том, что '' не является NULL - это ноль- длина строки константа. Теперь, прямо здесь люди собираются заскочить и сказать мне, что я неправ, и что я не знаю, о чем говорю, и поставить под сомнение мое происхождение, и, вероятно, начать ходатайство об отказе в моем социальном обеспечении и пнуть меня из дома старого пуканья, но я прав. Вот демонстрация:

DECLARE
  vStr  VARCHAR2(10) := '';
  cStr  CHAR(10) := '';
  vNULL VARCHAR2(10) := NULL;
  cNULL CHAR(10) := NULL;
  nStr  NUMBER := '';
  nNULL NUMBER := NULL;
BEGIN
  DBMS_OUTPUT.PUT_LINE('LENGTH(vStr)  = ' || LENGTH(vStr));
  DBMS_OUTPUT.PUT_LINE('LENGTH(cStr)  = ' || LENGTH(cStr));

  DBMS_OUTPUT.PUT_LINE('LENGTH(vNULL) = ' || LENGTH(vNULL));
  DBMS_OUTPUT.PUT_LINE('LENGTH(cNULL) = ' || LENGTH(cNULL));

  DBMS_OUTPUT.PUT_LINE('LENGTH(nStr)  = ' || LENGTH(nStr));
  DBMS_OUTPUT.PUT_LINE('LENGTH(nNULL) = ' || LENGTH(nNULL));
END;

Теперь , не глядя вниз (да, я знаю - это сложно. Попробуйте ... :-) Какой вывод вы ожидаете от приведенного выше кода? Если вы похожи на большинство людей (включая меня, пока я не споткнулся об этом несколько лет go), вы ожидаете, что это будет:

LENGTH(vStr)  = 
LENGTH(cStr)  = 
LENGTH(vNULL) = 
LENGTH(cNULL) = 
LENGTH(nStr)  = 
LENGTH(nNULL) = 

То есть вы ожидаете LENGTH функция, возвращающая NULL, когда применяется ко всем этим переменным - потому что все они должны быть NULL, верно?

Но это не то, что вы получаете ( см. Эту базу данных <> fiddle ). То, что вы на самом деле получаете:

LENGTH(vStr)  = 
LENGTH(cStr)  = 10
LENGTH(vNULL) = 
LENGTH(cNULL) = 
LENGTH(nStr)  = 
LENGTH(nNULL) = 

Вау! Подождите!! Что там делает 10?!?

Ну, это довольно просто. Подумайте о семантике типа данных CHAR в PL / SQL. Если вы назначаете строку переменной или полю CHAR, а длина назначенной строки короче определенной длины переменной или поля, значение, назначенное переменной или полю, дополняется справа до полного определенного ширина переменной / поля - в данном случае 10 символов. Поэтому, когда '' - то есть строковая константа нулевой длины - назначается переменной cStr, значение, назначенное переменной, дополняется справа до определенной ширины переменной, поэтому cStr заканчивается заполнен 10 пробелами. Но когда NULL присваивается той же символьной переменной, в конечном итоге устанавливается значение NULL, как и ожидалось, и функция LENGTH возвращает NULL, как и ожидалось.

Семантика VARCHAR2 (и на данный момент , VARCHAR - по крайней мере, до тех пор, пока Oracle не найдет поддержку семантики ANSI для VARCHAR - что они собираются сделать в настоящий момент (tm)) различаются в Oracle. Когда переменной / полю типа VARCHAR2 присваивается значение, оно не выполняет никаких дополнений; вместо этого он присваивает переменной или полю только значащие символы исходной строки, и если результирующая длина строки, назначенной переменной, равна нулю, тогда для переменной или поля устанавливается значение NULL в соответствии с правилом Oracle что «строковые значения нулевой длины совпадают с NULL». Но это происходит в тот момент, когда значение присваивается переменной. '' само по себе все еще является строковой константой нулевой длины.

Просто помните - это не то, что вы не знаете, что убьет вас. Это "то, что ты знаешь", но это не так ...: -)

0 голосов
/ 25 апреля 2020

Вы должны использовать, как показано ниже, чтобы сопоставить ноль с нулем, чтобы получить строку

select 1 from dual
where nvl('',SYSDATE) BETWEEN NVL('', SYSDATE) AND NVL('', SYSDATE)
0 голосов
/ 25 апреля 2020

Проблема в том, что некоторые пользовательские интерфейсы (включая, к сожалению, SQL* Plus и SQL Developer) не поддерживают тип данных DATE для переменных связывания.

Это ограничение этих пользовательские интерфейсы. Oracle SQL вполне способен работать с переменными связывания DATE, и если вы сможете передать ему NULL типа данных DATE, он обработает это, как и ожидалось. См. Иллюстрацию ниже.

Некоторые опубликованные ответы касаются вторичной проблемы - если вы передадите NULL типа данных CHAR или VARCHAR2, что произойдет? Я не думаю, что понимание точной причины важно; исключительно важно , чтобы понять это ограничение интерфейсных программ и - если вы вынуждены использовать их для взаимодействия с базой данных - убедитесь, что ваши переменные связывания имеют строковый тип данных, и запрос отражает это (с использованием TO_DATE с правильной моделью формата даты).

Если вы используете PL / SQL и / или, возможно, другие вызывающие среды (например, не уверен насчет ApEx, так как я его не использую) ), вы можете передать NULL типа данных DATE напрямую и получить ожидаемое поведение. Вот иллюстрация: я строю ваш запрос как динамический c запрос с переменными связывания, и вызываю его и передаю ему NULL типа данных DATE. Посмотрите, что происходит:

declare
  l_sql clob;
  l_n   number;
begin
  l_sql := 'select 1 
            from   dual 
            where  sysdate between nvl(:start_date, sysdate)
                               and nvl(:end_date  , sysdate)';
  execute immediate l_sql into l_n using cast(null as date), cast(null as date);
  dbms_output.put_line(l_n);
end;
/


1

PL/SQL procedure successfully completed.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...