【SQL】DELETE文でデータを削除する方法|WHERE条件・JOIN・サブクエリ・TRUNCATE違い・安全な削除パターンまで完全解説

データベースで不要になったレコードを削除するには、SQLの DELETE文 を使います。単純な1行削除から、WHERE条件・サブクエリ・JOINを使った複雑な削除、さらにはTRUNCATEとの使い分けやトランザクションによる安全な運用まで、実務で必要な知識は多岐にわたります。

この記事では、DELETE文の基本から応用パターン、RDBMS間の構文差異、よくあるエラーと対処法まで 体系的に解説 します。

この記事で学べること

  • DELETE文の基本構文と全行削除の注意点
  • WHERE条件(=, IN, BETWEEN, LIKE, AND/OR)での削除
  • サブクエリを使った削除パターン
  • JOINを使った削除(MySQL / PostgreSQL / SQL Server / Oracle)
  • TRUNCATE TABLE との違いと使い分け
  • DELETE + ORDER BY + LIMIT(MySQL限定)
  • トランザクションで安全に削除する方法
  • CASCADE DELETE(外部キー連動削除)
  • RETURNING句で削除したデータを取得する方法
  • RDBMS間の構文差異比較
  • よくあるエラーと対処法
スポンサーリンク
  1. サンプルデータの準備
  2. DELETE文の基本構文
    1. 特定の1行を削除する(主キー指定)
    2. 全行削除(WHERE句なし)
  3. WHERE条件を使った削除パターン
    1. 等値条件(=)で削除
    2. IN句で複数値を指定して削除
    3. BETWEEN で範囲指定して削除
    4. LIKE でパターンマッチして削除
    5. AND / OR で複数条件を組み合わせて削除
    6. WHERE条件の演算子一覧
  4. サブクエリを使った削除
    1. IN + サブクエリで削除
    2. EXISTS + サブクエリで削除
    3. NOT IN / NOT EXISTS で「該当しないもの」を削除
  5. JOINを使った削除(RDBMS別構文)
    1. MySQL の構文
    2. PostgreSQL の構文
    3. SQL Server の構文
    4. Oracle の構文
    5. RDBMS別 JOIN削除の構文比較
  6. DELETE と TRUNCATE TABLE の違い
  7. DELETE + ORDER BY + LIMIT(MySQL限定)
    1. 他のRDBMSでの同等処理
  8. トランザクションで安全に削除する
  9. CASCADE DELETE(外部キー連動削除)
    1. テーブル定義例
    2. ON DELETE オプション一覧
  10. RETURNING句で削除したデータを取得する
    1. PostgreSQL の RETURNING
    2. 削除データを別テーブルに移動する応用例
    3. Oracle の RETURNING INTO
    4. RDBMS別 RETURNING 対応状況
  11. RDBMS間の構文差異 総まとめ
  12. よくあるエラーと対処法
    1. MySQL「サブクエリで同じテーブルを参照できない」の回避策
  13. 実務での安全な削除パターン
    1. 1. 削除前にSELECTで確認する
    2. 2. バッチ削除(大量データの場合)
    3. 3. 論理削除(ソフトデリート)の検討
    4. 4. バックアップしてから削除
    5. 安全な削除のチェックリスト
  14. まとめ

サンプルデータの準備

この記事では以下の2つのテーブルを使って解説します。

▶ CREATE TABLE + INSERT文(クリックで展開)
-- 社員テーブル
CREATE TABLE employees (
    id         INT PRIMARY KEY,
    name       VARCHAR(50),
    dept       VARCHAR(20),
    salary     INT,
    hire_date  DATE,
    status     VARCHAR(10) DEFAULT 'active'
);

