No PostgreSQL, geralmente há uma diferença bastante pequena em comprimentos de lista razoáveis, embora
IN
é muito mais limpo conceitualmente. Muito longo AND ... <> ...
listas e muito longas NOT IN
listas ambos têm um desempenho terrível, com AND
muito pior do que NOT IN
. Em ambos os casos, se eles forem longos o suficiente para você fazer a pergunta, você deve fazer um teste de exclusão de antijunção ou subconsulta em uma lista de valores.
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
WHERE NOT EXISTS(SELECT 1 FROM excluded e WHERE t.item = e.item);
ou:
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
LEFT OUTER JOIN excluded e ON (t.item = e.item)
WHERE e.item IS NULL;
(Nas versões modernas do Pg, ambos produzirão o mesmo plano de consulta de qualquer maneira).
Se a lista de valores for longa o suficiente (muitas dezenas de milhares de itens), a análise de consulta pode começar a ter um custo significativo. Neste ponto, você deve considerar a criação de um
TEMPORARY
tabela, COPY
ndo os dados a serem excluídos, possivelmente criando um índice nele e, em seguida, usando uma das abordagens acima na tabela temporária em vez do CTE. Demonstração:
CREATE UNLOGGED TABLE exclude_test(id integer primary key);
INSERT INTO exclude_test(id) SELECT generate_series(1,50000);
CREATE TABLE exclude AS SELECT x AS item FROM generate_series(1,40000,4) x;
onde
exclude
é a lista de valores a serem omitidos. Em seguida, comparo as seguintes abordagens nos mesmos dados com todos os resultados em milissegundos:
NOT IN
lista:3424.596AND ...
lista:80173.823VALUES
baseado emJOIN
exclusão:20.727VALUES
exclusão de subconsulta baseada:20.495-
JOIN
baseado em tabela , nenhum índice na ex-lista:25.183 - Com base em tabela de subconsulta, sem índice na ex-lista:23.985
... tornando a abordagem baseada em CTE mais de três mil vezes mais rápida que o
AND
list e 130 vezes mais rápido que o NOT IN
Lista. Código aqui:https://gist.github.com/ringerc/5755247 (proteja seus olhos, você que segue este link).
Para este tamanho de conjunto de dados, adicionar um índice na lista de exclusão não fez diferença.
Notas:
IN
lista gerada comSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
lista gerada comSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Exclusão de tabela baseada em subconsulta e junção foi praticamente a mesma em execuções repetidas.
- O exame do plano mostra que Pg traduz
NOT IN
para<> ALL
Então... você pode ver que há um enorme intervalo entre ambos
IN
e AND
listas vs fazer uma junção adequada. O que me surpreendeu foi a rapidez com que fazer isso com um CTE usando um VALUES
list estava... analisando os VALUES
list não levou quase nenhum tempo, executando o mesmo ou ligeiramente mais rápido que a abordagem de tabela na maioria dos testes. Seria bom se o PostgreSQL pudesse reconhecer automaticamente um
IN
absurdamente longo cláusula ou cadeia de AND
semelhante condições e mude para uma abordagem mais inteligente, como fazer uma junção com hash ou transformá-la implicitamente em um nó CTE. No momento não sabe como fazer isso. Veja também:
- esta útil postagem de blog que Magnus Hagander escreveu sobre o assunto