【Oracle】更新するデータの件数を指定する方法|ROWNUM・ROW_NUMBER・FETCH FIRSTの使い分け

OracleのUPDATE文で「先頭の100件だけ更新したい」「特定の条件で並べ替えた上位N件だけ更新したい」と思ったことはありませんか?

MySQLなら UPDATE ... LIMIT 100 で簡単にできますが、OracleにはLIMIT句がありません。代わりに ROWNUMROW_NUMBER()FETCH FIRST N ROWS ONLY(12c以降)などを使って件数を制限します。

この記事では、件数を指定してUPDATEする全パターンを、基本から実務レベルまで網羅的に解説します。

この記事でわかること

  • ROWNUM を使った基本的な件数制限UPDATE
  • ソート後のN件を更新する方法(サブクエリ)
  • ROW_NUMBER() OVER() による柔軟な件数指定
  • FETCH FIRST N ROWS ONLY(Oracle 12c+)の活用
  • UPDATE ... WHERE IN (サブクエリ) パターン
  • 大量データのバッチ更新(分割更新)テクニック
  • MySQL / SQL Server / PostgreSQL との比較
  • トランザクションでの安全な更新手順
スポンサーリンク
  1. OracleにはLIMIT句がない
  2. ROWNUM を使った件数制限UPDATE(基本)
    1. ROWNUMの条件の書き方
    2. ROWNUMの重要な制限:ソート順が保証されない
  3. ROWNUM + サブクエリでソート後のN件を更新
    1. WHERE条件付きでソート後のN件を更新
  4. ROW_NUMBER() OVER() を使った件数指定UPDATE
    1. ROW_NUMBER() のメリット
    2. 範囲指定:N件目〜M件目を更新する
    3. PARTITION BY でグループごとにN件更新
  5. FETCH FIRST N ROWS ONLY(Oracle 12c以降)
    1. SELECT での基本構文
    2. UPDATE と組み合わせる
    3. OFFSETと組み合わせる
  6. UPDATE + WHERE IN (サブクエリ) パターン
    1. 基本パターン
    2. EXISTS を使ったパターン
  7. バッチ更新(大量データの分割更新)
    1. PL/SQL によるバッチ更新
    2. バッチサイズの目安
    3. BULK COLLECT + FORALL による高速バッチ更新
  8. 他のRDBMSとの比較
    1. MySQL の書き方
    2. SQL Server の書き方
    3. PostgreSQL の書き方
  9. トランザクションでの安全な更新
    1. 更新前に確認 → 更新 → 検証 → COMMIT の流れ
    2. SAVEPOINT を使った段階的な更新
  10. よくあるエラーと対処法
    1. ORA-00933: SQL command not properly ended
    2. ORA-01427: single-row subquery returns more than one row
    3. ROWNUM = N(N > 1)で結果が0件になる
  11. 実務パターン集
    1. パターン1: テストデータの一部だけ更新
    2. パターン2: 古いデータをアーカイブ用にマーク
    3. パターン3: 優先度の高い順にN件処理
    4. パターン4: データ移行の段階的実行
  12. 方法の選び方チャート
  13. まとめ

OracleにはLIMIT句がない

MySQLやPostgreSQLでは、UPDATE文に LIMIT を付けるだけで更新件数を制限できます。

MySQL(参考:OracleではNG)
-- MySQLでは可能だが、Oracleではエラーになる
UPDATE employees
SET status = 'inactive'
LIMIT 100;  -- ORA-00933: SQL command not properly ended

注意:Oracleでは UPDATE文に LIMIT 句を直接使用できません。代わりに ROWNUM や分析関数を利用します。

Oracleで件数を制限してUPDATEするには、以下のいずれかの方法を使います。

方法 バージョン 特徴
ROWNUM 全バージョン 最もシンプル、ソート不可
ROWNUM + サブクエリ 全バージョン ソート後のN件を更新可能
ROW_NUMBER() OVER() 9i以降 柔軟なソート・条件指定
FETCH FIRST N ROWS ONLY 12c以降 直感的なSQL標準構文
WHERE IN (サブクエリ) 全バージョン 主キー指定で確実な更新

ROWNUM を使った件数制限UPDATE(基本)

