In PostgreSQL di solito c'è una differenza abbastanza piccola a lunghezze di elenco ragionevoli, sebbene IN
è molto più pulito concettualmente. AND ... <> ...
molto lungo liste e molto lungo NOT IN
le liste hanno entrambe prestazioni terribili, con AND
molto peggio di NOT IN
.
In entrambi i casi, se sono abbastanza lunghi da consentirti anche di porre la domanda, dovresti invece eseguire un test di esclusione di sottoquery o anti-unione su un elenco di valori.
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);
oppure:
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;
(Sulle versioni moderne di Pg, entrambe produrranno comunque lo stesso piano di query).
Se l'elenco valori è sufficientemente lungo (molte decine di migliaia di elementi), l'analisi delle query potrebbe iniziare ad avere un costo significativo. A questo punto dovresti considerare di creare un TEMPORARY
tabella, COPY
ing dei dati da escludere, eventualmente creando un indice su di esso, quindi utilizzando uno degli approcci precedenti sulla tabella temporanea anziché sul CTE.
Demo:
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;
dove exclude
è l'elenco dei valori da omettere.
Quindi confronto i seguenti approcci sugli stessi dati con tutti i risultati in millisecondi:
NOT IN
elenco:3424.596AND ...
elenco:80173.823VALUES
basato suJOIN
esclusione:20.727VALUES
esclusione di sottoquery basata:20,495JOIN
basato su tabelle , nessun indice nell'ex-elenco:25.183- Base tabella di sottoquery, nessun indice nell'ex-elenco:23.985
... rendendo l'approccio basato su CTE oltre tremila volte più veloce di AND
list e 130 volte più veloce di NOT IN
elenco.
Codice qui:https://gist.github.com/ringerc/5755247 (proteggetevi gli occhi, voi che seguite questo link).
Per questa dimensione del set di dati, l'aggiunta di un indice nell'elenco di esclusione non ha fatto alcuna differenza.
Note:
IN
elenco generato conSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
elenco generato conSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- La subquery e l'esclusione di tabelle basate su join erano più o meno le stesse per esecuzioni ripetute.
- L'esame del piano mostra che Pg traduce
NOT IN
a<> ALL
Quindi... puoi vedere che c'è davvero un enorme divario tra i due IN
e AND
liste vs fare un join corretto. Ciò che mi ha sorpreso è stata la velocità con cui è stato eseguito con un CTE utilizzando un VALUES
list stava ... analizzando i VALUES
list non ha richiesto quasi tempo, con prestazioni uguali o leggermente più veloci di l'approccio della tabella nella maggior parte dei test.
Sarebbe bello se PostgreSQL potesse riconoscere automaticamente un IN
assurdamente lungo clausola o catena di AND
simili condizioni e passare a un approccio più intelligente come eseguire un hash join o trasformarlo implicitamente in un nodo CTE. In questo momento non sa come farlo.
Vedi anche:
- questo utile post sul blog che Magnus Hagander ha scritto sull'argomento