【Oracle】IN句に1000件以上を指定する5つの方法|OR分割・タプル比較・一時テーブル・サブクエリ・XMLTABLE

【Oracle】IN句の上限を回避する方法 Oracle

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 の数が多いとパフォーマンスが低下する可能性
適用場面:件数が少なめ(1,000〜3,000件程度)で、アプリケーションから動的 SQL を生成する場合に最適です。

方法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 の利点
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に依存)

よくある質問

Q. IN句のサブクエリ(SELECT文)でも1000件制限がありますか?
A. いいえ、ありません。1,000 件制限はリテラル値(直接記述した値)の場合だけです。IN (SELECT ...) の形式では件数制限はなく、サブクエリが何百万件を返しても動作します。
Q. タプル比較(行値コンストラクタ)の 65,535 件制限はどこから来ていますか?
A. Oracle の式リストの内部制限です。(col1, col2) IN ((v1, v2), ...) の形式では、タプル全体を1つの式として扱い、その数の上限が 65,535 件となっています。
Q. グローバル一時テーブルとローカル一時テーブルの違いは何ですか?
A. Oracle には「グローバル一時テーブル(GTT)」のみがあります(SQL Server のようなローカル一時テーブルはありません)。GTT はオブジェクトとして永続的に存在しますが、データは各セッションに独立して管理され、ON COMMIT DELETE ROWS または ON COMMIT PRESERVE ROWS の設定でデータの保持期間を制御できます。
Q. IN句と EXISTS ではどちらが速いですか?
A. 一般的に、外部テーブルの件数が多い場合は EXISTS の方が速いことが多いです。EXISTS は条件を満たす最初の行を見つけた時点で評価を停止するためです。ただし、Oracle の実行計画によっては IN がより速いこともあります。EXPLAIN PLAN で確認するのが確実です。
Q. Java や Python などのアプリケーションから大量の ID を IN 句に渡す場合の推奨方法は?
A. アプリケーションから動的に SQL を生成する場合は、以下が推奨です。①値をバインド変数のリストで渡すことができない場合は GTT(一時テーブル)に INSERT してから JOIN する方法が最もパフォーマンスが安定します。②1,000〜3,000件程度なら OR 分割でも実用的です。③Spring Data JPA などのフレームワークを使う場合は IN (?1) で自動分割されることもあります。

まとめ

Oracle IN句 1,000件制限の回避方法まとめ

  • Oracle の IN 句(リテラル値)は 最大 1,000 件(ORA-01795)
  • 最も簡単:OR で 1,000 件ずつ分割
  • まとめて指定したい:タプル比較で最大 65,535 件まで
  • 大量件数・高パフォーマンス:グローバル一時テーブル(GTT)
  • 値がテーブルにある:サブクエリ / EXISTS が最もベストプラクティス

あわせて読みたい