ROWNUM はOracleが行を返す際に自動的に付与する疑似列です。WHERE句に指定することで、更新対象の行数を制限できます。

SQL
-- 最初の100件だけを更新する
UPDATE employees
SET status = 'inactive'
WHERE department_id = 10
  AND ROWNUM <= 100;

実行結果

100行が更新されました。

ROWNUMの条件の書き方

ROWNUM は行が返される順番に1から振られるため、条件の書き方にルールがあります。

条件 動作 結果
ROWNUM <= 100 先頭100件 正常動作
ROWNUM < 101 先頭100件 正常動作(同じ意味)
ROWNUM = 1 先頭1件 正常動作
ROWNUM = 5 5番目の行のみ 0件になる(注意)
ROWNUM > 5 6件目以降 0件になる(注意)

注意:ROWNUMは行が返されるときに順番に振られるため、ROWNUM = 5ROWNUM > 5 のように「先頭を飛ばす」条件では結果が0件になります。ROWNUMは必ず1から始まる連番としてしか使えません。

ROWNUMの重要な制限:ソート順が保証されない

ROWNUMWHERE句の評価時に振られるため、ORDER BY より先に番号が付きます。つまり、ROWNUM だけでは「特定の並び順で上位N件」を正確に更新することはできません。

NG例:意図通りに動かない
-- 給与の低い順に100件更新したいが…
UPDATE employees
SET bonus = 5000
WHERE ROWNUM <= 100
ORDER BY salary ASC;  -- ORDER BYはROWNUMの後に評価される!

このSQL自体は構文エラーになります。UPDATE文ではORDER BY句は使えません。ソート後のN件を更新するには、次のセクションで紹介するサブクエリを使う方法が必要です。

ROWNUM + サブクエリでソート後のN件を更新

「給与が低い順に100件だけ更新したい」のように、特定の順序で上位N件を更新するには、サブクエリで先にソートしてからROWNUMで件数を制限します。

SQL
-- 給与が低い順に上位100件を更新
UPDATE employees
SET bonus = 5000
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id
        FROM employees
        ORDER BY salary ASC
    )
    WHERE ROWNUM <= 100
);

ポイント:サブクエリの中で ORDER BY を実行し、外側のクエリで ROWNUM を使う「二重サブクエリ」構造がポイントです。内側でソートしてから外側で件数を絞ることで、正しい順序での上位N件を取得できます。

WHERE条件付きでソート後のN件を更新

SQL
-- 部署10の社員のうち、入社日が古い順に50件を更新
UPDATE employees
SET status = 'senior'
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id
        FROM employees
        WHERE department_id = 10
        ORDER BY hire_date ASC
    )
    WHERE ROWNUM <= 50
);

ROW_NUMBER() OVER() を使った件数指定UPDATE

ROW_NUMBER() はOracle 9i以降で使える分析関数(ウィンドウ関数)で、ソート順を明示的に指定した行番号を振ることができます。ROWNUMより柔軟で、実務ではこの方法が最もよく使われます。

SQL
-- 給与が高い順に上位50件を更新
UPDATE employees
SET bonus = 10000
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id,
               ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
        FROM employees
    )
    WHERE rn <= 50
);

ROW_NUMBER() のメリット

比較項目 ROWNUM ROW_NUMBER()
ソート順の指定 不可(サブクエリ必須) OVER句で直接指定
範囲指定(N件目〜M件目) 困難 rn BETWEEN 51 AND 100 で可能
PARTITION BY 不可 グループごとのN件に対応
可読性 シンプル 意図が明確

範囲指定:N件目〜M件目を更新する

ROW_NUMBER() なら、「51件目から100件目まで」のような範囲指定も簡単にできます。

SQL
-- 51件目〜100件目を更新(ページング的な更新)
UPDATE employees
SET processed_flag = 'Y'
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id,
               ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
        FROM employees
        WHERE processed_flag = 'N'
    )
    WHERE rn BETWEEN 51 AND 100
);

PARTITION BY でグループごとにN件更新

部署ごとに上位3名のボーナスを更新するなど、グループ単位での件数制限ができるのは ROW_NUMBER() ならではの機能です。

