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 列に含まれている必要があります(キー保存ビュー)。条件を満たさない場合は
インラインビュー経由の 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)完全ガイドも参照してください。