INSERT INTO employees VALUES
(1,  '田中太郎',   '営業',   350000, '2020-04-01', 'active'),
(2,  '鈴木花子',   '開発',   420000, '2019-07-15', 'active'),
(3,  '佐藤一郎',   '営業',   300000, '2021-01-10', 'inactive'),
(4,  '高橋美咲',   '開発',   450000, '2018-03-20', 'active'),
(5,  '山田健太',   '総務',   280000, '2022-06-01', 'active'),
(6,  '伊藤裕子',   '営業',   320000, '2020-11-15', 'inactive'),
(7,  '中村翔太',   '開発',   380000, '2021-09-01', 'active'),
(8,  '小林真理',   '総務',   290000, '2023-02-01', 'active'),
(9,  '渡辺大輔',   '営業',   310000, '2019-12-01', 'inactive'),
(10, '松本彩香',   '開発',   400000, '2022-04-01', 'active');

-- 退職者テーブル
CREATE TABLE resigned (
    emp_id      INT PRIMARY KEY,
    resign_date DATE
);

INSERT INTO resigned VALUES
(3,  '2023-03-31'),
(6,  '2023-09-30'),
(9,  '2024-01-15');

employees テーブル

id name dept salary hire_date status
1 田中太郎 営業 350000 2020-04-01 active
2 鈴木花子 開発 420000 2019-07-15 active
3 佐藤一郎 営業 300000 2021-01-10 inactive
4 高橋美咲 開発 450000 2018-03-20 active
5 山田健太 総務 280000 2022-06-01 active
6 伊藤裕子 営業 320000 2020-11-15 inactive
7 中村翔太 開発 380000 2021-09-01 active
8 小林真理 総務 290000 2023-02-01 active
9 渡辺大輔 営業 310000 2019-12-01 inactive
10 松本彩香 開発 400000 2022-04-01 active

resigned テーブル

emp_id resign_date
3 2023-03-31
6 2023-09-30
9 2024-01-15

DELETE文の基本構文

DELETE文は、テーブルから行を削除するSQL文です。基本構文は以下の通りです。

DELETE文の基本構文
DELETE FROM テーブル名
WHERE 条件;

注意:WHERE句を省略すると、テーブル内のすべての行が削除されます。実務では必ずWHERE条件を指定しましょう。

特定の1行を削除する(主キー指定)

最も安全な削除方法は、主キー(PRIMARY KEY)を指定して1行だけ削除することです。

主キーで1行削除
DELETE FROM employees
WHERE id = 3;

実行結果

1 row deleted.

→ id=3(佐藤一郎)の行が削除されます。

全行削除(WHERE句なし)

WHERE句を省略すると、テーブル内のすべてのデータが削除されます。テーブル自体は残ります。

全行削除(危険)
-- テーブル内の全行を削除(テーブル構造は残る)
DELETE FROM employees;

注意:全行削除はロールバック可能ですが、大量データの場合は非常に時間がかかります。全行削除が目的なら後述の TRUNCATE TABLE の方が高速です。

WHERE条件を使った削除パターン

WHERE句にはさまざまな条件演算子を使えます。代表的なパターンを見ていきましょう。

等値条件(=)で削除

部署が「総務」の社員を削除
DELETE FROM employees
WHERE dept = '総務';

実行結果

2 rows deleted.

→ id=5(山田健太)、id=8(小林真理)が削除されます。

IN句で複数値を指定して削除

指定したIDの社員を一括削除
DELETE FROM employees
WHERE id IN (3, 6, 9);

実行結果

3 rows deleted.

→ id=3, 6, 9 の3行が削除されます。

BETWEEN で範囲指定して削除

給与が28万〜31万の社員を削除
DELETE FROM employees
WHERE salary BETWEEN 280000 AND 310000;

実行結果

4 rows deleted.

→ salary が 280000〜310000 の社員(id=3,5,8,9)が削除されます。
  BETWEENは両端を含みます。

LIKE でパターンマッチして削除

名前が「田」で始まる社員を削除
DELETE FROM employees
WHERE name LIKE '田%';

実行結果

1 row deleted.

→ id=1(田中太郎)が削除されます。

AND / OR で複数条件を組み合わせて削除

営業部かつ非アクティブな社員を削除
DELETE FROM employees
WHERE dept = '営業'
  AND status = 'inactive';

実行結果

3 rows deleted.

→ id=3(佐藤一郎)、id=6(伊藤裕子)、id=9(渡辺大輔)が削除されます。
総務部または給与30万以下の社員を削除
DELETE FROM employees
WHERE dept = '総務'
   OR salary <= 300000;

