Vamos ao Problema:
O moido dessa vez é saber pq o NOT IN não estava funcionando corretamente em alguns casos, no PG 9.0, seria um bug ou banco estava mentindo ?
A experiencia tem me mostrado que o banco não mente, o irônico é dizer isso no dia primeiro de abril, mas, cito a fato de que SQL tem base matemática na teoria dos conjuntos e algebra relacional, e dizer isso de um o banco SGBD Relacional em particular o Postgres, um projeto de muitos anos; é dá murro em ponta de faca.
Apresentando o desafio...
--Problema exposto
DROP TABLE IF EXISTS tb1;
CREATE TEMP TABLE tb1
(
id_tb1 integer PRIMARY KEY
, descricao varchar(28) NOT NULL
);
DROP TABLE IF EXISTS tb2;
CREATE TEMP TABLE tb2
(
id_tb2 integer PRIMARY KEY
, descricao varchar(28) NOT NULL
, id_tb1 integer NULL
, CONSTRAINT fk_tb2_id_tb1 FOREIGN KEY (id_tb1) REFERENCES tb1 (id_tb1)
);
INSERT INTO tb1 (id_tb1, descricao) VALUES (1,'TESTE1');
INSERT INTO tb1 (id_tb1, descricao) VALUES (2,'TESTE2');
INSERT INTO tb1 (id_tb1, descricao) VALUES (3,'TESTE3');
INSERT INTO tb1 (id_tb1, descricao) VALUES (4,'TESTE4');
INSERT INTO tb1 (id_tb1, descricao) VALUES (5,'TESTE5');
INSERT INTO tb1 (id_tb1, descricao) VALUES (6,'TESTE6');
INSERT INTO tb1 (id_tb1, descricao) VALUES (7,'TESTE7');
SELECT * FROM tb1;
/* Resultado
id_tb1 | descricao
--------+-----------
1 | TESTE1
2 | TESTE2
3 | TESTE3
4 | TESTE4
5 | TESTE5
6 | TESTE6
7 | TESTE7
(7 rows)
*/
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (8,'TESTE1',1);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (9,'TESTE2',2);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (10,'TESTE3',3);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (11,'TESTE4',4);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (12,'TESTE5',5);
INSERT INTO tb2 (id_tb2, descricao, id_tb1) VALUES (13,'TESTE7',NULL);
SELECT * FROM tb2;
/* Resultado
id_tb2 | descricao | id_tb1
--------+-----------+--------
8 | TESTE1 | 1
9 | TESTE2 | 2
10 | TESTE3 | 3
11 | TESTE4 | 4
12 | TESTE5 | 5
13 | TESTE7 | NULL
(6 rows)
*/
--Query 1
SELECT *
FROM tb1
WHERE id_tb1 IN
(
SELECT id_tb1
FROM tb2
)
;
/* Resultado
id_tb1 | descricao
--------+-----------
1 | TESTE1
2 | TESTE2
3 | TESTE3
4 | TESTE4
5 | TESTE5
(5 rows)
*/
--Query 2 (Seria um bug do Postgres? Retornou nada!)
SELECT *
FROM tb1
WHERE id_tb1 NOT IN
(
SELECT id_tb1
FROM tb2
)
;
--Nada, aparentemente o correto seria trazer os registros especificados, será o que SGBD (o banco) Postgres está mentindo ?
/* Resultado
id_tb1 | descricao
--------+-----------
(0 rows)
*/
--Análise 1, uma consulta equivalente com not in, apenas para testar
SELECT *
FROM tb1
WHERE id_tb1 <> ALL
(
SELECT id_tb1
FROM tb2
)
;
--Idem ao problema apresentado na Query 2
/* Resultado
id_tb1 | descricao
--------+-----------
(0 rows)
*/
--Análise 2, o que está havendo?
SELECT
DISTINCT *
FROM tb2
ORDER BY 1
;
--Existe um campo NULL no final do resultado. Será que esse NULL se propaga no NOT IN, mas pq não no IN ?
/* Resultado
id_tb2 | descricao | id_tb1
--------+-----------+--------
8 | TESTE1 | 1
9 | TESTE2 | 2
10 | TESTE3 | 3
11 | TESTE4 | 4
12 | TESTE5 | 5
13 | TESTE7 | NULL
(6 rows)
*/
-- Exemplo 1, Solução
SELECT *
FROM tb1
WHERE id_tb1 NOT IN
(
SELECT id_tb1
FROM tb2
WHERE id_tb1 IS NOT NULL
)
;
--Com a inclusão da condicional removendo os nulos, deu certo, pq?, Qual a explicação disso?
/* Resultado
id_tb1 | descricao
--------+-----------
6 | TESTE6
7 | TESTE7
(2 rows)
*/
Conclusão
No Postgres o null se propaga, isto é, qualquer coisa concatenada com NULL se torna NULL exemplo:
SELECT 'NULL' || NULL AS teste1; SELECT 'Isto ' || 'é' ||' apenas '|| ' um ' || NULL || ' teste .... ' AS teste2;
Esse mesmo conceito se aplica também ao NOT IN.
Então:
Deve ser observado que se o resultado da expressão à esquerda for nulo,
ou se não houver nenhum valor igual à direita e uma das linhas à direita tiver o valor nulo,
o resultado da construção NOT IN será nulo, e não verdade.
Isto está de acordo com as regras normais do SQL para combinações booleanas de valores nulos. [1]
Idem na versões demais versões do Postgres e a ultima 9.0 [2]
Concluindo isso não é um bug mas sim um comportamento padrão do NOT IN, que está em conformidade com as normas SQL/ANSI,
lembrando que uso de NOT IN não é recomendável quando se pensa em desempenho.
E finalmente o banco (SGBDR) não mente! :)
[1]
http://pgdocptbr.sourceforge.net/pg80/functions-subquery.html#FUNCTIONS-NOT-IN-SUBQ
[2]
http://www.postgresql.org/docs/9.0/static/functions-comparisons.html#AEN16842
Este comentário foi removido pelo autor.
ResponderExcluirValeu professor, Grande artigo. essa já está no Twitter.
ResponderExcluirUm abraço.
Gilvan Costa
Esse comportamento do NOT IN foi testado e também se aplica aos seguintes SGBDRs: MS SQL-Server 2005, MySQL 5.5 (InnoDB) e Oracle 11G R2
ResponderExcluir