OracleのSQLで大量のデータを IN 句に指定しようとして、エラーになった経験はありませんか?
Oracleの IN 句には 1,000件まで という上限があります。1,001件以上を指定すると ORA-01795 エラーが発生します。
この記事では、IN句の1,000件制限を回避する5つの方法を、パフォーマンスの違い・メリット・デメリットとともに解説します。
IN句の1,000件制限とは?
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:OR で複数の IN 句に分割する
最もシンプルな方法です。1,000件ごとに分割して OR で結合します。
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:複合条件(タプル比較)を使う
値とダミー値のペアにすることで、IN句の制限を回避できます。
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)を使う
大量の値を一時テーブルに格納し、JOINまたはサブクエリで参照する方法です。数万件以上の場合に適しています。
-- 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;
| メリット | デメリット |
|---|---|
| 件数の上限がない | 一時テーブルの作成が必要 |
| 大量データでもパフォーマンスが安定 | INSERT処理のオーバーヘッドがある |
| インデックスを追加してさらに高速化可能 | DDL権限(CREATE TABLE)が必要 |
回避方法4:サブクエリに変換する
条件値が別のテーブルに存在する場合は、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) | リテラル値の一覧には使えない |
| SQLがシンプル | – |
回避方法5:XMLTABLEを使う
動的にリテラル値のリストを生成する場合に、XMLTABLEを使ってテーブル化できます。
SELECT * FROM employees
WHERE emp_id IN (
SELECT TO_NUMBER(COLUMN_VALUE)
FROM XMLTABLE(
'1,2,3,1001,1002,2001,2002,3001'
)
);
カンマ区切りの文字列をテーブルに変換できるため、動的SQLとの相性が良い方法です。
| メリット | デメリット |
|---|---|
| 一時テーブルの作成が不要 | 大量データではパフォーマンスが低下 |
| 動的SQLと組み合わせやすい | XML解析のオーバーヘッドがある |
各方法の比較まとめ
| 方法 | 上限 | パフォーマンス | 手軽さ | おすすめ場面 |
|---|---|---|---|---|
| OR分割 | なし(実質的に) | 中 | 高 | 〜数千件の場合 |
| タプル比較 | 65,535件 | 中 | 高 | 〜数万件の場合 |
| 一時テーブル | なし | 高 | 低 | 大量データ・繰り返し実行 |
| サブクエリ/EXISTS | なし | 高 | 高 | 条件値がテーブルにある場合 |
| XMLTABLE | なし | 低〜中 | 中 | 動的SQL・少量データ |
他のDBMSのIN句上限
参考として、主要なデータベースのIN句上限を比較します。
| DBMS | IN句の上限 |
|---|---|
| Oracle | 1,000件 |
| SQL Server | 制限なし(パラメータは2,100個まで) |
| PostgreSQL | 制限なし |
| MySQL | 制限なし(max_allowed_packetに依存) |
まとめ
- OracleのIN句は リテラル値1,000件 が上限
- 最も手軽な回避策は OR分割 または タプル比較
- パフォーマンス重視なら 一時テーブル または EXISTS が最適
- 条件値がテーブルにあるなら サブクエリ/EXISTS を使うのがベストプラクティス