ポイント:OR条件は思わぬ行が削除対象になることがあるため、事前にSELECTで確認しましょう。

WHERE条件の演算子一覧

演算子 説明
= 等しい WHERE id = 3
!= / <> 等しくない WHERE status <> 'active'
<, >, <=, >= 大小比較 WHERE salary >= 400000
IN (...) リスト内に含まれる WHERE id IN (1, 3, 5)
NOT IN (...) リスト内に含まれない WHERE id NOT IN (1, 2)
BETWEEN a AND b 範囲内(両端含む) WHERE salary BETWEEN 300000 AND 400000
LIKE パターンマッチ WHERE name LIKE '田%'
IS NULL NULLである WHERE salary IS NULL
IS NOT NULL NULLでない WHERE salary IS NOT NULL

サブクエリを使った削除

別テーブルの情報を元に削除する場合、サブクエリ(副問い合わせ)を使います。全RDBMS共通で使える汎用性の高い方法です。

IN + サブクエリで削除

退職者テーブルに存在する社員を employees から削除します。

IN + サブクエリ
DELETE FROM employees
WHERE id IN (
    SELECT emp_id
    FROM resigned
);

実行結果

3 rows deleted.

→ resigned テーブルに emp_id が存在する id=3, 6, 9 が削除されます。

EXISTS + サブクエリで削除

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

EXISTS + サブクエリ
DELETE FROM employees e
WHERE EXISTS (
    SELECT 1
    FROM resigned r
    WHERE r.emp_id = e.id
);

ポイント:EXISTS はサブクエリの結果が1行でも見つかれば TRUE を返します。IN は全件リストを展開するため、サブクエリの結果が大量の場合は EXISTS の方が効率的です。

NOT IN / NOT EXISTS で「該当しないもの」を削除

退職者テーブルに存在しない社員を削除
DELETE FROM employees
WHERE id NOT IN (
    SELECT emp_id
    FROM resigned
);

注意:NOT IN のサブクエリ結果に NULL が含まれると、全体が FALSE になり 1行も削除されません。NULLの可能性がある場合は NOT EXISTS を使いましょう。

JOINを使った削除(RDBMS別構文)

JOINを使った削除は、サブクエリよりも直感的に書ける場合があります。ただし、構文がRDBMSごとに異なるため注意が必要です。

MySQL の構文

MySQL: DELETE + JOIN
DELETE e
FROM employees e
INNER JOIN resigned r ON e.id = r.emp_id;

MySQLでは DELETE の後に 削除対象のエイリアス を指定します。

PostgreSQL の構文

PostgreSQL: DELETE + USING
DELETE FROM employees e
USING resigned r
WHERE e.id = r.emp_id;

PostgreSQLでは USING 句で結合するテーブルを指定し、WHERE で条件を書きます。

SQL Server の構文

SQL Server: DELETE + FROM + JOIN
DELETE e
FROM employees e
INNER JOIN resigned r ON e.id = r.emp_id;

SQL ServerはMySQLと同じ構文が使えます。

Oracle の構文

Oracle: DELETE + サブクエリ(JOINなし)
DELETE FROM employees
WHERE id IN (
    SELECT emp_id FROM resigned
);

OracleにはDELETEにJOIN構文がないため、サブクエリまたはEXISTSを使います。

RDBMS別 JOIN削除の構文比較

RDBMS 構文 特徴
MySQL DELETE e FROM ... JOIN ... DELETE直後にエイリアス指定
PostgreSQL DELETE FROM ... USING ... WHERE ... USING句で結合テーブル指定
SQL Server DELETE e FROM ... JOIN ... MySQLと同じ構文
Oracle DELETE FROM ... WHERE IN (SELECT ...) JOIN不可、サブクエリを使用

DELETE と TRUNCATE TABLE の違い

テーブル内の全行を削除する方法には DELETETRUNCATE TABLE の2つがあります。動作が大きく異なるので、違いを理解した上で使い分けましょう。