SQL
-- 部署ごとに給与上位3名のボーナスを更新
UPDATE employees
SET bonus = 20000
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id,
               ROW_NUMBER() OVER (
                   PARTITION BY department_id
                   ORDER BY salary DESC
               ) AS rn
        FROM employees
    )
    WHERE rn <= 3
);

実行結果

部署10: 3名更新
部署20: 3名更新
部署30: 3名更新
合計: 9行が更新されました。

FETCH FIRST N ROWS ONLY(Oracle 12c以降)

Oracle 12c で導入された FETCH FIRST N ROWS ONLY は、SQL標準に準拠した行制限構文です。SELECT文では直接使えますが、UPDATE文には直接使えないため、サブクエリとして組み合わせます

SELECT での基本構文

SQL(SELECT文での使用例)
-- 給与が高い順に上位10件を取得
SELECT employee_id, last_name, salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;

UPDATE と組み合わせる

SQL
-- FETCH FIRST を使って上位10件を更新(12c以降)
UPDATE employees
SET bonus = 15000
WHERE employee_id IN (
    SELECT employee_id
    FROM employees
    ORDER BY salary DESC
    FETCH FIRST 10 ROWS ONLY
);

OFFSETと組み合わせる

OFFSET を使うと、先頭の何件かをスキップしてから取得できます。

SQL
-- 11番目〜20番目の社員を更新
UPDATE employees
SET review_status = 'pending'
WHERE employee_id IN (
    SELECT employee_id
    FROM employees
    ORDER BY hire_date ASC
    OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY
);

ポイント:FETCH FIRST N ROWS ONLY は内部的に ROW_NUMBER() に変換されるため、パフォーマンスは同等です。可読性に優れているため、12c以降の環境ではこの構文がおすすめです。

UPDATE + WHERE IN (サブクエリ) パターン

主キーを使って更新対象を明確に指定するパターンです。件数制限だけでなく、複雑な条件による更新にも対応できます。

基本パターン

SQL
-- 未処理データのうち最新200件を処理済みに更新
UPDATE orders
SET processed = 'Y',
    processed_date = SYSDATE
WHERE order_id IN (
    SELECT order_id
    FROM (
        SELECT order_id
        FROM orders
        WHERE processed = 'N'
        ORDER BY order_date DESC
    )
    WHERE ROWNUM <= 200
);

EXISTS を使ったパターン

大量データの場合、IN より EXISTS の方がパフォーマンスが良いことがあります。

SQL
-- EXISTS を使った件数制限UPDATE
UPDATE employees e
SET e.bonus = 5000
WHERE EXISTS (
    SELECT 1
    FROM (
        SELECT employee_id
        FROM employees
        WHERE department_id = 20
          AND ROWNUM <= 50
    ) sub
    WHERE sub.employee_id = e.employee_id
);

バッチ更新(大量データの分割更新)

数百万件のデータを一度にUPDATEすると、UNDO領域(ロールバックセグメント)の枯渇やロック長期化の問題が起きます。バッチ更新は大量データを小さな単位に分割して更新するテクニックです。

PL/SQL によるバッチ更新

PL/SQL
DECLARE
    v_batch_size NUMBER := 1000;  -- 1回あたりの更新件数
    v_total      NUMBER := 0;
BEGIN
    LOOP
        UPDATE large_table
        SET status = 'processed'
        WHERE status = 'pending'
          AND ROWNUM <= v_batch_size;

        -- 更新件数を取得
        v_total := v_total + SQL%ROWCOUNT;

        -- 更新対象がなくなったら終了
        EXIT WHEN SQL%ROWCOUNT = 0;

        -- バッチごとにコミット
        COMMIT;

        DBMS_OUTPUT.PUT_LINE('累計更新件数: ' || v_total);
    END LOOP;

    COMMIT;
    DBMS_OUTPUT.PUT_LINE('完了: 合計 ' || v_total || ' 件更新');
END;
/

実行結果

累計更新件数: 1000
累計更新件数: 2000
累計更新件数: 3000
...
完了: 合計 50000 件更新

バッチサイズの目安

