【Oracle】ORA-01427の原因と解決方法|single-row subquery returns more than one row・サブクエリの直し方

【Oracle】ORA-01427の原因と解決方法|single-row subquery returns more than one row・サブクエリの直し方 Oracle

ORA-01427: single-row subquery returns more than one row は、1行だけ返る前提で書いたサブクエリが、実際には2行以上を返したときに発生するOracleエラーです。日本語環境では「単一行副問合せにより2行以上の行が戻されました」と表示されます。

よくあるのは、= (SELECT ...)< (SELECT ...)SET column = (SELECT ...) のように単一値を期待する場所で、サブクエリが複数行を返しているパターンです。

先に結論
ORA-01427は、サブクエリを1行に絞るか、複数行を前提にした演算子へ書き換えると解消できます。複数候補を許すなら INEXISTS、1行だけ選ぶなら条件追加、MAX/MINROW_NUMBER() などを使います。安易に ROWNUM = 1 で隠す前に、なぜ複数行返るのかを確認します。
スポンサーリンク

ORA-01427とは

Oracle公式のエラー説明では、ORA-01427は単一行サブクエリが複数行を返した場合に発生し、ANYALLINNOT IN を使うか、1行だけ返すように問い合わせを書き換えることが対処として案内されています。

書き方 サブクエリに期待する行数 複数行なら
= (SELECT ...) 1行だけ ORA-01427の原因になる
< (SELECT ...) 1行だけ ANY / ALL を検討する
IN (SELECT ...) 複数行でもよい 候補集合として比較できる
EXISTS (SELECT ...) 該当行があるかだけ見る 相関条件で存在判定する

まず原因のサブクエリを単体で実行する

ORA-01427が出たら、まず右側のサブクエリを単体で実行し、何行返っているか確認します。1行のつもりが複数行返っていれば、条件不足か、そもそも複数行を扱う設計にすべきSQLです。

check-subquery-row-count.sql
-- 元のSQLでエラーになる例
SELECT *
FROM orders o
WHERE o.customer_id = (
    SELECT c.customer_id
    FROM customers c
    WHERE c.rank = 'A'
);

-- まずサブクエリを単体で確認する
SELECT c.customer_id
FROM customers c
WHERE c.rank = 'A';

-- 何行返るか数える
SELECT COUNT(*)
FROM customers c
WHERE c.rank = 'A';

サブクエリの基本は Oracleのサブクエリ完全ガイド でも整理しています。ORA-01427は、スカラーサブクエリと複数行サブクエリの使い分けミスとして見ると直しやすいです。

= を IN に書き換えるケース

サブクエリが複数の候補値を返し、そのどれかに一致すればよい場合は、= ではなく IN を使います。これはOracle公式の対処にも沿った直し方です。

equals-to-in.sql
-- NG: サブクエリが複数行返るとORA-01427
SELECT *
FROM orders o
WHERE o.customer_id = (
    SELECT c.customer_id
    FROM customers c
    WHERE c.rank = 'A'
);

-- OK: 複数候補のどれかに一致すればよいならIN
SELECT *
FROM orders o
WHERE o.customer_id IN (
    SELECT c.customer_id
    FROM customers c
    WHERE c.rank = 'A'
);

INEXISTS の使い分けは OracleのIN句記事 でも扱っています。

EXISTSに書き換えるケース

別テーブルに該当行が存在するかを見たいだけなら、EXISTS が自然です。相関条件を書くことで、外側の行ごとに関連行があるかを判定できます。

exists-rewrite.sql
-- OK: 顧客表にランクAの該当顧客が存在する注文を取得
SELECT *
FROM orders o
WHERE EXISTS (
    SELECT 1
    FROM customers c
    WHERE c.customer_id = o.customer_id
      AND c.rank = 'A'
);

EXISTS は、サブクエリが返す値そのものではなく、行が存在するかを見ます。サブクエリの値を外側の式へ直接返す必要がない場合は、ORA-01427を避けやすい書き方です。

JOINに書き換えるケース

サブクエリで値を1つ返すより、関連テーブルを普通に結合したほうが自然な場合もあります。複数行が返ること自体が正しい要件なら、スカラーサブクエリではなくJOINで行として扱います。

join-rewrite.sql
-- NG: 複数の注文がある顧客ではORA-01427になる
SELECT c.customer_id,
       (SELECT o.order_id
        FROM orders o
        WHERE o.customer_id = c.customer_id) AS order_id
FROM customers c;

-- OK: 注文を行として扱うならJOINにする
SELECT c.customer_id,
       o.order_id