TRUNCATE TABLE の構文
TRUNCATE TABLE employees;
項目 DELETE TRUNCATE TABLE
WHERE条件 指定可能 指定不可(全行のみ)
速度 遅い(行ごとに削除) 高速(テーブルを再作成)
ロールバック 可能 不可(多くのRDBMS)
トリガー起動 起動する 起動しない
AUTO_INCREMENT リセットされない リセットされる
ログ記録 行ごとに記録 最小限のログ
外部キー制約 制約チェックあり 参照されている場合は実行不可

使い分けの目安:

  • 条件付き削除 → DELETE
  • テスト後のデータリセット → TRUNCATE
  • ロールバックが必要 → DELETE
  • 大量データの全削除で速度重視 → TRUNCATE

DELETE + ORDER BY + LIMIT(MySQL限定)

MySQLでは、DELETE文に ORDER BYLIMIT を組み合わせて「上位N件だけ削除」できます。

MySQL: 古いデータから3件だけ削除
DELETE FROM employees
WHERE status = 'inactive'
ORDER BY hire_date ASC
LIMIT 3;

実行結果

3 rows deleted.

→ status='inactive' の中で hire_date が古い順に3件が削除されます。

注意:ORDER BY + LIMIT を使った DELETE は MySQL限定 の機能です。PostgreSQL, SQL Server, Oracle では使えません。他のRDBMSでは、サブクエリや CTE(WITH句)を使って同等の処理を実現します。

他のRDBMSでの同等処理

▶ PostgreSQL / SQL Server / Oracle の構文(クリックで展開)
PostgreSQL: CTIDを使った上位N件削除
DELETE FROM employees
WHERE ctid IN (
    SELECT ctid FROM employees
    WHERE status = 'inactive'
    ORDER BY hire_date ASC
    LIMIT 3
);
SQL Server: TOP を使った上位N件削除
DELETE TOP(3) FROM employees
WHERE status = 'inactive';

-- ※ ORDER BY指定不可。順序制御にはサブクエリを使用:
DELETE FROM employees
WHERE id IN (
    SELECT TOP 3 id FROM employees
    WHERE status = 'inactive'
    ORDER BY hire_date ASC
);
Oracle: ROWNUM / FETCH FIRST を使った上位N件削除
DELETE FROM employees
WHERE id IN (
    SELECT id FROM employees
    WHERE status = 'inactive'
    ORDER BY hire_date ASC
    FETCH FIRST 3 ROWS ONLY
);

トランザクションで安全に削除する

実務では、DELETE文を実行する前に トランザクション で囲むことで、誤削除を防ぐことができます。問題があれば ROLLBACK で元に戻せます。

トランザクションを使った安全な削除手順
-- ① トランザクション開始
BEGIN;

-- ② まずSELECTで削除対象を確認
SELECT * FROM employees
WHERE status = 'inactive';

-- ③ 確認できたらDELETE実行
DELETE FROM employees
WHERE status = 'inactive';

-- ④ 結果を再確認
SELECT COUNT(*) FROM employees;

-- ⑤-A 問題なければ確定
COMMIT;

-- ⑤-B 問題があれば取り消し
-- ROLLBACK;

トランザクション手順のまとめ

  • BEGIN(または START TRANSACTION)でトランザクション開始
  • SELECT で削除対象の確認
  • DELETE を実行
  • 結果を確認して COMMIT(確定)または ROLLBACK(取り消し)

ポイント:MySQL では START TRANSACTION も使えます。PostgreSQL は自動的に BEGIN でトランザクションが開始されます。SQL Server は BEGIN TRANSACTION を使います。

CASCADE DELETE(外部キー連動削除)

外部キー制約に ON DELETE CASCADE を設定すると、親テーブルの行を削除した際に、子テーブルの関連行も自動的に削除されます。

テーブル定義例

ON DELETE CASCADE の設定
-- 親テーブル
CREATE TABLE departments (
    id    INT PRIMARY KEY,
    name  VARCHAR(50)
);