データ規模 推奨バッチサイズ 理由
〜10万件 1,000〜5,000 小規模なら一度に更新可能
10万〜100万件 5,000〜10,000 UNDO領域とのバランス
100万件以上 10,000〜50,000 ロック時間とスループットの最適化

注意:バッチ更新でCOMMITを挟むと、途中でエラーが発生した場合に一部のみ更新された状態になります。データの整合性を重視する場合は、1トランザクションで更新するか、処理済みフラグなどで再実行可能な設計にしてください。

BULK COLLECT + FORALL による高速バッチ更新

さらにパフォーマンスを求める場合は、BULK COLLECTFORALL を使います。

PL/SQL(高速バッチ)
DECLARE
    TYPE id_table IS TABLE OF employees.employee_id%TYPE;
    v_ids id_table;
    CURSOR c_emp IS
        SELECT employee_id
        FROM employees
        WHERE status = 'pending';
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp BULK COLLECT INTO v_ids LIMIT 1000;
        EXIT WHEN v_ids.COUNT = 0;

        FORALL i IN 1 .. v_ids.COUNT
            UPDATE employees
            SET status = 'active'
            WHERE employee_id = v_ids(i);

        COMMIT;
    END LOOP;
    CLOSE c_emp;
END;
/

ポイント:FORALL は通常のFORループより数倍〜数十倍高速です。PL/SQLとSQLエンジン間のコンテキストスイッチを削減できるため、大量データの更新に適しています。

他のRDBMSとの比較

Oracle以外のRDBMSでは、UPDATE文に直接件数制限を指定できるものがあります。

RDBMS 構文 備考
Oracle WHERE ROWNUM <= N LIMIT句なし。サブクエリが必要
MySQL UPDATE ... LIMIT N 直接指定可能
SQL Server UPDATE TOP(N) ... 直接指定可能
PostgreSQL WHERE ctid IN (SELECT ... LIMIT N) LIMITはSELECTのみ。サブクエリ必要

MySQL の書き方

MySQL
-- MySQL: UPDATE + LIMIT
UPDATE employees
SET status = 'inactive'
WHERE department_id = 10
ORDER BY hire_date ASC
LIMIT 100;

SQL Server の書き方

SQL Server
-- SQL Server: UPDATE TOP
UPDATE TOP(100) employees
SET status = 'inactive'
WHERE department_id = 10;

PostgreSQL の書き方

PostgreSQL
-- PostgreSQL: サブクエリ + LIMIT
UPDATE employees
SET status = 'inactive'
WHERE ctid IN (
    SELECT ctid
    FROM employees
    WHERE department_id = 10
    ORDER BY hire_date ASC
    LIMIT 100
);

トランザクションでの安全な更新

件数を指定してUPDATEする際は、トランザクションを活用して安全に更新しましょう。

更新前に確認 → 更新 → 検証 → COMMIT の流れ

SQL(安全な更新手順)
-- ステップ1: 更新対象を事前に確認
SELECT COUNT(*) AS target_count
FROM employees
WHERE status = 'pending'
  AND ROWNUM <= 500;

-- ステップ2: 更新を実行
UPDATE employees
SET status = 'active',
    updated_at = SYSDATE
WHERE status = 'pending'
  AND ROWNUM <= 500;

-- ステップ3: 更新件数を確認
-- SQL%ROWCOUNT または SQL*Plusの出力で確認

-- ステップ4: 問題なければコミット、問題があればロールバック
COMMIT;    -- または ROLLBACK;

SAVEPOINT を使った段階的な更新

SQL
-- SAVEPOINTで段階的に更新
SAVEPOINT before_update;

UPDATE employees
SET salary = salary * 1.05
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id
        FROM employees
        WHERE department_id = 10
        ORDER BY performance_score DESC
    )
    WHERE ROWNUM <= 20
);

-- 結果を確認して問題があれば部分ロールバック
-- ROLLBACK TO before_update;

-- 問題なければコミット
COMMIT;

よくあるエラーと対処法

ORA-00933: SQL command not properly ended

UPDATE文に LIMITORDER BY を直接使おうとした場合に発生します。

エラー例と対処
-- NG: UPDATE文にLIMITは使えない
UPDATE employees SET status = 'X' LIMIT 100;
-- ORA-00933: SQL command not properly ended

