PostgreSQL - numero massimo di parametri nella clausola "IN"?


147

In Postgres, puoi specificare una clausola IN, come questa:

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

Qualcuno sa qual è il numero massimo di parametri che è possibile passare in IN?

Risposte:


83

Secondo il codice sorgente che si trova qui, a partire dalla riga 850, PostgreSQL non limita esplicitamente il numero di argomenti.

Di seguito è riportato un commento in codice dalla riga 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.
 */

56

Questa non è davvero una risposta alla domanda attuale, tuttavia potrebbe aiutare anche gli altri.

Almeno posso dire che esiste un limite tecnico di 32767 valori (= Short.MAX_VALUE) passabili al backend PostgreSQL, usando il driver JDBC di Posgresql 9.1.

Questo è un test di "elimina da x dove id in (... 100k valori ...)" con il driver 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)

6
L'OP ha chiesto informazioni sulla limitazione del motore DB, ma alla ricerca della limitazione JDBC sono venuto qui ed è quello che cercavo. Quindi c'è un limite, tuttavia, abbastanza alto.
9ilsdx 9rvj 0lo,

36
explain select * from test where id in (values (1), (2));

PIANO DI QUERY

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

Ma se provi la seconda query:

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

PIANO DI QUERY

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)

Possiamo vedere che Postgres costruisce una tabella temporanea e ci unisce


Ma quello che ho sentito che Postgres-9.3 + sembra essere lo stesso performer. datadoghq.com/blog/…
PiyusG

18

Non vi è alcun limite al numero di elementi che si sta passando alla clausola IN. Se ci sono più elementi lo considererà come array e quindi per ogni scansione nel database controllerà se è contenuto nell'array o meno. Questo approccio non è così scalabile. Invece di utilizzare la clausola IN, provare a utilizzare INNER JOIN con la tabella temporanea. Fare riferimento a http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ per ulteriori informazioni. L'uso delle scale INNER JOIN e di Query Optimizer possono utilizzare hash join e altre ottimizzazioni. Considerando che con la clausola IN non c'è modo per l'ottimizzatore di ottimizzare la query. Ho notato uno speedup di almeno 2 volte con questa modifica.


2
Il link a cui ti riferisci non dice di che DBMS sta parlando. Mentre posso confermare che su Oracle DB, l'uso di tabelle temporanee offre un notevole incremento delle prestazioni rispetto all'utilizzo della combinazione di query ORe INclausole a causa dell'elevato sovraccarico nell'analisi e nella pianificazione di tali query, non ho potuto confermare il problema con Postgres 9.5, vedere questa risposta .
Blubb,

17

Come qualcuno più esperto con Oracle DB, ero preoccupato anche per questo limite. Ho eseguito un test delle prestazioni per una query con ~ 10'000 parametri in un INelenco, recuperando i numeri primi fino a 100.000 da una tabella con i primi 100.000 numeri interi elencando effettivamente tutti i numeri primi come parametri di query .

I miei risultati indicano che non è necessario preoccuparsi di sovraccaricare l'ottimizzatore del piano di query o di ottenere piani senza utilizzo dell'indice , poiché trasformerà la query in modo = ANY({...}::integer[])che possa utilizzare gli indici come previsto:

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

Tuttavia, questo (piuttosto vecchio) thread sulla mailing list di pgsql-hackers indica che c'è ancora un costo non trascurabile nella pianificazione di tali query, quindi prendi la mia parola con un pizzico di sale.


3

Se hai query come:

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

puoi aumentare le prestazioni se riscrivi la tua query come:

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

10
PostgreSQL di EXPLAINdice che è internamente riscrivere la mia IN (...)come ANY ('{...}'::integer[]).
Kiran Jonnalagadda,

4
Ad ogni modo, @KiranJonnalagadda, aumenta le prestazioni (trascurabili, forse) se non è necessario alcun lavoro interno.
Rodrigo,

1

L'ho appena provato. la risposta è -> numero intero fuori intervallo come valore di 2 byte: 32768


0

Potresti prendere in considerazione il refactoring di quella query invece di aggiungere un elenco arbitrariamente lungo di ID ... Potresti usare un intervallo se gli ID effettivamente seguono il modello nel tuo esempio:

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

Un'altra opzione è quella di aggiungere una selezione interna:

SELECT * 
FROM user 
WHERE id IN (
    SELECT userId
    FROM ForumThreads ft
    WHERE ft.id = X
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.