【Oracle】INSERT・UPDATE・DELETE完全ガイド|INSERT ALL・RETURNING INTO・インラインビュー更新・TRUNCATE比較まで実例で解説

【Oracle】INSERT・UPDATE・DELETE完全ガイド|INSERT ALL・RETURNING INTO・インラインビュー更新・TRUNCATE比較まで実例で解説 Oracle

Oracle の DML(Data Manipulation Language)は INSERT・UPDATE・DELETE の3文です。基本的な1行操作に加え、Oracle 固有の構文である INSERT ALL・キー保存インラインビューによる UPDATE/DELETE・RETURNING INTO による値の即時取得など、実務で役立つパターンを体系的に押さえておく必要があります。

この記事でわかること

  • INSERT:単一行・複数行・SELECT からの一括挿入・INSERT ALL
  • UPDATE:サブクエリ・インラインビュー(結合 UPDATE)・複数列同時更新
  • DELETE:WHERE EXISTS・インラインビュー DELETE・一部削除のパターン
  • RETURNING INTO:DML 実行直後に影響行の値を PL/SQL 変数に取り込む
  • SQL%ROWCOUNT で影響行数を確認する
  • TRUNCATE と DELETE の違い・使い分け
スポンサーリンク

INSERT:行を挿入する

基本の INSERT(単一行)
-- 全列に値を指定(列名リストを省略できるが、列順に依存するため非推奨)
INSERT INTO departments VALUES (300, 'IT Support', 200, 1700);

-- 列名リストを明示する(推奨:テーブル変更に強い)
INSERT INTO departments (department_id, department_name, manager_id, location_id)
VALUES (300, 'IT Support', 200, 1700);

-- 一部の列のみ(省略列には DEFAULT 値または NULL が入る)
INSERT INTO departments (department_id, department_name)
VALUES (301, 'Training');

-- シーケンスを使った採番
INSERT INTO orders (order_id, customer_id, order_date, status)
VALUES (order_seq.NEXTVAL, 101, SYSDATE, 'PENDING');
INSERT INTO … SELECT(別テーブルからの一括挿入)
-- 別テーブルのデータをそのまま挿入
INSERT INTO employees_archive
SELECT * FROM employees
WHERE hire_date < DATE '2015-01-01';

-- 列を加工しながら挿入
INSERT INTO monthly_summary (dept_id, month, headcount, total_salary)
SELECT
    department_id,
    TO_CHAR(SYSDATE, 'YYYY-MM'),
    COUNT(*),
    SUM(salary)
FROM employees
GROUP BY department_id;

-- 行数の確認(INSERT INTO ... SELECT は何行挿入されたか確認できる)
-- SQL*Plus / SQL Developer では "N 行が作成されました。" と表示される
INSERT ALL:1つの SELECT から複数テーブルに同時挿入
-- 無条件 INSERT ALL: SOURCE テーブルの全行を複数のターゲットに同時挿入
-- SELECT は1回だけ実行され、各 INTO 句に同じ行が挿入される
INSERT ALL
    INTO orders_2023 (order_id, customer_id, amount)
        VALUES (order_id, customer_id, amount)
    INTO orders_archive (order_id, archived_at)
        VALUES (order_id, SYSDATE)
SELECT order_id, customer_id, amount
FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2023;

-- 条件付き INSERT ALL(WHEN 句で挿入先を分岐)
INSERT ALL
    WHEN amount >= 100000 THEN
        INTO orders_large VALUES (order_id, customer_id, amount)
    WHEN amount >= 10000 AND amount < 100000 THEN
        INTO orders_medium VALUES (order_id, customer_id, amount)
    ELSE
        INTO orders_small VALUES (order_id, customer_id, amount)
SELECT order_id, customer_id, amount FROM orders;

-- 条件付き INSERT FIRST(最初にマッチした WHEN のみ実行・複数 INTO への重複なし)
INSERT FIRST
    WHEN amount >= 100000 THEN
        INTO orders_large VALUES (order_id, customer_id, amount)
    WHEN amount >= 10000 THEN
        INTO orders_medium VALUES (order_id, customer_id, amount)  -- 100000以上はここに来ない
    ELSE
        INTO orders_small VALUES (order_id, customer_id, amount)
SELECT order_id, customer_id, amount FROM orders;
INSERT ALL vs INSERT FIRST の違い
INSERT ALL では1行が複数の WHEN 条件を満たす場合、すべての WHEN 句の INTO が実行されます(複数テーブルへの二重挿入が起きる)。一方 INSERT FIRST は最初にマッチした WHEN のみ実行し、残りはスキップします。分類用途には INSERT FIRST が安全です。

UPDATE:行を更新する

基本の UPDATE と複数列の同時更新
-- 単純な UPDATE
UPDATE employees
SET salary = salary * 1.05
WHERE department_id = 50;

