In PostgreSQL gibt es normalerweise einen ziemlich kleinen Unterschied bei vernünftigen Listenlängen, obwohl IN
ist konzeptionell viel sauberer. Sehr langes AND ... <> ...
Listen und sehr lange NOT IN
Listen funktionieren beide schrecklich, mit AND
viel schlimmer als NOT IN
.
In beiden Fällen sollten Sie, wenn sie lang genug sind, um die Frage überhaupt zu stellen, stattdessen einen Anti-Join- oder Unterabfrage-Ausschlusstest über eine Werteliste durchführen.
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);
oder:
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;
(Auf modernen Pg-Versionen erzeugen beide sowieso denselben Abfrageplan).
Wenn die Werteliste lang genug ist (viele zehntausend Elemente), kann das Analysieren von Abfragen erhebliche Kosten verursachen. An dieser Stelle sollten Sie erwägen, einen TEMPORARY
zu erstellen Tabelle, COPY
die Daten, die ausgeschlossen werden sollen, darin speichern, möglicherweise einen Index darauf erstellen und dann einen der oben genannten Ansätze für die temporäre Tabelle anstelle des CTE verwenden.
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;
wobei exclude
ist die Liste der auszulassenden Werte.
Ich vergleiche dann die folgenden Ansätze auf denselben Daten mit allen Ergebnissen in Millisekunden:
NOT IN
Liste:3424.596AND ...
Liste:80173.823VALUES
basierend aufJOIN
Ausschluss:20.727VALUES
basierender Unterabfrageausschluss:20.495- Tabellenbasierter
JOIN
, kein Index auf Ex-Liste:25.183 - Unterabfragetabellenbasiert, kein Index auf Ex-Liste:23.985
... was den CTE-basierten Ansatz über dreitausend Mal schneller macht als das AND
Liste und 130 mal schneller als NOT IN
Liste.
Code hier:https://gist.github.com/ringerc/5755247 (schirmt eure Augen ab, ihr, die diesem Link folgt).
Für diese Datensatzgröße machte das Hinzufügen eines Indexes zur Ausschlussliste keinen Unterschied.
Hinweise:
IN
Liste generiert mitSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
Liste generiert mitSELECT string_agg(item::text, ' AND item <> ') from exclude;
)- Unterabfrage- und Join-basierter Tabellenausschluss waren bei wiederholten Durchläufen ziemlich gleich.
- Die Untersuchung des Plans zeigt, dass Pg
NOT IN
übersetzt zu<> ALL
Sie können also sehen, dass es wirklich riesig ist Lücke zwischen den beiden IN
und AND
Listen im Vergleich zu einer ordnungsgemäßen Verknüpfung. Was mich überrascht hat, war, wie schnell es mit einem CTE unter Verwendung eines VALUES
geht list war ... das Parsen der VALUES
list nahm fast keine Zeit in Anspruch und war genauso oder etwas schneller als den Tabellenansatz in den meisten Tests.
Es wäre schön, wenn PostgreSQL automatisch einen absurd langen IN
erkennen könnte Klausel oder Kette ähnlicher AND
Bedingungen und wechseln Sie zu einem intelligenteren Ansatz, wie z. B. einem Hash-Join oder einer impliziten Umwandlung in einen CTE-Knoten. Im Moment weiß es nicht, wie das geht.
Siehe auch:
- dieser praktische Blogbeitrag, den Magnus Hagander zu diesem Thema geschrieben hat