Oracle の SQL で大量のデータを IN 句に指定しようとしたとき、「ORA-01795: リストの中の最大式の数は 1000 です」エラーに遭遇したことはありませんか?
Oracle には IN 句のリテラル値の数に 1,000 件という上限があります。この記事では、この制限を回避する5つの方法を、パフォーマンスの違い・メリット・デメリットとともに解説します。
- ORA-01795 エラーの原因と制限の適用範囲
- OR で分割する方法(最も簡単)
- タプル比較(行値コンストラクタ)で 65,535 件まで対応する方法
- グローバル一時テーブル(GTT)を使った大量件数の対応
- サブクエリ / EXISTS に変換してIN句制限を回避する方法
- XMLTABLE を使った文字列からのテーブル変換
ORA-01795 エラーとは
Oracle では、IN 句に指定できるリテラル値(直接記述した値)の数が 最大 1,000 件です。
-- 1,000件まではOK SELECT * FROM employees WHERE emp_id IN (1, 2, 3, ..., 1000); -- 1,001件以上はエラー SELECT * FROM employees WHERE emp_id IN (1, 2, 3, ..., 1001); -- ORA-01795: リストの中の最大式の数は 1000 です
この 1,000 件制限は リテラル値(直接記述した値) に対してのみ適用されます。サブクエリで指定する場合(
IN (SELECT ...))は制限がありません。方法1:OR で分割する(最も簡単)
1,000 件ずつに分割して OR で連結します。アプリケーション側で動的に SQL を生成する場合に最も手軽です。
SELECT * FROM employees WHERE emp_id IN (1, 2, 3, ..., 1000) OR emp_id IN (1001, 1002, 1003, ..., 2000) OR emp_id IN (2001, 2002, 2003, ..., 3000);
| メリット | デメリット |
|---|---|
| SQL 構造がシンプルで理解しやすい | 値が増えると SQL が非常に長くなる |
| 既存 SQL にわずかな変更で対応可能 | OR の数が多いとパフォーマンスが低下する可能性 |
方法2:タプル比較(行値コンストラクタ)で 65,535 件まで対応
値とダミー値のペアにすることで、IN 句の制限を 65,535 件まで拡張できます。
SELECT * FROM employees WHERE (emp_id, 1) IN ( (1, 1), (2, 1), (3, 1), ..., (2000, 1) );
1列目が実際の検索値、2列目の 1 はダミー値です。タプル比較の上限は 65,535 件です。
-- 65,536件以上はエラー -- ORA-00913: 値の個数が多すぎます
| メリット | デメリット |
|---|---|
| 1つのIN句で最大65,535件まで対応 | ダミー値が必要で可読性が低い |
| OR を使った分割より見通しが良い | 65,535件を超える場合は別の方法が必要 |
方法3:グローバル一時テーブル(GTT)を使う
値をグローバル一時テーブルに INSERT して、JOIN または IN サブクエリで参照します。大量件数に対応でき、パフォーマンスも良好です。
-- 1. グローバル一時テーブルを事前に作成(一度だけ実行) CREATE GLOBAL TEMPORARY TABLE tmp_ids ( id NUMBER ) ON COMMIT DELETE ROWS; -- 2. 値を一括 INSERT INSERT INTO tmp_ids (id) VALUES (1); INSERT INTO tmp_ids (id) VALUES (2); -- ... 必要な件数分 -- 3. JOIN で検索 SELECT e.* FROM employees e JOIN tmp_ids t ON e.emp_id = t.id; -- または IN サブクエリで SELECT * FROM employees WHERE emp_id IN (SELECT id FROM tmp_ids);
ON COMMIT DELETE ROWS:トランザクションをコミットすると自動的にデータが削除されます。ON COMMIT PRESERVE ROWS:セッションが終了するまでデータが保持されます。GTT は複数セッションから同時に使用しても、各セッションで独立したデータが見えます。
| メリット | デメリット |
|---|---|
| 件数制限なし | 一時テーブルの事前作成が必要(DDL が必要) |
| 大量データでもパフォーマンスが安定 | INSERT のオーバーヘッドがある |
| インデックスを追加してさらに高速化可能 | — |
方法4:サブクエリ / EXISTS に変換する(最もベストプラクティス)
対象の値がほかのテーブルに存在するなら、IN 句をサブクエリまたは EXISTS に置き換えるのが最もすっきりした解決策です。
-- サブクエリに変換(IN 句の制限なし) SELECT * FROM employees WHERE emp_id IN ( SELECT target_id FROM target_list WHERE condition = 'active' ); -- EXISTS に変換するとさらにパフォーマンスが良い場合がある SELECT * FROM employees e WHERE EXISTS ( SELECT 1 FROM target_list t WHERE e.emp_id = t.target_id AND t.condition = 'active' );
EXISTS は対象行が見つかった時点で評価を停止するため、IN より効率的なことが多いです。特に外部テーブルの件数が多い場合は EXISTS の方がパフォーマンスが良い傾向があります。
| メリット | デメリット |
|---|---|
| 件数制限なし | 値のリストがテーブルに存在する必要がある |
| パフォーマンスが良い(特にEXISTS) | リテラル値のリストには使えない |
| SQL がシンプル | — |
方法5:XMLTABLE を使う
カンマ区切りの文字列を動的にテーブルに変換できます。動的 SQL と組み合わせる場面に便利です。
SELECT * FROM employees
WHERE emp_id IN (
SELECT TO_NUMBER(COLUMN_VALUE)
FROM XMLTABLE(
'1,2,3,1001,1002,2001,2002,3001'
)
);
| メリット | デメリット |
|---|---|
| 一時テーブルの作成が不要 | 大量データではパフォーマンスが低下 |
| 動的 SQL と組み合わせやすい | XML 解析のオーバーヘッドがある |
5つの方法の比較まとめ
| 方法 | 件数上限 | パフォーマンス | 実装の容易さ | 最適な場面 |
|---|---|---|---|---|
| OR 分割 | 制限なし(分割で対応) | △(OR が多いと低下) | ◎ | 1,000〜3,000件程度 |
| タプル比較 | 65,535件 | ○ | ○ | 数千件程度の固定リスト |
| 一時テーブル(GTT) | 制限なし | ◎ | △(事前作成が必要) | 大量件数・繰り返し実行 |
| サブクエリ/EXISTS | 制限なし | ◎ | ◎ | 値がテーブルに存在する場合 |
| XMLTABLE | 制限なし | △ | ○ | 動的SQL・少量データ |
他の RDBMS との比較
| DBMS | IN句の上限 |
|---|---|
| Oracle | 1,000件(リテラル値) |
| SQL Server | 制限なし(パラメータは2,100まで) |
| PostgreSQL | 制限なし |
| MySQL | 制限なし(max_allowed_packetに依存) |
よくある質問
IN (SELECT ...) の形式では件数制限はなく、サブクエリが何百万件を返しても動作します。(col1, col2) IN ((v1, v2), ...) の形式では、タプル全体を1つの式として扱い、その数の上限が 65,535 件となっています。ON COMMIT DELETE ROWS または ON COMMIT PRESERVE ROWS の設定でデータの保持期間を制御できます。IN (?1) で自動分割されることもあります。まとめ
- Oracle の IN 句(リテラル値)は 最大 1,000 件(ORA-01795)
- 最も簡単:OR で 1,000 件ずつ分割
- まとめて指定したい:タプル比較で最大 65,535 件まで
- 大量件数・高パフォーマンス:グローバル一時テーブル(GTT)
- 値がテーブルにある:サブクエリ / EXISTS が最もベストプラクティス