-- 複数列を同時に更新
UPDATE employees
SET salary        = salary * 1.1,
    commission_pct = NVL(commission_pct, 0) + 0.01,
    hire_date      = hire_date             -- 変更なし(明示的に書いてもOK)
WHERE job_id = 'SA_REP';

-- サブクエリで取得した値で更新
UPDATE employees e
SET salary = (
    SELECT AVG(salary) * 1.1
    FROM employees
    WHERE department_id = e.department_id
)
WHERE department_id IN (10, 20, 30);
複数列をサブクエリで同時更新(スカラーサブクエリの行セット版)
-- SET (col1, col2) = (SELECT ...) で複数列を1つのサブクエリから更新
UPDATE employees e
SET (salary, job_id) = (
    SELECT new_salary, new_job_id
    FROM salary_adjustments sa
    WHERE sa.employee_id = e.employee_id
)
WHERE EXISTS (
    SELECT 1 FROM salary_adjustments sa
    WHERE sa.employee_id = e.employee_id
);
-- → salary_adjustments に存在する社員だけ、新しい salary と job_id に更新
インラインビュー(結合)UPDATE:別テーブルの値で更新
-- Oracle 独自: キー保存ビューを使った結合 UPDATE
-- "キー保存ビュー" = ベーステーブルの PK が SELECT に含まれているビュー

-- employees の salary を departments.budget に基づいて更新
UPDATE (
    SELECT e.salary, d.budget
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    WHERE d.location_id = 1700    -- インラインビューの条件
) v
SET v.salary = v.salary * 1.05;
-- → employees テーブルの salary だけが更新される(departments は不変)

-- NG パターン(どちらのテーブルも更新不可なビューになる場合)
-- MERGE 文を使えば同様の処理が確実にできる
インラインビュー UPDATE の “キー保存” 条件
インラインビュー経由の UPDATE が成功するには、UPDATE する列が属するテーブルの主キーがインラインビューの SELECT 列に含まれている必要があります(キー保存ビュー)。条件を満たさない場合は ORA-01779: キー保存でない表にマッピングする列は変更できません エラーになります。その場合は MERGE 文を使うと確実に書けます。

DELETE:行を削除する

基本の DELETE と WHERE EXISTS による安全な削除
-- 基本の DELETE
DELETE FROM order_items WHERE order_id = 1001;
DELETE FROM employees WHERE hire_date < DATE '2000-01-01';

-- WHERE EXISTS: 別テーブルに対応する行がある場合だけ削除(安全・高速)
-- IN より EXISTS の方が大量データで高速な場合が多い
DELETE FROM employees e
WHERE EXISTS (
    SELECT 1
    FROM terminated_employees t
    WHERE t.employee_id = e.employee_id
    AND   t.term_date < ADD_MONTHS(SYSDATE, -12)  -- 1年以上前に退職
);

-- WHERE IN: サブクエリが返す値リストに一致する行を削除
DELETE FROM order_items
WHERE order_id IN (
    SELECT order_id FROM orders
    WHERE status = 'CANCELLED'
    AND   order_date < DATE '2023-01-01'
);
-- 注意: IN のサブクエリに NULL が含まれると DELETE 対象が0件になる場合がある
-- → EXISTS を使うと NULL の影響を受けない
インラインビュー DELETE(結合条件で削除)
-- インラインビュー経由での DELETE(Oracle 独自構文)
-- 結合した結果のうち削除対象テーブルの行だけを削除
DELETE FROM (
    SELECT e.rowid AS rid
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    WHERE d.location_id = 9999    -- 閉鎖拠点の部門に属する社員
)
;

-- 別の書き方: ROWID を使うと確実にキー保存の問題を回避できる
DELETE FROM employees
WHERE rowid IN (
    SELECT e.rowid
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    WHERE d.location_id = 9999
);

RETURNING INTO:DML 直後に値を取得する

RETURNING INTO は INSERT・UPDATE・DELETE の実行直後に、影響を受けた行の列値を PL/SQL 変数に格納できます。「更新後の値を SELECT で再取得する」という二度手間を省けます。

INSERT / UPDATE / DELETE で RETURNING INTO を使う
DECLARE
    v_new_id    employees.employee_id%TYPE;
    v_new_sal   employees.salary%TYPE;
    v_old_sal   employees.salary%TYPE;
