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;
où 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.596AND ...
liste :80173.823VALUES
basé surJOIN
exclusion :20.727VALUES
exclusion de sous-requête basée :20.495JOIN
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 avecSELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
AND
liste générée avecSELECT 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