PostgreSQL - nombre maximum de paramètres dans la clause «IN»?

147

Dans Postgres, vous pouvez spécifier une clause IN, comme ceci:

SELECT * FROM user WHERE id IN (1000, 1001, 1002)

Quelqu'un sait-il quel est le nombre maximum de paramètres que vous pouvez transmettre à IN?

Alex Riley
la source

Réponses:

83

Selon le code source situé ici, à partir de la ligne 850, PostgreSQL ne limite pas explicitement le nombre d'arguments.

Voici un commentaire de code de la ligne 870:

/*
 * We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only
 * possible if the inputs are all scalars (no RowExprs) and there is a
 * suitable array type available.  If not, we fall back to a boolean
 * condition tree with multiple copies of the lefthand expression.
 * Also, any IN-list items that contain Vars are handled as separate
 * boolean conditions, because that gives the planner more scope for
 * optimization on such clauses.
 *
 * First step: transform all the inputs, and detect whether any are
 * RowExprs or contain Vars.
 */
Jordan S. Jones
la source
56

Ce n'est pas vraiment une réponse à la question actuelle, mais cela pourrait aussi aider d'autres.

Au moins, je peux dire qu'il existe une limite technique de 32767 valeurs (= Short.MAX_VALUE) transmissibles au backend PostgreSQL, en utilisant le pilote JDBC 9.1 de Posgresql.

Ceci est un test de "suppression de x où id dans (... 100k valeurs ...)" avec le pilote jdbc postgresql:

Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 100000
    at org.postgresql.core.PGStream.SendInteger2(PGStream.java:201)
nimai
la source
6
L'OP a posé des questions sur la limitation du moteur DB, mais à la recherche de la limitation JDBC, je suis venu ici et c'est ce que je voulais. Il y a donc une limitation, cependant, assez élevée.
9ilsdx 9rvj 0lo
36
explain select * from test where id in (values (1), (2));

PLAN DE DEMANDE

 Seq Scan on test  (cost=0.00..1.38 rows=2 width=208)
   Filter: (id = ANY ('{1,2}'::bigint[]))

Mais si essayez la 2ème requête:

explain select * from test where id = any (values (1), (2));

PLAN DE DEMANDE

Hash Semi Join  (cost=0.05..1.45 rows=2 width=208)
       Hash Cond: (test.id = "*VALUES*".column1)
       ->  Seq Scan on test  (cost=0.00..1.30 rows=30 width=208)
       ->  Hash  (cost=0.03..0.03 rows=2 width=4)
             ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=4)

Nous pouvons voir que postgres construit une table temporaire et la rejoindre

hacker13ua
la source
Mais ce que j'ai entendu dire que postgres-9.3 + semble être le même performant. datadoghq.com/blog/…
PiyusG
18

Il n'y a pas de limite au nombre d'éléments que vous passez à la clause IN. S'il y a plus d'éléments, il le considérera comme un tableau, puis pour chaque analyse dans la base de données, il vérifiera s'il est contenu dans le tableau ou non. Cette approche n'est pas si évolutive. Au lieu d'utiliser la clause IN, essayez d'utiliser INNER JOIN avec une table temporaire. Référez-vous à http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ pour plus d'informations. L'utilisation des échelles INNER JOIN ainsi que de l'optimiseur de requêtes peut utiliser la jointure par hachage et d'autres optimisations. Alors qu'avec la clause IN, l'optimiseur n'a aucun moyen d'optimiser la requête. J'ai remarqué une accélération d'au moins 2x avec ce changement.

Prasanthe Jayachandran
la source
2
Le lien auquel vous faites référence ne dit pas de quel SGBD il parle. Bien que je puisse confirmer que sur Oracle DB, l'utilisation de tables temporaires augmente considérablement les performances par rapport à l'utilisation de requêtes combinant ORet de INclauses en raison de la surcharge importante liée à l'analyse et à la planification de telles requêtes, je n'ai pas pu confirmer le problème avec Postgres 9.5, voir cette réponse .
blubb
17

En tant que personne plus expérimentée avec Oracle DB, j'étais également préoccupée par cette limite. J'ai effectué un test de performance pour une requête avec ~ 10'000 paramètres dans une IN-list, récupérant des nombres premiers jusqu'à 100'000 à partir d'une table avec les 100'000 premiers entiers en listant tous les nombres premiers comme paramètres de requête .

Mes résultats indiquent que vous n'avez pas à vous soucier de surcharger l'optimiseur de plan de requête ou d'obtenir des plans sans utilisation d'index , car cela transformera la requête pour l'utiliser = ANY({...}::integer[])là où elle peut exploiter les indices comme prévu:

-- prepare statement, runs instantaneous:
PREPARE hugeplan (integer, integer, integer, ...) AS
SELECT *
FROM primes
WHERE n IN ($1, $2, $3, ..., $9592);

-- fetch the prime numbers:
EXECUTE hugeplan(2, 3, 5, ..., 99991);

-- EXPLAIN ANALYZE output for the EXECUTE:
"Index Scan using n_idx on primes  (cost=0.42..9750.77 rows=9592 width=5) (actual time=0.024..15.268 rows=9592 loops=1)"
"  Index Cond: (n = ANY ('{2,3,5,7, (...)"
"Execution time: 16.063 ms"

-- setup, should you care:
CREATE TABLE public.primes
(
  n integer NOT NULL,
  prime boolean,
  CONSTRAINT n_idx PRIMARY KEY (n)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE public.primes
  OWNER TO postgres;

INSERT INTO public.primes
SELECT generate_series(1,100000);

Cependant, ce fil (assez ancien) sur la liste de diffusion pgsql-hackers indique qu'il y a toujours un coût non négligeable dans la planification de telles requêtes, alors croyez-moi sur parole.

blubb
la source
3

Si vous avez une requête comme:

SELECT * FROM user WHERE id IN (1, 2, 3, 4 -- and thousands of another keys)

vous pouvez augmenter les performances si vous réécrivez votre requête comme:

SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
hacker13ua
la source
10
PostgreSQL's EXPLAINdit qu'il réécrit en interne mon IN (...)as ANY ('{...}'::integer[]).
Kiran Jonnalagadda
4
Quoi qu'il en soit, @KiranJonnalagadda, cela augmente les performances (négligeables, peut-être) si aucun travail interne n'est nécessaire.
Rodrigo
1

J'ai juste essayé. la réponse est -> entier hors plage sous la forme d'une valeur sur 2 octets: 32768

Andrew
la source
0

Vous pouvez envisager de refactoriser cette requête au lieu d'ajouter une liste d'ID arbitrairement longue ... Vous pouvez utiliser une plage si les ID suivent effectivement le modèle de votre exemple:

SELECT * FROM user WHERE id >= minValue AND id <= maxValue;

Une autre option consiste à ajouter une sélection interne:

SELECT * 
FROM user 
WHERE id IN (
    SELECT userId
    FROM ForumThreads ft
    WHERE ft.id = X
);
PatrikAkerstrand
la source