【Oracle】ORA-01795: リストに指定できる式の最大数は1000です の原因と回避方法

【Oracle】ORA-01795: リストに指定できる式の最大数は1000です の原因と回避方法 Oracle

ORA-01795: リストに指定できる式の最大数は1000です は、IN 句などのリストに1000個を超える値を指定したときに発生するOracleエラーです。大量のIDを画面やバッチから渡して検索するときによく出ます。

この記事では、ORA-01795の原因、すぐ確認するポイント、分割ORでの暫定回避、一時表やJOINを使う本命の直し方、アプリ側での注意点まで整理します。「OracleのIN句は何件まで指定できるのか」「IN句1000件上限 に当たったらどう直すか」を、実務寄りにまとめます。サブクエリや IN 句の基本を確認したい場合は、Oracleサブクエリ完全ガイドも参考になります。

先に結論: ORA-01795は、IN (値1, 値2, ...) の値を1000個以内にするだけなら分割ORで回避できます。ただし実務では、ID一覧を一時表に入れてJOINする、または元データをサブクエリ化する方法のほうが保守しやすく高速になりやすいです。
スポンサーリンク

OracleのIN句の上限は1000件

Oracleの IN 句では、1つのリストに指定できる式は最大1000個です。1000件までは通りますが、1001件以上になるとORA-01795が発生します。英語環境では maximum number of expressions in a list is 1000 のようなメッセージになります。

値の数 結果 補足
999件 実行可能 上限以内
1000件 実行可能 上限ちょうど
1001件以上 ORA-01795 分割、JOIN、一時表などが必要

ORA-01795の意味

Oracleでは、1つのリストに指定できる式の数に上限があります。代表例は WHERE id IN (...) で、括弧内の値が1000個を超えるケースです。

ora01795-bad.sql
-- NG: IN句に1000個を超える値を指定している
SELECT *
FROM   orders
WHERE  order_id IN (1, 2, 3, /* ... */, 1000, 1001);

このSQLでは IN 句のリストが1000件を超えているため、ORA-01795になります。値の型が数値でも文字列でも、リストの件数が多ければ同じように発生します。

まず確認するポイント

確認箇所 見ること よくある原因
IN 括弧内の値の数 ID一覧が1000件を超えている
SQL生成処理 配列やリストの分割有無 アプリ側で全IDをそのまま連結している
画面検索 一括選択の件数 チェックした対象が多すぎる
バッチ処理 対象IDの渡し方 CSVやログから作ったIDリストを直接IN句にしている
実行計画 大量ORや巨大SQLの影響 分割回避後にSQLが重くなっている

原因1: IN句に1000件を超える値を渡している

もっとも多い原因は、アプリやバッチが作ったID一覧をそのまま IN 句に展開するパターンです。

in-list-too-long.sql
-- NG: 実際には1001件以上の値が並ぶ
SELECT order_id,
       customer_id,
       order_date
FROM   orders
WHERE  order_id IN (:id1, :id2, :id3, /* ... */, :id1001);

バインド変数を使っていても、リスト内の式の数が1000を超えればORA-01795になります。バインド変数そのものの使い方は大事ですが、このエラーでは「バインドしているか」より「リストの件数」が問題です。バインド変数の基本は、Oracleバインド変数完全ガイドで整理しています。

暫定回避: 1000件以内に分割してORでつなぐ

すぐ動かしたいだけなら、リストを1000件以内に分割して OR でつなぐ方法があります。小規模な管理画面や一時対応では使えます。

split-in-list.sql
-- OK: 1つのINリストを1000件以内に分ける
SELECT *
FROM   orders
WHERE  order_id IN (:id1, :id2, /* ... */, :id1000)
   OR  order_id IN (:id1001, :id1002, /* ... */, :id1500);

ただし、分割ORはSQLが長くなりやすく、可読性も実行計画も悪くなりがちです。件数が増え続ける処理では、次の一時表やJOIN方式を検討したほうが安全です。

