PostgreSQL - número máximo de parâmetros na cláusula "IN"?

147

No Postgres, você pode especificar uma cláusula IN, assim:

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

Alguém sabe qual é o número máximo de parâmetros que você pode passar para IN?

Alex Riley
fonte

Respostas:

83

De acordo com o código fonte localizado aqui, começando na linha 850, o PostgreSQL não limita explicitamente o número de argumentos.

A seguir, um comentário de código da linha 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
fonte
56

Esta não é realmente uma resposta para a pergunta atual, mas também pode ajudar outras pessoas.

Pelo menos, posso dizer que há um limite técnico de 32767 valores (= Short.MAX_VALUE) passáveis ​​para o back-end do PostgreSQL, usando o driver JDBC 9.1 do Posgresql.

Este é um teste de "delete from x where id in (... 100k values ​​...)" com o driver 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
fonte
6
O OP perguntou sobre a limitação do mecanismo de banco de dados, mas, ao procurar pela limitação de JDBC, eu vim aqui e foi para isso que eu estava procurando. Portanto, há uma limitação, no entanto, bastante alta.
precisa saber é o seguinte
36
explain select * from test where id in (values (1), (2));

PLANO DE CONSULTA

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

Mas se tentar a segunda consulta:

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

PLANO 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 o postgres constrói a tabela temporária e se junta a ela

hacker13ua
fonte
Mas o que ouvi dizer que o postgres-9.3 + ambos parece ter o mesmo desempenho. datadoghq.com/blog/...
PiyusG
18

Não há limite para o número de elementos que você está passando para a cláusula IN. Se houver mais elementos, ele será considerado como matriz e, para cada varredura no banco de dados, verificará se está contido na matriz ou não. Essa abordagem não é tão escalável. Em vez de usar a cláusula IN, tente usar INNER JOIN com a tabela temporária. Consulte http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/ para obter mais informações. O uso de escalas INNER JOIN bem como o otimizador de consulta pode fazer uso de junção de hash e outra otimização. Enquanto na cláusula IN não há como o otimizador otimizar a consulta. Eu notei uma aceleração de pelo menos 2x com essa alteração.

Prasanth Jayachandran
fonte
2
O link ao qual você está se referindo não diz do que o DBMS está falando. Embora eu possa confirmar que no banco de dados Oracle, o uso de tabelas temporárias aumenta bastante o desempenho do uso de combinações ORe INcláusulas de consultas devido à grande sobrecarga na análise e no planejamento de tais consultas, mas não pude confirmar o problema com o Postgres 9.5. Consulte esta resposta .
blubb
17

Como alguém mais experiente no Oracle DB, eu também estava preocupado com esse limite. Realizei um teste de desempenho para uma consulta com ~ 10.000 parâmetros em uma lista IN, buscando números primos de até 100.000 de uma tabela com os primeiros 100.000 , listando todos os números primos como parâmetros de consulta .

Meus resultados indicam que você não precisa se preocupar em sobrecarregar o otimizador de plano de consulta ou obter planos sem o uso de índice , pois ele transformará a consulta em uso, = ANY({...}::integer[])onde poderá aproveitar os índices conforme o esperado:

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

No entanto, esse tópico (bastante antigo) na lista de discussão pgsql-hackers indica que ainda há um custo não negligenciável no planejamento de tais consultas, então aceite minha palavra com um pouco de sal.

blubb
fonte
3

Se você tiver uma consulta como:

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

você pode aumentar a performance se reescrever sua consulta como:

SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
hacker13ua
fonte
10
O PostgreSQL EXPLAINdiz que está reescrevendo internamente meu IN (...)como ANY ('{...}'::integer[]).
22415 Kiran Jonnalagadda
4
De qualquer forma, @KiranJonnalagadda, aumenta o desempenho (insignificante, talvez) se nenhum trabalho interno for necessário.
Rodrigo
1

Apenas tentei. a resposta é -> número inteiro fora do intervalo como um valor de 2 bytes: 32768

Andrew
fonte
0

Convém refatorar essa consulta em vez de adicionar uma lista arbitrariamente longa de IDs ... Você pode usar um intervalo se os IDs realmente seguirem o padrão no seu exemplo:

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

Outra opção é adicionar uma seleção interna:

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