2013-06-11 4 views
7

Lassen Sie uns sagen, dass ich eine Menge von Elementen haben:SQL: Wenn es um NOT IN und NOT EQUAL TO geht, was ist effizienter und warum?

  • element1
  • Element2
  • Item3
  • Item4
  • Item5

Eine Abfrage kann auf zwei Arten aufgebaut sein. Erstens:

SELECT * 
FROM TABLE 
WHERE ITEM NOT IN ('item1', 'item2', 'item3', 'item4','item5') 

Oder es kann geschrieben werden als:

SELECT * 
FROM TABLE 
WHERE ITEM != 'item1' 
    AND ITEM != 'item2' 
    AND ITEM != 'item3' 
    AND ITEM != 'item4' 
    AND ITEM != 'item5' 
  1. , die effizienter ist und warum?
  2. An welchem ​​Punkt wird einer effizienter als der andere? Mit anderen Worten, was wäre, wenn es 500 Gegenstände gäbe?

Meine Frage bezieht sich speziell auf PostgreSQL.

+2

Kleinen nitpick zu gehen: (?)! Der Standard-SQL-Operator für "nicht gleich" ist '<>' obwohl alle DBMS scheinen der Nicht-Standard 'zu unterstützen = 'genauso gut. –

+0

Wenn Sie "effizienter" sagen, meinen Sie "schneller"? "Effizient" kann sich auf viele andere Dinge als nur die Ausführungsgeschwindigkeit beziehen. –

Antwort

31

In PostgreSQL gibt es normalerweise einen recht kleinen Unterschied bei vernünftigen Listenlängen, obwohl IN konzeptionell viel sauberer ist. Sehr lange AND ... <> ... Listen und sehr lange NOT IN Listen führen beide schrecklich, mit AND viel schlechter als NOT IN.

In beiden Fällen, wenn sie lang genug sind, dass Sie sogar die Frage stellen, sollten Sie stattdessen einen Anti-Join oder Unterabfrage-Ausschluss-Test ü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 moderne Pg Versionen beide den gleichen Abfrage-Plan ohnehin produzieren).

Wenn die Werteliste lang genug ist (viele Zehntausende von Elementen), dann kann das Parsen von Abfragen erhebliche Kosten verursachen. An diesem Punkt sollten Sie in Betracht ziehen, eine TEMPORARY Tabelle zu erstellen, COPY die auszuschließenden Daten, möglicherweise einen Index dafür zu erstellen, und dann einen der oben genannten Ansätze für die temporäre Tabelle anstelle des CTE zu 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; 

wo exclude die Liste der Werte ist zu unterlassen.

Ich vergleiche dann die folgenden Ansätze auf den gleichen Daten mit allen Ergebnissen in Millisekunden:

  • NOT IN Liste: 3424,596
  • AND ... Liste: 80173,823
  • VALUES basiert JOIN Ausschluss: 20.727
  • VALUES basierte Unterabfrage Ausschluss: 20,495
  • Tabellenbasierte JOIN, kein Index auf ex-Liste: 25,183
  • Subquery Tabelle basiert, kein Index auf ex-Liste: 23,985

... die CTE-basierten Ansatz über dreitausend Mal schneller als die AND Liste und 130-mal schneller als die NOT IN Liste machen.

Code hier: https://gist.github.com/ringerc/5755247 (Schild deine Augen, ihr folgt diesem Link).

Für diese Datensatzgröße machte das Hinzufügen eines Indexes zur Ausschlussliste keinen Unterschied.

Hinweise:

  • IN Liste erstellt 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;)
  • Subquery und basierte Tabelle Ausschluss verbinden waren sehr ähnlich verläuft über wiederholt.
  • Prüfung des Plans zeigt, dass Pg So NOT IN zu <> ALL

übersetzt ... können Sie sehen, dass ein wirklich riesige Lücke zwischen den beiden IN und AND Listen gibt es eine richtige beitreten vs tun. Was mich überraschte war, wie schnell es mit einem CTE mit einer VALUES Liste war ... Parsing die VALUES Liste dauerte fast keine Zeit, Durchführung der gleichen oder etwas schneller als die Tabelle Ansatz in den meisten Tests.

Es wäre schön, wenn PostgreSQL automatisch eine grotesk lange IN Klausel oder eine Kette von ähnlichen AND Bedingungen und wie macht einen Hash zu einem intelligenteren Ansatz wechselt erkennen konnte beitreten oder implizit in einen CTE Knoten drehen. Im Moment weiß es nicht, wie das geht. auch

Siehe:

+0

Es gibt keine spezielle Einschränkung für postgresql, aber einige Datenbanken haben eine Grenze dafür, wie groß der 'IN'-Operator erhalten kann, was +1 für das 'AND ... <> ...' Konstrukt ergibt. –

+3

@BurhanKhalid Die Verwendung von verketteten 'UND ... <> ...' macht dem Parser und Planer das Leben schwer. Es gab kürzlich Mailinglisten-Berichte von Abfrageplanung, die * Minuten * für Abfragen mit Zehntausenden solcher Klauseln, wie sie von einigen schrecklichen ORMs generiert wurden, benötigten. –

+1

Verdammt ... _ * Minuten * _ für _Planung_ ?! –

8

ich nicht zustimmen etwas mit dem Original akzeptierte Antwort von @Jayram.

Nicht zuletzt ist der Link für SQL Server und widerspricht vielen anderen Artikeln und Antworten. Außerdem gibt es keine Indizes für die Beispieltabelle.

Normalerweise für Unterabfrage SQL-Konstrukten

  • <> (oder !=) ein skalaren Vergleich
  • NOT IN eine ist links anti-Semi-Join relationaler Operator

Einfacher ausgedrückt

  • NOT IN wird eine Form der JOIN, dass ein Index verwenden können
  • != ist oft nicht-Sargable und ein Index

Dies wurde auf dba.se diskutiert nicht verwendet werden können (außer PostgreSQL!): "The use of NOT logic in relation to indexes" . Für PostgreSQL, dann diese explainextended article erklärt das interne mehr (aber nicht für eine Liste von Konstanten mit NOT IN leider).

Wie auch immer, für eine Liste von Konstanten würde ich normalerweise NOT IN vor <> verwenden, weil es einfacher zu lesen ist und wegen dem, was @CraigRinger erklärt.

Für eine Unterabfrage, NOT EXISTS ist der Weg

+0

Keine von beiden ist richtig für PostgreSQL; Es kann manchmal einen Index für '<>' verwenden, wobei Tabellenstatistiken die Theorie unterstützen, dass der ausgeschlossene Wert überwiegend üblich ist, und AFAIK kann keinen Index für eine 'NOT IN'-Liste verwenden, die intern in' id <übersetzt wird > ALLE'. –

+0

Mein 2. Link sagt, es kann nicht anders als andere RDBMS. Wie auch immer, ich würde NOT EXISTS verwenden – gbn

+0

Stimme völlig zu; 'NOT EXISTS' oder ein Anti-Join auf der Datenliste ist der vernünftige Weg. PostgreSQL macht "nicht vorhanden" ohnehin zu einem Anti-Join. –

Verwandte Themen