-- 子テーブル(外部キーにCASCADE設定)
CREATE TABLE employees (
    id       INT PRIMARY KEY,
    name     VARCHAR(50),
    dept_id  INT,
    FOREIGN KEY (dept_id)
        REFERENCES departments(id)
        ON DELETE CASCADE
);
親テーブルの行を削除 → 子テーブルも連動削除
-- departments の id=1 を削除
DELETE FROM departments
WHERE id = 1;

-- → employees テーブルの dept_id=1 の行も自動削除される

ON DELETE オプション一覧

オプション 動作 用途
CASCADE 子テーブルの行も削除 強い依存関係(注文→明細)
SET NULL 外部キーをNULLに設定 参照は残すが紐付けを解除
SET DEFAULT 外部キーをデフォルト値に設定 デフォルトカテゴリに移動
RESTRICT 子テーブルに行があればエラー 誤削除防止(デフォルト)
NO ACTION RESTRICTと同様(タイミングが異なる場合あり) RDBMS依存

注意:CASCADE DELETE は便利ですが、意図せず大量のデータが連鎖的に削除される危険があります。使用時は必ず影響範囲を確認し、テスト環境で検証しましょう。

RETURNING句で削除したデータを取得する

PostgreSQL と Oracle では、DELETE文に RETURNING 句を付けることで、削除した行のデータを同時に取得できます。ログ記録やバックアップに便利です。

PostgreSQL の RETURNING

PostgreSQL: 削除したデータを返す
DELETE FROM employees
WHERE status = 'inactive'
RETURNING id, name, dept;

実行結果

 id | name     | dept
----+----------+------
  3 | 佐藤一郎 | 営業
  6 | 伊藤裕子 | 営業
  9 | 渡辺大輔 | 営業
(3 rows deleted)

削除データを別テーブルに移動する応用例

RETURNING の結果を INSERT INTO ... SELECT と組み合わせて、削除データをバックアップテーブルに移動できます。

PostgreSQL: CTEで削除データを別テーブルに移動
WITH deleted AS (
    DELETE FROM employees
    WHERE status = 'inactive'
    RETURNING *
)
INSERT INTO employees_archive
SELECT * FROM deleted;

Oracle の RETURNING INTO

Oracle: PL/SQLで削除データを変数に格納
DECLARE
    v_id   NUMBER;
    v_name VARCHAR2(50);
BEGIN
    DELETE FROM employees
    WHERE id = 3
    RETURNING id, name INTO v_id, v_name;

    DBMS_OUTPUT.PUT_LINE('Deleted: ' || v_id || ' - ' || v_name);
END;

RDBMS別 RETURNING 対応状況

RDBMS RETURNING 代替方法
PostgreSQL 対応
Oracle 対応(RETURNING INTO) PL/SQL内で使用
SQL Server OUTPUT句で代替 OUTPUT DELETED.*
MySQL 非対応 SELECT → DELETE の2ステップ

RDBMS間の構文差異 総まとめ

DELETE文に関する主要RDBMSの機能差異を一覧でまとめます。

機能 MySQL PostgreSQL SQL Server Oracle
基本 DELETE
JOIN削除 DELETE e FROM…JOIN USING句 DELETE e FROM…JOIN ✗(サブクエリ)
ORDER BY + LIMIT ✗(TOP)
RETURNING RETURNING OUTPUT句 RETURNING INTO
TRUNCATE ROLLBACK
CASCADE DELETE

よくあるエラーと対処法

DELETE文の実行時に発生しやすいエラーとその対処法をまとめます。

エラー 原因 対処法
Cannot delete or update a parent row: a foreign key constraint fails 子テーブルに参照されている行を削除しようとした 子テーブルのデータを先に削除するか、ON DELETE CASCADEを設定
Lock wait timeout exceeded 他のトランザクションがロック中 ロック解放を待つか、バッチ削除(LIMIT使用)に変更
You are using safe update mode MySQL の sql_safe_updates が ON WHERE句にキーを含めるか、一時的に SET SQL_SAFE_UPDATES = 0
You can't specify target table for update in FROM clause MySQLでDELETE対象テーブルをサブクエリで参照 サブクエリを派生テーブル(サブサブクエリ)でラップ
ORA-02292: integrity constraint violated - child record found Oracleで外部キー制約違反 子テーブルを先に削除するか、CASCADE制約を追加