-- OK: ROWNUMで件数を制限
UPDATE employees SET status = 'X' WHERE ROWNUM <= 100;

ORA-01427: single-row subquery returns more than one row

サブクエリが複数行を返している場合に発生します。= ではなく IN を使ってください。

エラー例と対処
-- NG: = は単一行しか受け取れない
UPDATE employees
SET bonus = 5000
WHERE employee_id = (
    SELECT employee_id FROM employees WHERE ROWNUM <= 10
);
-- ORA-01427: single-row subquery returns more than one row

-- OK: IN を使う
UPDATE employees
SET bonus = 5000
WHERE employee_id IN (
    SELECT employee_id FROM employees WHERE ROWNUM <= 10
);

ROWNUM = N(N > 1)で結果が0件になる

エラー例と対処
-- NG: ROWNUM = 5 は結果が0件になる
UPDATE employees SET bonus = 5000 WHERE ROWNUM = 5;
-- 0行が更新されました(意図と異なる)

-- OK: ROW_NUMBER() を使って特定の行を指定
UPDATE employees
SET bonus = 5000
WHERE employee_id IN (
    SELECT employee_id
    FROM (
        SELECT employee_id,
               ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
        FROM employees
    )
    WHERE rn = 5
);

実務パターン集

パターン1: テストデータの一部だけ更新

SQL
-- テスト用に先頭10件だけステータスを変更
UPDATE test_orders
SET order_status = 'SHIPPED',
    ship_date = SYSDATE
WHERE order_status = 'PENDING'
  AND ROWNUM <= 10;

パターン2: 古いデータをアーカイブ用にマーク

SQL
-- 作成日が古い順に1000件をアーカイブ対象に
UPDATE log_table
SET archive_flag = 'Y'
WHERE log_id IN (
    SELECT log_id
    FROM (
        SELECT log_id
        FROM log_table
        WHERE archive_flag = 'N'
        ORDER BY created_at ASC
    )
    WHERE ROWNUM <= 1000
);

パターン3: 優先度の高い順にN件処理

SQL
-- 優先度が高い順に50件を処理中に変更
UPDATE task_queue
SET status = 'PROCESSING',
    started_at = SYSDATE
WHERE task_id IN (
    SELECT task_id
    FROM (
        SELECT task_id,
               ROW_NUMBER() OVER (
                   ORDER BY priority DESC, created_at ASC
               ) AS rn
        FROM task_queue
        WHERE status = 'WAITING'
    )
    WHERE rn <= 50
);

パターン4: データ移行の段階的実行

PL/SQL
-- データ移行: 新カラムに値を設定(段階的実行)
DECLARE
    v_count NUMBER;
BEGIN
    LOOP
        UPDATE customers
        SET full_name = last_name || ' ' || first_name
        WHERE full_name IS NULL
          AND ROWNUM <= 5000;

        v_count := SQL%ROWCOUNT;
        COMMIT;
        EXIT WHEN v_count = 0;
    END LOOP;
END;
/

方法の選び方チャート

用途に応じて最適な方法を選びましょう。

やりたいこと おすすめの方法
単純に先頭N件を更新 WHERE ROWNUM <= N
ソート後の上位N件を更新 ROWNUM + サブクエリ or ROW_NUMBER()
N件目〜M件目の範囲指定 ROW_NUMBER() + BETWEEN
グループごとに上位N件 ROW_NUMBER() + PARTITION BY
12c以降でシンプルに書きたい FETCH FIRST N ROWS ONLY
大量データの分割更新 PL/SQL バッチ更新

まとめ

この記事のまとめ

  • OracleにはMySQLのような UPDATE ... LIMIT 構文がない
  • 最もシンプルな方法は WHERE ROWNUM <= N
  • ソート後のN件を更新するにはサブクエリでORDER BYを先に評価する
  • ROW_NUMBER() OVER() は範囲指定やPARTITION BYに対応する最も柔軟な方法
  • Oracle 12c以降なら FETCH FIRST N ROWS ONLY が直感的
  • 大量データはバッチ更新(PL/SQLのLOOP + COMMIT)で分割処理する
  • 本番更新ではSAVEPOINTを活用し、確認してからCOMMITする