I PostgreSQL är det vanligtvis en ganska liten skillnad vid rimliga listlängder, även om IN
är mycket renare begreppsmässigt. Mycket lång AND ... <> ...
listor och mycket långa NOT IN
Båda listorna presterar fruktansvärt, med AND
mycket värre än NOT IN
.
I båda fallen, om de är tillräckligt långa för att du ens ska kunna ställa frågan, bör du istället göra ett anti-join eller subquery exkluderingstest över en värdelista.
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);
eller:
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;
(På moderna Pg-versioner kommer båda att producera samma frågeplan ändå).
Om värdelistan är tillräckligt lång (många tiotusentals artiklar) kan frågeanalys börja ha en betydande kostnad. Vid det här laget bör du överväga att skapa en TEMPORARY
tabell, COPY
inkludera data för att utesluta i den, eventuellt skapa ett index på den, och sedan använda en av ovanstående metoder på temptabellen istället för 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;
där exclude
är listan över värden som ska utelämnas.
Jag jämför sedan följande tillvägagångssätt på samma data med alla resultat i millisekunder:
NOT IN
lista:3424.596AND ...
lista:80173.823VALUES
baseradJOIN
uteslutning:20,727VALUES
baserad uteslutning av underfråga:20,495- Tabellbaserad
JOIN
, inget index på ex-listan:25.183 - Baserad på delfråga, inget index på ex-listan:23.985
... vilket gör det CTE-baserade tillvägagångssättet över tre tusen gånger snabbare än AND
lista och 130 gånger snabbare än NOT IN
lista.
Koda här:https://gist.github.com/ringerc/5755247 (skydda ögonen, ni som följer denna länk).
För denna datamängdsstorlek gjorde det ingen skillnad att lägga till ett index på uteslutningslistan.
Anmärkningar:
IN
lista genererad medSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
lista genererad medSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Undersökning och kopplingsbaserad tabelluteslutning var i stort sett desamma vid upprepade körningar.
- Undersökning av planen visar att Pg översätter
NOT IN
till<> ALL
Så... du kan se att det finns en verkligt stor gap mellan båda IN
och AND
listor kontra att göra en ordentlig join. Det som förvånade mig var hur snabbt man gör det med en CTE med en VALUES
listan ... analyserade VALUES
listan tog nästan ingen tid alls, presterade samma eller något snabbare än tabellmetoden i de flesta tester.
Det skulle vara trevligt om PostgreSQL automatiskt kunde känna igen en absurt lång IN
sats eller kedja av liknande AND
villkor och byt till ett smartare tillvägagångssätt som att göra en hashad join eller implicit omvandla den till en CTE-nod. Just nu vet den inte hur man gör det.
Se även:
- det här praktiska blogginlägget skrev Magnus Hagander om ämnet