PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

SQL:Wenn es um NOT IN und NOT EQUAL TO geht, was ist effizienter und warum?

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.596
  • AND ... Liste:80173.823
  • VALUES basierend auf JOIN Ausschluss:20.727
  • VALUES 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 mit SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • AND Liste generiert mit SELECT 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