PostgreSQL: ¿número máximo de parámetros en la cláusula "IN"?

147

En Postgres, puede especificar una cláusula IN, como esta:

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

¿Alguien sabe cuál es el número máximo de parámetros que puede pasar a IN?

Alex Riley
fuente

Respuestas:

83

Según el código fuente ubicado aquí, comenzando en la línea 850, PostgreSQL no limita explícitamente el número de argumentos.

El siguiente es un comentario de código de la línea 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
fuente
56

Esto no es realmente una respuesta a la presente pregunta, sin embargo, también podría ayudar a otros.

Al menos puedo decir que hay un límite técnico de 32767 valores (= Short.MAX_VALUE) pasables al backend PostgreSQL, utilizando el controlador JDBC 9.1 de Posgresql.

Esta es una prueba de "eliminar de x donde id en (... 100k valores ...)" con el controlador postgresql jdbc:

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
fuente
66
El OP ha preguntado sobre la limitación del motor DB, pero en busca de la limitación JDBC, he venido aquí y eso es lo que estaba buscando. Sin embargo, hay una limitación, bastante alta.
9ilsdx 9rvj 0lo
36
explain select * from test where id in (values (1), (2));

PLAN DE CONSULTA

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

Pero si prueba la segunda consulta:

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

PLAN DE CONSULTA

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)

Podemos ver que la tabla temporal de compilación de postgres y unirse con ella

hacker13ua
fuente
Pero lo que escuché que postgres-9.3 + ambos parecen tener el mismo rendimiento. datadoghq.com/blog/…
PiyusG
18

No hay límite para la cantidad de elementos que está pasando a la cláusula IN. Si hay más elementos, lo considerará como una matriz y luego, para cada escaneo en la base de datos, verificará si está contenida en la matriz o no. Este enfoque no es tan escalable. En lugar de usar la cláusula IN, intente usar INNER JOIN con la tabla temporal. Consulte http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ para obtener más información. El uso de escalas INNER JOIN y el optimizador de consultas pueden hacer uso de hash join y otras optimizaciones. Mientras que con la cláusula IN no hay forma de que el optimizador optimice la consulta. He notado una aceleración de al menos 2x con este cambio.

Prasanth Jayachandran
fuente
2
El enlace al que te refieres no dice de qué DBMS está hablando. Si bien puedo confirmar que en Oracle DB, el uso de tablas temporales proporciona un aumento masivo del rendimiento sobre el uso de consultas combinadas ORy INcláusulas debido a la gran sobrecarga en el análisis y la planificación de tales consultas, no pude confirmar el problema con Postgres 9.5, vea esta respuesta .
blubb
17

Como alguien más experimentado con Oracle DB, también estaba preocupado por este límite. INRealicé una prueba de rendimiento para una consulta con ~ 10'000 parámetros en una lista, obteniendo números primos hasta 100'000 de una tabla con los primeros 100'000 enteros al enumerar todos los números primos como parámetros de consulta .

Mis resultados indican que no debe preocuparse por sobrecargar el optimizador de planes de consulta u obtener planes sin uso de índice , ya que transformará la consulta para usarla = ANY({...}::integer[])donde pueda aprovechar los índices como se esperaba:

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

Sin embargo, esto (bastante viejo) hilo en la lista de correo pgsql-hackers indica que todavía hay un costo no despreciable en la planificación de tales consultas, así que tome mi palabra con un poco de sal.

blubb
fuente
3

Si tiene una consulta como:

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

puede aumentar el rendimiento si reescribe su consulta como:

SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
hacker13ua
fuente
10
PostgreSQL EXPLAINdice que está reescribiendo internamente mi IN (...)como ANY ('{...}'::integer[]).
Kiran Jonnalagadda
44
De todos modos, @KiranJonnalagadda, aumenta el rendimiento (insignificante, tal vez) si no se necesita trabajo interno.
Rodrigo
1

Solo lo intenté. la respuesta es -> entero fuera de rango como un valor de 2 bytes: 32768

Andrés
fuente
0

Es posible que desee considerar refactorizar esa consulta en lugar de agregar una lista arbitrariamente larga de identificadores ... Podría usar un rango si los identificadores siguen el patrón en su ejemplo:

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

Otra opción es agregar una selección interna:

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