PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

SQL :En ce qui concerne NOT IN et NOT EQUAL TO, qu'est-ce qui est le plus efficace et pourquoi ?

Dans PostgreSQL, il y a généralement une assez petite différence à des longueurs de liste raisonnables, bien que IN est beaucoup plus propre conceptuellement. AND ... <> ... très long listes et très longues NOT IN les listes fonctionnent très bien, avec AND bien pire que NOT IN .

Dans les deux cas, s'ils sont suffisamment longs pour que vous posiez même la question, vous devriez plutôt effectuer un test d'exclusion de sous-requête ou d'anti-jointure sur une liste de valeurs.

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

ou :

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;

(Sur les versions modernes de Pg, les deux produiront de toute façon le même plan de requête).

Si la liste de valeurs est suffisamment longue (plusieurs dizaines de milliers d'éléments), l'analyse des requêtes peut commencer à avoir un coût important. À ce stade, vous devriez envisager de créer un TEMPORARY tableau, COPY en y excluant les données, en créant éventuellement un index dessus, puis en utilisant l'une des approches ci-dessus sur la table temporaire au lieu du CTE.

Démo :

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;

exclude est la liste des valeurs à omettre.

Je compare ensuite les approches suivantes sur les mêmes données avec tous les résultats en millisecondes :

  • NOT IN liste :3424.596
  • AND ... liste :80173.823
  • VALUES basé sur JOIN exclusion :20.727
  • VALUES exclusion de sous-requête basée :20.495
  • JOIN basé sur une table , pas d'index sur l'ex-liste :25.183
  • Basé sur une table de sous-requête, pas d'index sur l'ex-liste :23.985

... rendant l'approche basée sur le CTE plus de trois mille fois plus rapide que le AND list et 130 fois plus rapide que le NOT IN liste.

Codez ici :https://gist.github.com/ringerc/5755247 (protégez vos yeux, vous qui suivez ce lien).

Pour cette taille d'ensemble de données, l'ajout d'un index sur la liste d'exclusion n'a fait aucune différence.

Remarques :

  • IN liste générée avec SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • AND liste générée avec SELECT string_agg(item::text, ' AND item <> ') from exclude; )
  • L'exclusion de table basée sur les sous-requêtes et les jointures était sensiblement la même lors des exécutions répétées.
  • L'examen du plan montre que Pg traduit NOT IN à <> ALL

Donc... vous pouvez voir qu'il y a vraiment énorme écart entre les deux IN et AND listes vs faire une bonne jointure. Ce qui m'a surpris, c'est à quelle vitesse le faire avec un CTE en utilisant un VALUES la liste était ... en train d'analyser les VALUES la liste n'a pris presque aucun temps, effectuant la même chose ou légèrement plus vite que l'approche par tableau dans la plupart des tests.

Ce serait bien si PostgreSQL pouvait reconnaître automatiquement un IN ridiculement long clause ou chaîne de AND similaires conditions et passer à une approche plus intelligente comme faire une jointure hachée ou la transformer implicitement en un nœud CTE. Pour le moment, il ne sait pas comment faire cela.

Voir aussi :

  • cet article de blog pratique que Magnus Hagander a écrit sur le sujet