(x NIE JEST NULL) vs (NIE x IS NULL) w PostgreSQL

16

Dlaczego x IS NOT NULLnie jest równy NOT x IS NULL?

Ten kod:

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;

daje następujący wynik:

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

podczas gdy spodziewałbym się uzyskać ten wynik:

(,): t
(,): f
(,): f
(1,): f
(1,): t <<<
(1,): t
Indygowiec
źródło
1
Czy bierzesz pod uwagę fakt, że faktycznie sprawdzasz cały rekord pod kątem wartości NULL. (Jesteś
joanolo,
@joanolo Tak. Zmieniłem kod, aby sprawdzić idw mojej prawdziwej bazie kodu, ale dopiero po kilku godzinach szukania problemu.
Anil
1
Wydaje mi się, że rec_variable IS NOT NULLsprawdza, czy wszystkie kolumny NIE są NULL, podczas gdy rec_variable IS NULLsprawdza, czy wszystkie kolumny są NULL. Stąd NOT rec_variable IS NULLdaje to, czego się spodziewałem - odpowiedź na pytanie „czy coś jest w środku?”.
Anil

Odpowiedzi:

17

Musisz rozróżnić dwie sytuacje: porównujesz jedną KOLUMNĘ z NULL lub porównujesz cały WIERSZ (NAGRYWANIE) z NULL.

Rozważ następujące zapytanie:

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) ;

Dostajesz to:

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

Myślę, że tego właśnie spodziewalibyśmy się. Sprawdzasz jedną KOLUMNĘ pod kątem NULL i otrzymujesz „txt IS NOT NULL” i „NOT txt IS NULL” są równoważne.

Jeśli jednak wykonasz inną kontrolę:

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) ;

To dostajesz

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

To może być zaskakujące. Jedna rzecz wygląda rozsądnie (x IS NULL), a (NOT x IS NULL) jest przeciwna do siebie. Inna sprawa (fakt, że ani „x IS NULL”, ani „x IS NOT NULL” nie są prawdziwe), wygląda dziwnie.

Jednak tak mówi dokumentacja PostgreSQL , która powinna się zdarzyć:

Jeśli wyrażenie ma wartość wiersza, wówczas IS NULL ma wartość true, gdy samo wyrażenie wiersza ma wartość NULL lub gdy wszystkie pola wiersza są puste, natomiast IS NOT NULL jest prawdziwe, gdy samo wyrażenie wiersza ma wartość inną niż null, a wszystkie pola wiersza są różna od zera. Z powodu tego zachowania IS NULL i IS NOT NULL nie zawsze zwracają odwrotne wyniki dla wyrażeń o wartościach wierszy; w szczególności wyrażenie o wartościach wierszy, które zawiera zarówno pola puste, jak i niepuste, zwróci wartość false dla obu testów. W niektórych przypadkach może być wskazane zapisanie wiersza IS DISTINCT FROM NULL lub row IS NOT DISTINCT FROM NULL, który po prostu sprawdzi, czy całkowita wartość wiersza jest null bez dodatkowych testów na polach wiersza.

Muszę wyznać, że nie sądzę, żebym kiedykolwiek używał porównania wartości równej zeru z wartością zerową, ale sądzę, że jeśli istnieje taka możliwość, może istnieć jakiś przypadek użycia. W każdym razie nie sądzę, żeby to było wspólne.

joanolo
źródło
Tak, wyjaśnienie ma sens i pasuje do wyników eksperymentów, które przeprowadziłem od czasu opublikowania tego. Dlaczego porównałem całą zmienną rekordu, ponieważ moje tło jest w językach innych niż SQL, gdzie jest to dość powszechne. Jeśli chodzi o przypadki użycia, myślę, że jest to przydatne, gdy chce się sprawdzić, czy wszystkie pola w zmiennej rekordu są wypełnione (rec IS NOT NULL), zamiast robić to pole po polu.
Anil,
1
@Anil: Dokładnie wspomniany przypadek użycia pojawił się wcześniej: stackoverflow.com/questions/21021102/...
Erwin Brandstetter,