FOR UPDATEで取得したレコードをそのまま更新する方法（Oracle Database ）

# FOR UPDATE付きカーソルでのレコード更新方法と注意点

## 1. FOR UPDATEカーソル内でのUPDATE利用可否

`SELECT ... FOR UPDATE`句付きのカーソルは、取得した行に排他ロック（Exclusiveロック）をかけます。これにより、同一トランザクション内でフェッチした行を安全に更新できます。ループ内でレコードをUPDATEする方法として、主に次の2通りがあります。

* **`WHERE CURRENT OF`句を使用する方法**：明示的に宣言したカーソル名がある場合、`UPDATE テーブル SET ... WHERE CURRENT OF カーソル名` で現在フェッチしている行を直接更新できます。
* **キーを使ってUPDATEする方法**：フェッチしたレコードの主キー（または行識別子）を使い、`UPDATE テーブル SET ... WHERE 主キー = rec.主キー` という形で更新します。これは暗黙カーソル（`FOR rec IN (...) LOOP`）でも同様に可能です。

## 2. REC変数を使ったUPDATE例

代表的なコード例を示します。

* **主キーで指定してUPDATE**：カーソルのRECから主キーを参照して更新する例です。

  ```plsql
  FOR rec IN (
    SELECT employee_id, salary 
      FROM employees 
     WHERE department_id = 10 
       FOR UPDATE
  ) LOOP
    -- フェッチしたレコードの給与に10%加算して更新（主キーを使用）
    UPDATE employees
       SET salary = rec.salary * 1.10
     WHERE employee_id = rec.employee_id;
  END LOOP;
  COMMIT;
  ```
* **WHERE CURRENT OFを使用する方法**：明示的カーソルを使い、`WHERE CURRENT OF` で更新する例です。

  ```plsql
  DECLARE
    CURSOR cur IS
      SELECT employee_id, salary 
        FROM employees 
       WHERE department_id = 10 
         FOR UPDATE;
    v_emp_id employees.employee_id%TYPE;
    v_sal    employees.salary%TYPE;
  BEGIN
    OPEN cur;
    LOOP
      FETCH cur INTO v_emp_id, v_sal;
      EXIT WHEN cur%NOTFOUND;
      -- カーソルcurの現在行を更新（内部的にROWIDで更新される）
      UPDATE employees
         SET salary = v_sal * 1.10
       WHERE CURRENT OF cur;
    END LOOP;
    CLOSE cur;
    COMMIT;
  END;
  ```

これらの例では、前者はフェッチした`rec.employee_id`をWHERE句で指定し、後者はカーソル名と`CURRENT OF`を使って現在行を更新しています。

## 3. ロックの影響（同時実行性への注意）

* 通常の`SELECT`（`FOR UPDATE`無し）はデータロックを取得しません。そのため、他セッションから同じテーブルや行を参照・更新でき、クエリは常にブロックされません。
* 一方、`SELECT ... FOR UPDATE`は該当行に排他ロックをかけます。これにより、他のセッションはその行を更新・削除できず、ロック解除（コミットまたはロールバック）まで待機が発生します。
* 行ロックは通常トランザクションの終了まで保持されるため、長時間コミットを遅らせると同時実行性能が低下します。大量の行をロックすると他処理の並列性が落ちるため、必要以上の行ロックは避けるべきです。

## 4. カーソル更新時の注意点

* **`WHERE CURRENT OF`句の利用条件**：`WHERE CURRENT OF カーソル名`を使うには、カーソル宣言に必ず`FOR UPDATE`が必要です。この場合、Oracleは内部的に現在行のROWIDで更新を行います。
* **一意のキー指定**：`WHERE CURRENT OF`を使わない手動更新では、フェッチしたレコードの主キーやROWIDをWHERE句で必ず指定します。例えば、`WHERE employee_id = rec.employee_id`のようにしないと、意図しない複数行を更新する恐れがあります。ROWIDを取得しておき、`WHERE ROWID = rec.rowid`とする方法もあります。
* **LOCK対象テーブルの指定**：複数テーブルをJOINしたSELECTの場合、`FOR UPDATE OF カラム`で指定したテーブルのみがロックされます。必要なテーブルだけをロック対象にすることができます。
* **コミットのタイミング**：カーソル処理後は必ず`COMMIT`してロックを解除します。コミットを忘れると行ロックが長時間残り、他セッションからの更新待機やデッドロックの原因になります。

以上のポイントを押さえれば、FOR UPDATE付きカーソルを使って安全に特定カラムを更新できます。

## 参考資料

https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Automatic-Locks-in-DML-Operations.html

https://www.techonthenet.com/oracle/cursors/current_of.php#:~:text=If%20you%20plan%20on%20updating,the%20WHERE%20CURRENT%20OF%20statement

https://www.shift-the-oracle.com/sql/select-for-update.html#:~:text=,FOR%20UPDATE%20%E3%81%A7%20SELECT%20%E3%81%97%E3%81%9F%E5%BE%8C%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B7%E3%83%BC%E3%82%B8%E3%83%A3%E5%87%A6%E7%90%86%E3%81%A7%E5%87%A6%E7%90%86%E4%B8%AD%E3%81%AE%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%8C%E6%9B%B4%E6%96%B0%E3%81%95%E3%82%8C%E3%81%9F%E3%82%8A%E3%80%81%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%82%8A%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E9%98%B2%E3%81%90%E3%81%93%E3%81%A8%E3%81%8C%E5%8F%AF%E8%83%BD%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82

https://docs.oracle.com/cd/E16338_01/appdev.112/b56260/static.htm#:~:text=,%E5%8F%A5%E3%81%AB%E7%8F%BE%E3%82%8C%E3%82%8B%E8%A1%8C%E3%81%AE%E3%81%BF%E3%81%8C%E3%83%AD%E3%83%83%E3%82%AF%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82