データベースで不要になったレコードを削除するには、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間の構文差異比較
- よくあるエラーと対処法
サンプルデータの準備
この記事では以下の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 の違い
テーブル内の全行を削除する方法には DELETE と TRUNCATE 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 BY と LIMIT を組み合わせて「上位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」という手順を習慣化し、安全にデータベースを運用しましょう。