sql >> Database >  >> RDS >> PostgreSQL

SQL:Als het gaat om NOT IN en NOT EQUAL TO, wat is dan efficiënter en waarom?

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.596
  • AND ... lijst:80173.823
  • VALUES op basis van JOIN uitsluiting:20.727
  • VALUES 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 met SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • AND lijst gegenereerd met SELECT 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


  1. Hoe de mediaan te berekenen in MySQL

  2. Fix "INSERT heeft meer expressies dan doelkolommen" in PostgreSQL

  3. Oracle EXPAND_SQL_TEXT gebruiken

  4. Uitdagingsoplossingen voor generatorreeksen - deel 4