回答:
ここにあるソースコードによると、行850以降、 PostgreSQLは引数の数を明示的に制限していません。
以下は、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.
*/
これは本当の質問に対する答えではありませんが、他の人にも役立つかもしれません。
少なくとも、PosgresqlのJDBCドライバー9.1を使用して、PostgreSQLバックエンドに渡すことができる32767の値(= Short.MAX_VALUE)の技術的な制限があることがわかります。
これは、postgresql jdbcドライバーを使用した「id from(... 100kの値...)からのxからの削除」のテストです。
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)
explain select * from test where id in (values (1), (2));
Seq Scan on test (cost=0.00..1.38 rows=2 width=208)
Filter: (id = ANY ('{1,2}'::bigint[]))
しかし、2番目のクエリを試す場合:
explain select * from test where id = any (values (1), (2));
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)
postgresが一時テーブルを作成し、それと結合することがわかります
IN句に渡す要素の数に制限はありません。さらに要素がある場合は、それを配列と見なし、データベース内のスキャンごとに、配列に含まれているかどうかを確認します。このアプローチはそれほどスケーラブルではありません。IN句を使用する代わりに、一時テーブルでINNER JOINを使用してみてください。詳細については、http://www.xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic/を参照してください。INNER JOINを使用すると、クエリオプティマイザーはハッシュ結合やその他の最適化を利用できます。一方、IN句では、オプティマイザーがクエリを最適化する方法はありません。この変更により、少なくとも2倍の高速化に気づきました。
Oracle DBの経験が豊富な人として、私もこの制限について心配していました。IN
-listに〜10'000個のパラメーターを含むクエリのパフォーマンステストを実行し、実際にすべての素数をクエリパラメーターとしてリストすることにより、最初の100'000整数のテーブルから100'000までの素数をフェッチしました。
私の結果では、クエリプランオプティマイザーのオーバーロードや、インデックスを使用せずにプランを取得することについて心配する必要がないことが示されています= ANY({...}::integer[])
。
-- 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);
ただし、pgsql-hackersメーリングリストのこの(かなり古い)スレッドは、そのようなクエリの計画に無視できないほどのコストがあることを示しています。
次のようなクエリがある場合:
SELECT * FROM user WHERE id IN (1, 2, 3, 4 -- and thousands of another keys)
次のようにクエリを書き換えると、パフォーマンスが向上します。
SELECT * FROM user WHERE id = ANY(VALUES (1), (2), (3), (4) -- and thousands of another keys)
EXPLAIN
は、内部的にをIN (...)
として書き換えているとしていANY ('{...}'::integer[])
ます。
IDの任意の長いリストを追加する代わりに、そのクエリをリファクタリングすることを検討したい場合があります... IDが実際に例のパターンに従っている場合は、範囲を使用できます。
SELECT * FROM user WHERE id >= minValue AND id <= maxValue;
別のオプションは、内部選択を追加することです:
SELECT *
FROM user
WHERE id IN (
SELECT userId
FROM ForumThreads ft
WHERE ft.id = X
);