(x не равен NULL) против (не x равен NULL) в PostgreSQL

16

Почему x IS NOT NULLне равно NOT x IS NULL?

Этот код:

CREATE TABLE bug_test (
    id int,
    name text
);

INSERT INTO bug_test
VALUES (1, NULL);

DO $$
DECLARE
    v_bug_test bug_test;
BEGIN
    RAISE NOTICE '%: %', v_bug_test, (v_bug_test IS NULL);
    RAISE NOTICE '%: %', v_bug_test, (v_bug_test IS NOT NULL);
    RAISE NOTICE '%: %', v_bug_test, (NOT v_bug_test IS NULL);

    SELECT *
    INTO v_bug_test
    FROM bug_test
    WHERE id = 1;

    RAISE NOTICE '%: %', v_bug_test, (v_bug_test IS NULL);
    RAISE NOTICE '%: %', v_bug_test, (v_bug_test IS NOT NULL);
    RAISE NOTICE '%: %', v_bug_test, (NOT v_bug_test IS NULL);
END
$$;

DROP TABLE bug_test;

дает следующий вывод:

(,): t
(,): f
(,): f
(1,): f
(1,): f ???
(1,): t

в то время как я ожидал бы получить этот вывод:

(,): t
(,): f
(,): f
(1,): f
(1,): t <<<
(1,): t
индиго
источник
1
Вы рассматриваете тот факт, что вы на самом деле проверяете всю запись на NULL? (Вы
Joanolo
@joanolo Да. Я изменил код, чтобы проверить его idв своей реальной базе кода, но только после нескольких часов поиска проблемы.
Анил
1
Мне кажется, что rec_variable IS NOT NULLпроверяет, все ли столбцы НЕ NULL, в то время rec_variable IS NULLкак проверяет, все ли столбцы NULL. Отсюда NOT rec_variable IS NULLдает то, что я ожидал - ответ на вопрос «есть ли что-нибудь внутри?».
Анил

Ответы:

17

Вы должны различать две ситуации: вы сравниваете одну СТОЛБЦУ с NULL или сравниваете всю строку (RECORD) с NULL.

Рассмотрим следующий запрос:

SELECT
    id, 
    txt, 
    txt     IS NULL AS txt_is_null, 
    NOT txt IS NULL AS not_txt_is_null, 
    txt IS NOT NULL AS txt_is_not_null
FROM
    (VALUES
        (1::integer, NULL::text)
    ) 
    AS x(id, txt) ;

Вы получаете это:

+----+-----+-------------+-----------------+-----------------+
| id | txt | txt_is_null | not_txt_is_null | txt_is_not_null | 
+----+-----+-------------+-----------------+-----------------+
|  1 |     | t           | f               | f               | 
+----+-----+-------------+-----------------+-----------------+

Это, я думаю, то, что вы и я ожидали бы. Вы проверяете одну КОЛОННУ против NULL и получаете «txt IS NOT NULL» и «NOT txt IS NULL» эквивалентны.

Однако, если вы делаете другую проверку:

SELECT
    id, 
    txt, 
    x       IS NULL AS x_is_null,
    NOT x   IS NULL AS not_x_is_null,
    x   IS NOT NULL AS x_is_not_null
FROM
    (VALUES
        (1, NULL)
    ) 
    AS x(id, txt) ;

Тогда вы получите

+----+-----+-----------+---------------+---------------+
| id | txt | x_is_null | not_x_is_null | x_is_not_null |
+----+-----+-----------+---------------+---------------+
|  1 |     | f         | t             | f             |
+----+-----+-----------+---------------+---------------+

Это может быть удивительно. Одна вещь выглядит разумной (x IS NULL) и (NOT x IS NULL) противоположны друг другу. Другое дело (тот факт, что ни «x IS NULL», ни «x IS NOT NULL» не верны), выглядит странно.

Тем не менее, это то, что что должно произойти документации PostgreSQL :

Если выражение имеет строковое значение, то IS NULL имеет значение true, если само выражение строки равно нулю или когда все поля строки имеют значение null, тогда как IS NOT NULL равно true, если само выражение строки не равно нулю и все поля строки имеют значение ненулевой. Из-за этого поведения IS NULL и IS NOT NULL не всегда возвращают обратные результаты для выражений со значениями строк; в частности, строковое выражение, содержащее как нулевые, так и ненулевые поля, вернет false для обоих тестов. В некоторых случаях может быть предпочтительнее записать строку IS DISTINCT FROM NULL или строку NOT DISTINCT FROM NULL, которая просто проверит, является ли значение строки в целом нулевым, без каких-либо дополнительных тестов для полей строки.

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

joanolo
источник
Да, объяснение имеет смысл и соответствует результатам экспериментов, которые я проводил с момента публикации этого сообщения. Почему я сравнил всю переменную записи, потому что мой фон не на языках SQL, где это довольно распространено. Что касается вариантов использования, я считаю, что это удобно, когда нужно проверить, заполнены ли все поля в переменной записи (rec IS NOT NULL), вместо того, чтобы делать это поле за полем.
Анил
1
@Anil: Точно такой случай использования, о котором вы упоминали, всплывал раньше: stackoverflow.com/questions/21021102/…
Erwin Brandstetter