MySQL「サブクエリで同じテーブルを参照できない」の回避策

エラーになるSQL
-- MySQL ではエラーになる
DELETE FROM employees
WHERE salary < (
    SELECT AVG(salary) FROM employees
);
回避策:派生テーブルでラップ
-- 派生テーブル(サブサブクエリ)でラップすればOK
DELETE FROM employees
WHERE salary < (
    SELECT avg_sal FROM (
        SELECT AVG(salary) AS avg_sal FROM employees
    ) AS tmp
);

実務での安全な削除パターン

本番環境でのDELETE操作は、一度実行すると取り返しがつかない場合があります。以下のベストプラクティスを守りましょう。

1. 削除前にSELECTで確認する

DELETE文のWHERE条件を、まず SELECT文で実行して削除対象を目視確認します。

SELECTで事前確認
-- まずSELECTで確認
SELECT * FROM employees
WHERE status = 'inactive';

-- 削除件数を確認
SELECT COUNT(*) FROM employees
WHERE status = 'inactive';

-- 確認できたらDELETE
DELETE FROM employees
WHERE status = 'inactive';

2. バッチ削除(大量データの場合)

数万〜数百万件を一度に削除すると、ロック・ログ肥大・レプリケーション遅延を引き起こします。LIMITを使って分割削除しましょう。

MySQL: バッチ削除(1000件ずつ)
-- 1000件ずつ削除(影響行が0になるまで繰り返し)
DELETE FROM access_logs
WHERE created_at < '2023-01-01'
LIMIT 1000;

3. 論理削除(ソフトデリート)の検討

物理的にデータを消すのではなく、フラグで削除済みを表現する方法です。データの復元が容易で、監査にも対応できます。

論理削除(ソフトデリート)
-- 物理削除の代わりに削除フラグを立てる
UPDATE employees
SET deleted_at = NOW(),
    is_deleted = 1
WHERE id = 3;

-- データ取得時は削除済みを除外
SELECT * FROM employees
WHERE is_deleted = 0;

4. バックアップしてから削除

削除前にデータを別テーブルにコピーしておけば、万が一の場合に復元できます。

バックアップ → 削除の手順
-- ① バックアップテーブルにコピー
CREATE TABLE employees_backup AS
SELECT * FROM employees
WHERE status = 'inactive';

-- ② 削除実行
DELETE FROM employees
WHERE status = 'inactive';

-- ③ 万が一の場合は復元
-- INSERT INTO employees SELECT * FROM employees_backup;

安全な削除のチェックリスト

DELETE実行前チェックリスト

  • WHERE条件をSELECTで事前確認したか?
  • 削除対象の件数は想定通りか?
  • 外部キー制約の影響(CASCADE)を確認したか?
  • トランザクション内で実行しているか?
  • バックアップは取得済みか?
  • 大量データの場合、バッチ削除にしたか?
  • 本番環境で実行する前にテスト環境で検証したか?

まとめ

SQL DELETE文の基本から応用、安全な運用パターンまで解説しました。最後にポイントを整理します。

カテゴリ ポイント
基本 DELETE FROM + WHERE で条件指定。WHERE省略で全行削除
WHERE条件 =, IN, BETWEEN, LIKE, AND/OR, IS NULL で柔軟に指定
サブクエリ IN / EXISTS で別テーブル参照。EXISTS の方が大量データに有利
JOIN削除 RDBMS によって構文が異なる(MySQL, PostgreSQL, SQL Server, Oracle)
TRUNCATE 全行削除で高速だが、WHERE指定・ロールバック不可
トランザクション BEGIN → SELECT確認 → DELETE → COMMIT/ROLLBACK
CASCADE ON DELETE CASCADE で子テーブルも自動削除。影響範囲に注意
RETURNING PostgreSQL / Oracle で削除データを取得可能
安全な運用 SELECT確認 → トランザクション → バッチ削除 → バックアップ

DELETE文はデータを直接削除する強力な操作です。「SELECT → トランザクション → DELETE → 確認 → COMMIT」という手順を習慣化し、安全にデータベースを運用しましょう。