(x NÃO É NULL) vs (NÃO é NULL) no PostgreSQL

16

Por que x IS NOT NULLnão é igual a NOT x IS NULL?

Este código:

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;

dá a seguinte saída:

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

enquanto eu esperaria obter esta saída:

(,): t
(,): f
(,): f
(1,): f
(1,): t <<<
(1,): t
Anil
fonte
11
Você está considerando o fato de que está realmente verificando um registro inteiro contra o NULL. (Você está
joanolo 9/16/16
@joanolo Sim. Mudei o código para procurar idna minha base de código real, mas somente depois de passar algumas horas procurando por um problema.
Anil
11
Parece-me que rec_variable IS NOT NULLestá verificando se todas as colunas NÃO são NULL, enquanto rec_variable IS NULLestá verificando se todas as colunas são NULL. Daí, NOT rec_variable IS NULLdá o que eu esperava - uma resposta para a pergunta "existe algo dentro?".
Anil

Respostas:

17

Você precisa distinguir duas situações: você pode comparar uma COLUNA contra NULL ou comparar toda a ROW (RECORD) com NULL.

Considere a seguinte consulta:

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

Você obtém isto:

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

Acho que é isso que você e eu esperávamos. Você está verificando uma COLUNA em relação a NULL e obtém "txt NÃO É NULL" e "NÃO txt É NULL" são equivalentes.

No entanto, se você fizer uma verificação diferente:

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

Então você recebe

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

Isso pode ser surpreendente. Uma coisa parece razoável (x IS NULL) e (NOT x IS NULL) são o oposto uma da outra. A outra coisa (o fato de que nem "x É NULL" nem "x NÃO É NULL" é verdadeiro) parece estranho.

No entanto, é isso que a documentação do PostgreSQL diz que deve acontecer:

Se a expressão tiver valor de linha, IS NULL será verdadeiro quando a própria expressão de linha for nula ou quando todos os campos da linha forem nulos, enquanto IS NOT NULL for verdadeira quando a expressão de linha for não nula e todos os campos da linha forem não nulo. Devido a esse comportamento, IS NULL e IS NOT NULL nem sempre retornam resultados inversos para expressões com valor de linha; em particular, uma expressão com valor de linha que contém campos nulos e não nulos retornará false para ambos os testes. Em alguns casos, pode ser preferível escrever a linha IS DISTINCT FROM NULL ou a linha NÃO DISTINCT FROM NULL, que simplesmente verificará se o valor geral da linha é nulo sem nenhum teste adicional nos campos da linha.

Devo confessar que acho que nunca usei uma comparação com valor de linha contra nulo, mas acho que, se a possibilidade existe, pode haver algum caso de uso para ela. Eu não acho que seja comum, de qualquer maneira.

joanolo
fonte
Sim, a explicação faz sentido e corresponde aos resultados dos experimentos que fiz desde a publicação. O motivo pelo qual eu comparei toda a variável de registro é porque meu background é em linguagens não-SQL, onde isso é bastante comum. Em relação aos casos de uso, acho que isso é útil quando se deseja verificar se todos os campos em uma variável de registro estão preenchidos (rec IS NOT NULL), em vez de fazê-lo campo a campo.
Anil
11
@ Anil: Exatamente o caso de uso que você mencionou apareceu antes: stackoverflow.com/questions/21021102/…
Erwin Brandstetter