PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

SQL:quando si tratta di NOT IN e NOT EQUAL TO, qual è più efficiente e perché?

In PostgreSQL di solito c'è una differenza abbastanza piccola a lunghezze di elenco ragionevoli, sebbene IN è molto più pulito concettualmente. AND ... <> ... molto lungo liste e molto lungo NOT IN le liste hanno entrambe prestazioni terribili, con AND molto peggio di NOT IN .

In entrambi i casi, se sono abbastanza lunghi da consentirti anche di porre la domanda, dovresti invece eseguire un test di esclusione di sottoquery o anti-unione su un elenco di valori.

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);

oppure:

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;

(Sulle versioni moderne di Pg, entrambe produrranno comunque lo stesso piano di query).

Se l'elenco valori è sufficientemente lungo (molte decine di migliaia di elementi), l'analisi delle query potrebbe iniziare ad avere un costo significativo. A questo punto dovresti considerare di creare un TEMPORARY tabella, COPY ing dei dati da escludere, eventualmente creando un indice su di esso, quindi utilizzando uno degli approcci precedenti sulla tabella temporanea anziché sul 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;

dove exclude è l'elenco dei valori da omettere.

Quindi confronto i seguenti approcci sugli stessi dati con tutti i risultati in millisecondi:

  • NOT IN elenco:3424.596
  • AND ... elenco:80173.823
  • VALUES basato su JOIN esclusione:20.727
  • VALUES esclusione di sottoquery basata:20,495
  • JOIN basato su tabelle , nessun indice nell'ex-elenco:25.183
  • Base tabella di sottoquery, nessun indice nell'ex-elenco:23.985

... rendendo l'approccio basato su CTE oltre tremila volte più veloce di AND list e 130 volte più veloce di NOT IN elenco.

Codice qui:https://gist.github.com/ringerc/5755247 (proteggetevi gli occhi, voi che seguite questo link).

Per questa dimensione del set di dati, l'aggiunta di un indice nell'elenco di esclusione non ha fatto alcuna differenza.

Note:

  • IN elenco generato con SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • AND elenco generato con SELECT string_agg(item::text, ' AND item <> ') from exclude; )
  • La subquery e l'esclusione di tabelle basate su join erano più o meno le stesse per esecuzioni ripetute.
  • L'esame del piano mostra che Pg traduce NOT IN a <> ALL

Quindi... puoi vedere che c'è davvero un enorme divario tra i due IN e AND liste vs fare un join corretto. Ciò che mi ha sorpreso è stata la velocità con cui è stato eseguito con un CTE utilizzando un VALUES list stava ... analizzando i VALUES list non ha richiesto quasi tempo, con prestazioni uguali o leggermente più veloci di l'approccio della tabella nella maggior parte dei test.

Sarebbe bello se PostgreSQL potesse riconoscere automaticamente un IN assurdamente lungo clausola o catena di AND simili condizioni e passare a un approccio più intelligente come eseguire un hash join o trasformarlo implicitamente in un nodo CTE. In questo momento non sa come farlo.

Vedi anche:

  • questo utile post sul blog che Magnus Hagander ha scritto sull'argomento