SQL и логические операторы и нулевые проверки - PullRequest
10 голосов
/ 21 декабря 2011

У меня есть расплывчатая, возможно, грубо-культовая память за годы работы с SQL Server, поэтому, когда у вас есть столбец с возможно нулевым значением, писать предикаты предложения WHERE небезопасно, например:

 ... WHERE the_column IS NULL OR the_column < 10 ...

Это было как-то связано с тем фактом, что правила SQL не предусматривают короткое замыкание (а на самом деле это плохая идея, возможно, по причинам оптимизации запросов), и, следовательно, сравнение "<" (или что-то еще) может быть оценено, даже если значение столбца равно нулю. Теперь, точно <em>почему это было бы ужасно, я не знаю, но я вспоминаю, как некоторые документы строго предупреждали меня всегда кодировать это как предложение "CASE":

 ... WHERE 1 = CASE WHEN the_column IS NULL THEN 1 WHEN the_column < 10 THEN 1 ELSE 0 END ...

(глупая часть "1 =" потому, что в SQL Server нет / не было первоклассных логических значений, или, по крайней мере, я думал, что этого не было.)

Итак, мои вопросы здесь:

  1. Это действительно так для SQL Server (или, возможно, более ранней версии SQL Server 2000 или 2005), или я просто чокнутый?
  2. Если это так, то же самое относится к PostgreSQL? (8,4, если это имеет значение)
  3. В чем конкретно проблема? Это как-то связано с тем, как работают индексы?

Мое обоснование в SQL довольно слабое.

Ответы [ 6 ]

11 голосов
/ 21 декабря 2011

Я не знаю SQL Server, поэтому не могу говорить об этом.

Учитывая выражение a L b для некоторого логического оператора L, нет гарантии, что a будет оцениваться до или после b или даже что и a, и b будут оцениваться:

Правила оценки выражений

Порядок вычисления подвыражений не определен. В частности, входные данные оператора или функции не обязательно оцениваются слева направо или в любом другом фиксированном порядке.

Кроме того, если результат выражения можно определить, оценивая только некоторые его части, то другие подвыражения могут вообще не оцениваться.
[...]
Обратите внимание, что это не то же самое, что «короткое замыкание» логических операторов слева направо, встречающееся в некоторых языках программирования.

Как следствие, неразумно использовать функции с побочными эффектами как часть сложных выражений. Особенно опасно полагаться на побочные эффекты или порядок оценки в предложениях WHERE и HAVING, поскольку эти пункты интенсивно обрабатываются в рамках разработки плана выполнения.

Что касается выражения вида:

the_column IS NULL OR the_column < 10

обеспокоен тем, что беспокоиться не о чем, поскольку NULL < n равно NULL для всех n, даже NULL < NULL равно NULL; Более того, NULL не соответствует действительности, поэтому

null is null or null < 10

это просто сложный способ сказать true or null, и это true независимо от того, какое подвыражение вычисляется первым.

Мне кажется, что «использование СЛУЧАЯ» звучит в основном как культ груза SQL. Однако, как и большинство грузовых культов, есть ядро ​​истины, похороненной под грузом; чуть ниже моего первого отрывка из руководства PostgreSQL вы найдете это:

Когда необходимо форсировать порядок оценки, можно использовать конструкцию CASE (см. Раздел 9.16). Например, это ненадежный способ избежать деления на ноль в предложении WHERE:

SELECT ... WHERE x > 0 AND y/x > 1.5;

Но это безопасно:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

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

Каждое условие является выражением, которое возвращает boolean результат. Если результат условия истинен, значением выражения CASE является результат , следующий за условием, а остальная часть выражения CASE не обрабатывается. Если результат условия неверен, любые последующие предложения WHEN проверяются таким же образом.

Итак, учитывая это:

case when A then Ra
     when B then Rb
     when C then Rc
     ...

A гарантированно оценивается до B, B до C и т. Д., И оценка останавливается, как только одно из условий оценивается как истинное значение.

В итоге, CASE короткое замыкание не вызывает ни AND, ни OR короткое замыкание, поэтому вам нужно использовать CASE только тогда, когда вам нужно защитить от побочных эффектов.

1 голос
/ 06 апреля 2013

вместо

the_column IS NULL OR the_column < 10

Я бы сделал

isnull(the_column,0) < 10

или для первого примера

WHERE 1 = CASE WHEN isnull(the_column,0) < 10 THEN 1 ELSE 0 END ...
1 голос
/ 21 декабря 2011

Ну, я неоднократно писал запросы, как в первом примере с тех пор, навсегда (черт возьми, я написал генераторы запросов, которые генерируют такие запросы), и у меня никогда не было проблем.

Я думаю, вы, возможно, помните какое-то предостережение, которое кто-то когда-то давал вам против написания фанки условий соединения , которые используют OR. В первом примере условия, к которым присоединяется OR, ограничивают один и тот же столбец одной и той же таблицы, что в порядке. Если ваше второе условие было условием соединения (т. Е. Оно ограничивало столбцы из двух разных таблиц), то вы можете попасть в плохие ситуации, когда у планировщика запросов просто нет другого выбора, кроме как использовать декартово соединение (плохое, плохое, плохое !!! ).

Я не думаю, что ваша функция CASE действительно что-то там делает, за исключением, возможно, затруднения попыток планировщика запросов найти хороший план выполнения для запроса.

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

1 голос
/ 21 декабря 2011

Я никогда не слышал о такой проблеме, и этот фрагмент документации по SQL Server 2000 использует WHERE advance < $5000 OR advance IS NULL в качестве примера, так что это не должно быть очень строгим правилом.Мое единственное беспокойство по поводу OR состоит в том, что он имеет более низкий приоритет, чем AND, поэтому вы можете случайно написать что-то вроде WHERE the_column IS NULL OR the_column < 10 AND the_other_column > 20, когда вы не это имеете в виду;но обычным решением являются круглые скобки, а не большое CASE выражение.

Я думаю, что в большинстве RDBMS индексы не содержат нулевых значений, поэтому индекс для the_column не будет очень полезен дляэтот запрос;но даже если бы это было не так, я не понимаю, почему большое CASE выражение было бы более удобным для индекса.

(Конечно, трудно доказать отрицание, и, возможно, кто-тоеще будет знать, о чем ты говоришь?)

0 голосов
/ 22 июля 2014

Другой пример, где CASE полезен, - это использование функций даты в столбцах varchar.добавление ISDATE перед использованием say convert (colA, datetime) может не сработать, а когда в colA имеются данные, не относящиеся к дате, запрос может завершиться ошибкой.

0 голосов
/ 21 декабря 2011

Нули могут сбивать с толку.«... WHERE 1 = CASE ...» полезно, если вы пытаетесь передать значение Null OR в качестве параметра ex."ГДЕ the_column = @parameter. Этот пост может быть полезен Передача Null с использованием OLEDB .

...