本命対策1: 一時表にIDを入れてJOINする

実務でおすすめしやすいのは、検索対象のIDを一時表に入れて、対象テーブルとJOINする方法です。SQLが短くなり、件数が増えても構造が変わりません。

create-gtt.sql
CREATE GLOBAL TEMPORARY TABLE search_order_ids (
  order_id NUMBER NOT NULL
) ON COMMIT DELETE ROWS;
create-gtt-index.sql
-- 件数が多い場合は検索列に索引を用意する
CREATE INDEX idx_search_order_ids_01
ON search_order_ids (order_id);
join-gtt.sql
-- アプリやバッチでsearch_order_idsへ対象IDを投入してから検索する
SELECT o.order_id,
       o.customer_id,
       o.order_date
FROM   orders o
       JOIN search_order_ids s
         ON s.order_id = o.order_id;

一時表を使うと、1000件上限を避けられるだけでなく、SQLの再利用性も上がります。対象件数が多い場合は、一時表側の検索列にも索引を用意して、JOIN条件で使えるようにします。一時表の作り方や ON COMMIT DELETE ROWS / PRESERVE ROWS の違いは、Oracleグローバル一時表(GTT)完全ガイドを参照してください。

本命対策2: ID一覧をサブクエリ化する

対象IDが別テーブルや検索条件から取れるなら、アプリでID一覧を作らず、SQL内でサブクエリにします。

use-subquery.sql
SELECT *
FROM   orders o
WHERE  o.customer_id IN (
  SELECT c.customer_id
  FROM   campaign_targets c
  WHERE  c.campaign_id = :campaign_id
);

この形なら、リストに値を1000個並べる必要がありません。また、対象抽出ロジックをDB側に寄せられるため、アプリとDBの間で大量IDを受け渡しする負荷も減らせます。

EXISTSに書き換える方法

IN 句の代わりに EXISTS を使うと、関連行の存在確認として自然に書ける場合があります。

use-exists.sql
SELECT *
FROM   orders o
WHERE  EXISTS (
  SELECT 1
  FROM   campaign_targets c
  WHERE  c.customer_id = o.customer_id
  AND    c.campaign_id = :campaign_id
);

INEXISTS のどちらが必ず速い、という単純な話ではありません。検索条件、件数、索引、統計情報によって変わるため、実行計画を見て判断します。

アプリ側で分割する場合の注意点

アプリ側でIDリストを分割する場合は、単に文字列を連結するのではなく、バインド変数を使ってSQLインジェクションを避けます。また、空リストや重複IDの扱いも決めておく必要があります。

注意点 理由 対応
空リスト IN () はSQLとして不正 検索前に0件返却する、または条件を付けない方針を決める
重複ID SQLが無駄に長くなる アプリ側で重複排除してから渡す
文字列連結 SQLインジェクションや型変換エラーの原因 バインド変数を使う
巨大SQL パース負荷が増える 一時表やJOINへ寄せる
chunk-example.py
def chunks(values, size=1000):
    for i in range(0, len(values), size):
        yield values[i:i + size]

# 実際のSQL生成では、各値を直接連結せずバインド変数にする

分割ORは「緊急回避」としては便利ですが、検索条件として頻繁に使うならDB設計や一時表方式を見直すほうが長期的には楽です。

避けたい回避策

ORA-01795を急いで避けようとして、別の問題を増やしてしまうことがあります。特に本番処理では、次のような回避策は慎重に扱います。

避けたい方法 問題点 代替案
値をSQL文字列に直接連結する SQLインジェクション、型変換エラー、巨大SQLの原因 バインド変数、一時表、JOIN
毎回GTTをCREATE/DROPする DDL負荷や権限管理が増える 事前作成した一時表を使い回す
分割ORを無制限に増やす SQLが長くなり、パース負荷が増える 件数が多い処理は一時表へ寄せる
タプル化などの小技だけで逃げる 意図が読みにくく、保守しづらい JOIN、EXISTS、サブクエリで表現する
avoid-bad-workarounds.sql
-- 避けたい: 値を連結した巨大SQLを作り続ける
SELECT *
FROM   orders
WHERE  order_id IN (/* アプリで連結した大量ID */);