FROM customers c
JOIN orders o
  ON o.customer_id = c.customer_id;

JOINにすると行数は増えます。1顧客1行で表示したいのか、注文ごとに1行で表示したいのか、出力粒度を先に決めることが大切です。

条件を追加して1行に絞るケース

本来1行だけ返るべきサブクエリなら、IN に逃がすのではなく、条件を追加して1行に絞るべきです。たとえば有効期間、ステータス、主キー、最新フラグなどが不足していないか確認します。

add-condition.sql
-- NG: 同じ顧客に複数住所があるとORA-01427
SELECT o.order_id,
       (SELECT a.zip_code
        FROM customer_addresses a
        WHERE a.customer_id = o.customer_id) AS zip_code
FROM orders o;

-- OK: 使用する住所を条件で1行に絞る
SELECT o.order_id,
       (SELECT a.zip_code
        FROM customer_addresses a
        WHERE a.customer_id = o.customer_id
          AND a.address_type = 'MAIN'
          AND a.is_current = 'Y') AS zip_code
FROM orders o;

MAX/MINで1行に集約するケース

複数行の中から最大値や最小値を使えばよい要件なら、MAXMIN で1行に集約します。ただし、どの値を採用するのか業務的に正しいかを確認してから使います。

aggregate-to-one-row.sql
-- OK: 最新注文日を1値として返す
SELECT c.customer_id,
       (SELECT MAX(o.order_date)
        FROM orders o
        WHERE o.customer_id = c.customer_id) AS latest_order_date
FROM customers c;

-- OK: 最小単価など、集約の意味が明確な場合に使う
SELECT p.product_id
FROM products p
WHERE p.price = (
    SELECT MIN(p2.price)
    FROM products p2
);

ROW_NUMBERで1行を選ぶケース

最新行、優先度が最も高い行など、並び順に基づいて1行を選ぶなら ROW_NUMBER() を使うと意図を表現できます。単純な ROWNUM = 1 より、どの行を選ぶかが明確になります。

row-number-one-row.sql
-- 顧客ごとの最新住所を1行選ぶ
SELECT o.order_id,
       (
           SELECT zip_code
           FROM (
               SELECT a.zip_code,
                      ROW_NUMBER() OVER (
                          PARTITION BY a.customer_id
                          ORDER BY a.updated_at DESC, a.address_id DESC
                      ) AS rn
               FROM customer_addresses a
               WHERE a.customer_id = o.customer_id
           ) x
           WHERE x.rn = 1
       ) AS zip_code
FROM orders o;

ANY / ALLを使うケース

>< の右側にサブクエリを置き、複数行が返る可能性がある場合は、ANYALL を使って比較の意味を明確にします。

any-all.sql
-- NG: サブクエリが複数行返るとORA-01427
SELECT *
FROM products p
WHERE p.price > (
    SELECT price
    FROM products
    WHERE category_id = 10
);

-- OK: いずれかの価格より高い
SELECT *
FROM products p
WHERE p.price > ANY (
    SELECT price
    FROM products
    WHERE category_id = 10
);

-- OK: すべての価格より高い
SELECT *
FROM products p
WHERE p.price > ALL (
    SELECT price
    FROM products
    WHERE category_id = 10
);

UPDATEでORA-01427が出る場合

UPDATEの SET column = (SELECT ...) でも、サブクエリが複数行を返すとORA-01427になります。更新元テーブルでキーが重複していないか、結合条件が不足していないかを確認します。

update-ora01427.sql
-- NG: 部署コードが重複していると複数行返る
UPDATE employees e
SET department_name = (
    SELECT d.department_name
    FROM departments d
    WHERE d.department_code = e.department_code
);

-- OK: 有効な1行に絞る
UPDATE employees e
SET department_name = (
    SELECT d.department_name
    FROM departments d
    WHERE d.department_code = e.department_code
      AND d.is_current = 'Y'
)
WHERE EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_code = e.department_code
      AND d.is_current = 'Y'
);

OracleのUPDATEやMERGEの基本は OracleのDML完全ガイド も参考になります。

重複データを確認するSQL

本来1行だけ返るべきマスタ参照でORA-01427が出る場合、SQLだけでなくデータの重複が原因のこともあります。参照キーごとに件数を集計して、重複しているキーを確認します。

find-duplicate-source.sql
-- 部署コードが一意である前提なのに複数行あるか確認する
SELECT department_code,
       COUNT(*) AS row_count
FROM departments
GROUP BY department_code
HAVING COUNT(*) > 1;

