In PostgreSQL is er meestal een vrij klein verschil bij redelijke lijstlengtes, hoewel IN
is conceptueel veel schoner. Heel lang AND ... <> ...
lijsten en erg lang NOT IN
lijsten presteren beide verschrikkelijk, met AND
veel erger dan NOT IN
.
In beide gevallen, als ze lang genoeg zijn om zelfs maar de vraag te stellen, zou je in plaats daarvan een anti-deelname- of subquery-uitsluitingstest moeten doen over een waardelijst.
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);
of:
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;
(Op moderne Pg-versies zullen beide toch hetzelfde zoekplan produceren).
Als de lijst met waarden lang genoeg is (vele tienduizenden items), kan het ontleden van query's aanzienlijke kosten met zich meebrengen. Op dit punt zou u moeten overwegen om een TEMPORARY
. te maken tabel, COPY
door de gegevens erin uit te sluiten, er mogelijk een index op te maken en vervolgens een van de bovenstaande benaderingen op de tijdelijke tabel te gebruiken in plaats van de 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;
waar exclude
is de lijst met waarden die moet worden weggelaten.
Vervolgens vergelijk ik de volgende benaderingen op dezelfde gegevens met alle resultaten in milliseconden:
NOT IN
lijst:3424.596AND ...
lijst:80173.823VALUES
op basis vanJOIN
uitsluiting:20.727VALUES
gebaseerde subquery-uitsluiting:20.495- Tabelgebaseerd
JOIN
, geen index op ex-lijst:25.183 - Gebaseerd op subquerytabel, geen index op ex-lijst:23.985
... waardoor de op CTE gebaseerde aanpak meer dan drieduizend keer sneller is dan de AND
lijst en 130 keer sneller dan de NOT IN
lijst.
Code hier:https://gist.github.com/ringerc/5755247 (scherm uw ogen af, gij die deze link volgt).
Voor deze datasetgrootte maakte het geen verschil om een index toe te voegen aan de uitsluitingslijst.
Opmerkingen:
IN
lijst gegenereerd metSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
lijst gegenereerd metSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Subquery en op joins gebaseerde tabeluitsluiting waren vrijwel hetzelfde bij herhaalde runs.
- Onderzoek van het plan toont aan dat Pg
NOT IN
vertaalt naar<> ALL
Dus... je kunt zien dat er een echt enorme . is kloof tussen beide IN
en AND
lijsten versus het doen van een goede join. Wat me verbaasde was hoe snel ik het deed met een CTE met een VALUES
lijst was ... bezig met het ontleden van de VALUES
lijst kostte bijna geen tijd, deed hetzelfde of iets sneller dan de tafelbenadering in de meeste tests.
Het zou leuk zijn als PostgreSQL automatisch een belachelijk lange IN
. zou herkennen clausule of keten van soortgelijke AND
voorwaarden en schakel over naar een slimmere aanpak, zoals een hash-join doen of er impliciet een CTE-node van maken. Op dit moment weet het niet hoe het dat moet doen.
Zie ook:
- deze handige blogpost die Magnus Hagander over het onderwerp schreef