-- 推奨: 対象IDを表として扱い、JOINする
SELECT o.*
FROM   orders o
       JOIN search_order_ids s
         ON s.order_id = o.order_id;

パフォーマンス面の考え方

ORA-01795を避けるために分割ORを増やすと、SQLが長くなり、パース負荷や実行計画の揺れが問題になることがあります。対象件数が多い場合は、次の順で検討すると現実的です。

  1. 一時的な少量検索なら、1000件以内に分割する
  2. 繰り返し使う処理なら、一時表にIDを投入してJOINする
  3. 元データがDB内にあるなら、サブクエリやEXISTSに寄せる
  4. 大量更新や削除なら、処理単位を分割してコミット設計も確認する

大量の INSERTUPDATEDELETE と組み合わせる場合は、Oracle INSERT・UPDATE・DELETE完全ガイドもあわせて確認すると、処理単位を設計しやすくなります。

似ているエラーとの違い

エラー 主な原因 見る場所
ORA-01795 リストの式が1000個を超えている IN 句や値リストの件数
ORA-00913 値や列の個数が多すぎる INSERTの列数、VALUES、サブクエリの列数
ORA-00932 データ型が一致しない 比較している列と値の型
ORA-01722 数値変換できない文字列がある 暗黙変換、WHERE句、JOIN条件

列数や値の数が合わない場合は、ORA-00913の原因と解決方法も近い観点で確認できます。型の違いが疑わしい場合は、Oracleデータ型完全ガイドも参考になります。

実務での判断早見表

状況 おすすめ 理由
一度だけの調査SQL 1000件以内に分割 早く試せる
管理画面の検索 分割ORまたは一時表 実装コストと件数次第で選ぶ
定期バッチ 一時表に投入してJOIN 保守しやすく、件数増加に強い
対象IDがDB内にある サブクエリ / EXISTS アプリへIDを持ち出さずに済む
集合同士を比較したい JOIN、EXISTS、集合演算 巨大なIN句よりSQLの意図が明確

集合演算で表現できる処理なら、Oracle集合演算完全ガイドも選択肢になります。

関連ページ

よくある質問

ORA-01795は何件から発生しますか?

1つのリストに1000個を超える式を指定したときに発生します。IN 句では、1000件までは許容され、1001件以上で問題になります。

バインド変数を使えばORA-01795は避けられますか?

避けられません。バインド変数を使っても、1つの IN リスト内の式が1000個を超えるとORA-01795になります。

分割ORで対応してもよいですか?

少量の一時対応なら使えます。ただしSQLが長くなりやすいため、定常処理では一時表やJOIN方式をおすすめします。

一時表を使う場合は毎回CREATE TABLEしますか?

通常は事前にグローバル一時表を作成しておき、処理ごとに対象IDを投入します。毎回DDLを実行する運用は避けるのが一般的です。

IN句をEXISTSに変えれば必ず速くなりますか?

必ず速くなるわけではありません。索引、統計情報、件数、条件によって変わるため、実行計画で確認します。

まとめ

ORA-01795「リストに指定できる式の最大数は1000です」は、IN 句などのリストに1000件を超える値を指定したときに発生します。まずはリストの件数を数え、1000件以内に分割できるか確認しましょう。

ただし、分割ORはあくまで暫定回避です。定期バッチや件数が増える検索では、一時表にIDを入れてJOINする、またはサブクエリやEXISTSに寄せるほうが保守しやすくなります。巨大なIN句を作らない設計にすると、ORA-01795だけでなくパース負荷やSQLの読みにくさも減らせます。