BEGIN
    -- INSERT RETURNING: 挿入した行の ID などをすぐ取得
    INSERT INTO employees (employee_id, last_name, email, hire_date, job_id, salary)
    VALUES (emp_seq.NEXTVAL, 'Tanaka', 'TANAKA', SYSDATE, 'IT_PROG', 80000)
    RETURNING employee_id, salary INTO v_new_id, v_new_sal;

    DBMS_OUTPUT.PUT_LINE('新規ID: ' || v_new_id || ', 初期給与: ' || v_new_sal);

    -- UPDATE RETURNING: 更新後の値をすぐ変数に格納(SELECT で再取得が不要)
    UPDATE employees
    SET salary = salary * 1.1
    WHERE employee_id = v_new_id
    RETURNING salary INTO v_new_sal;

    DBMS_OUTPUT.PUT_LINE('更新後給与: ' || v_new_sal);

    -- DELETE RETURNING: 削除した行の値を保持(ログに使える)
    DELETE FROM employees
    WHERE employee_id = v_new_id
    RETURNING salary INTO v_old_sal;

    DBMS_OUTPUT.PUT_LINE('削除した給与: ' || v_old_sal);

    ROLLBACK;
END;
/
RETURNING BULK COLLECT INTO で複数行の値を配列に取得
DECLARE
    TYPE t_ids   IS TABLE OF employees.employee_id%TYPE;
    TYPE t_sals  IS TABLE OF employees.salary%TYPE;
    v_ids   t_ids;
    v_sals  t_sals;
BEGIN
    -- 複数行の UPDATE → BULK COLLECT INTO で配列に取得
    UPDATE employees
    SET salary = salary * 1.1
    WHERE department_id = 50
    RETURNING employee_id, salary BULK COLLECT INTO v_ids, v_sals;

    DBMS_OUTPUT.PUT_LINE(v_ids.COUNT || '件を更新しました');

    FOR i IN 1 .. v_ids.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(
            '社員ID: ' || v_ids(i) || ' → 新給与: ' || v_sals(i)
        );
    END LOOP;

    ROLLBACK;
END;
/

SQL%ROWCOUNT:影響行数を確認する

SQL%ROWCOUNT で更新・削除件数をチェックする
DECLARE
    v_count NUMBER;
BEGIN
    UPDATE employees
    SET    salary = salary * 1.05
    WHERE  department_id = 999;   -- 存在しない部門

    v_count := SQL%ROWCOUNT;      -- 直前の DML の影響行数

    IF v_count = 0 THEN
        DBMS_OUTPUT.PUT_LINE('更新対象が見つかりませんでした');
        ROLLBACK;
    ELSE
        DBMS_OUTPUT.PUT_LINE(v_count || '件を更新しました');
        COMMIT;
    END IF;
END;
/

-- DELETE の件数確認
BEGIN
    DELETE FROM order_items WHERE order_id = 9999;
    IF SQL%ROWCOUNT = 0 THEN
        RAISE_APPLICATION_ERROR(-20001, '削除対象が存在しません: order_id=9999');
    END IF;
    COMMIT;
END;
/

TRUNCATE と DELETE の違い・使い分け

比較項目 DELETE TRUNCATE
DML / DDL DML(ROLLBACK 可能) DDL(ROLLBACK 不可・自動コミット)
WHERE 句 条件で一部削除が可能 全件削除のみ(WHERE 不可)
速度 UNDO ログを生成するため遅い UNDO ログなし・HWM リセット・高速
トリガー DELETE トリガーが起動する トリガーは起動しない
インデックス 影響行数分のインデックス更新が発生 インデックスも一括削除・再構築
HWM(高水位標) HWM はリセットされない(FULL SCAN 範囲は変わらない) HWM がリセットされ、FULL SCAN が速くなる
外部キー制約 FK の子テーブルがある場合は ORA-02292 FK の子テーブルがある場合は ORA-02266
TRUNCATE の使い方
-- テーブルの全件削除(高速・ROLLBACK 不可)
TRUNCATE TABLE temp_import_data;

-- パーティション単位での TRUNCATE(12c 以降: 特定パーティションのみ削除)
TRUNCATE TABLE orders_partitioned PARTITION (p_2022);

-- REUSE STORAGE: HWM はリセットせずセグメントを再利用(デフォルトは DROP STORAGE)
TRUNCATE TABLE work_table REUSE STORAGE;

-- TRUNCATE は DDL なので前後に自動 COMMIT が発生する
-- 実行前に未コミットの DML があれば確定されてしまうことに注意

まとめ

  • INSERT ALL:1回の SELECT で複数テーブルに同時挿入。INSERT FIRST で分類挿入
  • インラインビュー UPDATE/DELETE:結合条件で対象を絞る。ORA-01779 が出たら MERGE か ROWID 経由で書き直す
  • RETURNING INTO:INSERT/UPDATE/DELETE 直後に影響行の値を取得。SELECT で再取得する必要がない
  • SQL%ROWCOUNT:直前の DML の影響行数を確認。0件の場合のエラー処理に使う
  • TRUNCATE:全件削除・HWM リセット・高速だが ROLLBACK 不可。DELETE との違いを理解して使い分ける

UPDATE・DELETE の WHERE 条件に EXISTS や IN を使うサブクエリの詳細はサブクエリ完全ガイドを、挿入済みデータの重複防止や整合性保護には制約(CONSTRAINT)完全ガイドも参照してください。