-- 有効行が複数あるかを確認する
SELECT department_code,
       COUNT(*) AS current_count
FROM departments
WHERE is_current = 'Y'
GROUP BY department_code
HAVING COUNT(*) > 1;

重複がデータ不整合なら、SQLで無理に1行へ丸めるより、マスタ側のユニーク制約やデータクレンジングを検討します。

CASE式の中のサブクエリで発生する場合

CASE 式の THENELSE にサブクエリを書く場合も、1つの値として返すならサブクエリは1行1列でなければなりません。

case-subquery.sql
-- NG: THEN内のサブクエリが複数行返る可能性がある
SELECT c.customer_id,
       CASE
           WHEN c.rank = 'A' THEN (
               SELECT coupon_code
               FROM coupons
               WHERE rank = 'A'
           )
           ELSE NULL
       END AS coupon_code
FROM customers c;

-- OK: 1行に集約する、または別表としてJOINする
SELECT c.customer_id,
       CASE
           WHEN c.rank = 'A' THEN (
               SELECT MAX(coupon_code)
               FROM coupons
               WHERE rank = 'A'
           )
           ELSE NULL
       END AS coupon_code
FROM customers c;

安易なROWNUM = 1に注意

とりあえず AND ROWNUM = 1 を足すとエラー自体は消えることがあります。しかし、どの1行が選ばれるかが不明確だと、結果が安定しない、業務的に誤った値を使う、といった問題が残ります。

直し方 向いている場面 注意点
IN 複数候補のどれかに一致すればよい 件数が多い場合は実行計画も確認する
EXISTS 関連行の存在だけ見たい 相関条件を書き忘れない
MAX/MIN 最大・最小を使う意味がある 業務的に正しい集約か確認する
ROW_NUMBER() 並び順で1行を選ぶ ORDER BY の基準を明確にする
ROWNUM = 1 本当に任意の1行でよい 順序が不明確なまま使わない

修正チェックリスト

手順 確認すること 修正の方向
1 サブクエリを単体実行する 何行返るか確認する
2 複数行でよい要件か判断する IN / EXISTS にする
3 行として増えてよい要件か判断する JOIN にして出力粒度を変える
4 本来1行の要件か判断する 主キー、最新フラグ、有効期間などで絞る
5 代表値でよいか確認する MAX / MIN で集約する
6 順序で1行選ぶか確認する ROW_NUMBER() を使う
7 データ重複がないか確認する マスタのユニーク制約や重複データを調査する

ORA-00936やORA-00933との違い

ORA-01427 は、SQLの構文自体は解析できているが、実行時にサブクエリが複数行を返した状態です。一方、ORA-00936 は式不足、ORA-00933 は句の順序や終わり方の構文エラーです。

構文ミスが疑わしい場合は ORA-00936の原因と解決方法ORA-00933の原因と解決方法 も確認してください。

よくある質問

ORA-01427はなぜ発生しますか?

= (SELECT ...) のように1行だけ返る前提のサブクエリが、実際には2行以上を返すためです。まずサブクエリ単体で件数を確認します。

INに書き換えれば必ず正しいですか?

複数候補のどれかに一致すればよい要件なら正しいです。本来1行だけ返るべきマスタ参照なら、条件不足やデータ重複を直すほうが適切です。

MAXやMINで直してもよいですか?

最大値や最小値を使う業務的な意味がある場合は有効です。単にエラーを消すためだけに使うと、誤った値を採用する可能性があります。

JOINに書き換えるべき場合は?

サブクエリが複数行を返すこと自体が正しく、結果も複数行で表示してよい場合はJOINが向いています。1行にまとめたい場合は、JOINではなく集約やROW_NUMBERで出力粒度を調整します。

ROWNUM = 1で直してよいですか?

任意の1行で本当に問題ない場合以外は避けます。最新行や優先行を選ぶなら、ROW_NUMBER() と明確な ORDER BY を使います。

まとめ

ORA-01427は、単一行を期待するサブクエリが複数行を返したときに発生します。最初にサブクエリを単体で実行し、何行返るか、なぜ複数行になるのかを確認しましょう。

複数行でよいなら INEXISTS、行として増やしてよいなら JOIN、1行に絞るべきなら条件追加、MAX/MINROW_NUMBER() を使います。エラーを隠すのではなく、SQLの意図に合う形へ直すことが大切です。

参考

ORA-01427 – Oracle Database Error Help

Using Subqueries – Oracle SQL Language Reference

Scalar Subquery Expressions – Oracle SQL Language Reference