SQL DELETE文の使い方|WHERE条件・全件削除・TRUNCATEとの違い

記事の目的

SQL の DELETE 文を使って、テーブルから行を削除できるようになります。

WHERE 句で条件を付けて特定の行だけ消す書き方、全件削除、よく似た TRUNCATEDROP との違い、外部キー制約があるときの挙動、トランザクションでロールバックする方法までを、PostgreSQL・MySQL・SQL Server の製品差に注意しながら整理します。

重要な注意(DELETE はデータを失わせうる破壊的操作です)

DELETE で削除した行は、トランザクションを使わずに実行した場合、原則として元に戻せません。WHERE 句を書き忘れると全件削除になり、復旧はバックアップからのリストアしか方法がありません。

本記事のコードを本番環境で実行する前に、必ずバックアップを取得し、テスト環境で同じ操作を試してから実行してください。実行前には、DELETESELECT に置き換えて影響範囲を確認する、または明示的なトランザクション( BEGIN;ROLLBACK / COMMIT; )で囲む、といった習慣を強くおすすめします。

記事のサンプルテーブルの前提

本記事では、説明用に次のような orders テーブルがある前提で進めます。

  • id :自動採番(注文番号)
  • customer_id :顧客 ID(文字列)
  • amount :金額(整数。NULL を含む可能性あり)
  • status :注文ステータス( 'pending' / 'shipped' / 'cancelled' のいずれか)
  • order_date :注文日(日付型)
目次

最短の答え ── 安全な DELETE 手順

DELETE は元に戻せない可能性があるため、いきなり実行せず、次の順で進めるのが安全です。下の例 PostgreSQL のトランザクション構文で書いています。MySQL / SQL Server の構文差は後述の「トランザクションとロールバック」を参照してください。

SELECT COUNT(*) FROM orders WHERE status = 'cancelled';
SELECT * FROM orders WHERE status = 'cancelled';

BEGIN;
DELETE FROM orders WHERE status = 'cancelled';

SELECT * FROM orders;
SELECT COUNT(*) FROM orders WHERE status = 'cancelled'; -- 0 になっているはず

COMMIT; -- 確定
-- または
ROLLBACK; -- 取り消して元の状態に戻す
  • 手順 1:SELECT で削除対象を確認
  • 手順 2:トランザクションを開始(PostgreSQL の例)
  • 手順 3:DELETE を実行
  • 手順 4:削除後の状態を確認
  • 手順 5:問題なければ COMMIT、間違いなら ROLLBACK

「全件削除」「 TRUNCATE での高速で空にする」は別物として扱うべき操作なので、本文の「全件削除(WHERE を書かない)」「TRUNCATE との違い」「DROP との違い」のセクションで個別に説明します。


ここから先は、DELETE の基本構文・ WHERE 条件・全件削除・ TRUNCATEDROP との違い・外部キー制約・ロールバックまでを順に解説します。

DELETE 構文の基本

DELETE は、テーブルから条件に合う行を削除する DML(データ操作言語)です。

DELETE FROM テーブル名
WHERE 条件;
  • DELETE FROM のあとに、行を消したいテーブル名を書きます。
  • WHERE 句で「どの行を消すか」を指定します。
  • WHERE 句を書かないと全行が削除対象になります。

列を指定する SELECT と違い、DELETE は行単位の削除です。「特定の列だけ消したい」場合は UPDATESET 列 = NULL などで列の値を変えます。

元のテーブルは行単位で変化します。SELECT のように結果を返すだけの操作ではありません。

WHERE 句で条件付き削除

実務でもっとも使うのは、WHERE で対象を絞った削除です。

-- 例1: キャンセル済みの注文を削除
DELETE FROM orders
WHERE status = 'cancelled';

-- 例2: 2026年1月より前の注文を削除
DELETE FROM orders
WHERE order_date < '2026-01-01';

-- 例3: 複数条件を組み合わせる
DELETE FROM orders
WHERE status = 'cancelled'
  AND order_date < '2026-01-01';

WHERE の書き方は SELECT で使うものと同じです。詳しくは別記事「SQL WHERE 句の使い方」を参照してください。

実行前に SELECT で影響範囲を確認する

DELETEWHERE を書いたら、そのまま SELECT に置き換えて件数と内容を確認するのが事故防止の基本です。

-- まず SELECT で確認
SELECT COUNT(*) FROM orders WHERE status = 'cancelled';
SELECT * FROM orders WHERE status = 'cancelled';

-- 想定通りの件数・内容であれば DELETE を実行
DELETE FROM orders WHERE status = 'cancelled';

WHERE 句の条件が思い込みで書かれていないかを、SELECT の結果で必ず一度見てから DELETE に切り替える、というルーティンをおすすめします。

PostgreSQL の RETURNING(補足)

PostgreSQL では DELETE の末尾に RETURNING を付けると、削除した行を結果として返せます。実行内容のログ取りや、削除と同時にバックアップを取りたい場面で便利です。詳しくは PostgreSQL 固有の別記事で扱います。

-- PostgreSQL のみ:削除した行を返す
DELETE FROM orders
WHERE status = 'cancelled'
RETURNING *;

全件削除(WHERE を書かない)

WHERE を付けない DELETE は、テーブルの全行を削除します。書き忘れや誤コピーで本番データを全消しする事故の典型です。

DELETE FROM orders; -- すべての行を削除

次のような工夫で防ぎます。

  • 全件削除のつもりがない場合、WHERE を書く前にエディタ上で DELETE FROM orders WHERE まで先に打ち、WHERE 句を埋めるクセを付ける。
  • 全件削除のつもりでも、必ず明示的なトランザクション( BEGIN;ROLLBACK / COMMIT; )で囲み、件数確認後に COMMIT する。
  • 本番環境では、WHERE のない DELETE を実行しないようにレビューを必須にする。

「本当にテーブルを空にしたい」場合、次節の TRUNCATE も選択肢になります。

TRUNCATE との違い(製品差が大きい)

TRUNCATE TABLE は、テーブル構造を残したまま全行を高速に削除するためのコマンドです。MySQL では DDL として扱われ暗黙的にコミットされますが、PostgreSQL や SQL Server ではトランザクション内でロールバックできるなど、製品によって挙動が異なります。

DELETE との大枠の違いは次の通りです。

観点DELETE FROM テーブル;TRUNCATE TABLE テーブル;
行を消す単位1 行ずつ削除(ログに記録)テーブルの中身を一括で空にする
WHERE 句使える使えない(全件のみ)
大きなテーブルでの速度行数に比例して遅い一般に高速
トリガー(DELETE トリガー)発火する製品により異なる(多くは発火しない)

TRUNCATE の製品差(要確認)

製品ロールバック自動採番のリセットDELETE トリガー外部キー参照時
PostgreSQLトランザクション内で可既定ではリセットしない( RESTART IDENTITY 指定時のみリセット)発火しない(TRUNCATE トリガーが別途ある)参照されていると失敗( CASCADE で連鎖 TRUNCATE 可)
MySQL不可(DDL 扱いで暗黙 COMMIT が発生)AUTO_INCREMENT がリセットされる発火しない参照されていると基本失敗
SQL Serverトランザクション内で可IDENTITY がリセットされる発火しない参照されているテーブルには基本使えない

実務での使い分けの目安は次の通りです。

  • 行の一部だけ削除したい → DELETE + WHERE
  • 全件削除+ロールバック可能性が必要 → DELETE FROM テーブル; をトランザクション内で
  • 全件削除+テーブルをまっさらにしたい・高速にしたい → TRUNCATE TABLE テーブル; (製品差を確認したうえで)

上記の挙動は版や設定で変わります。本番で使う前に、各製品の現行ドキュメントで「ロールバック可否」「自動採番のリセット」「外部キー参照時の挙動」を必ず確認してください。

DROP との違い

「全部消したい」系の操作には DROP TABLE もあります。DELETETRUNCATEDROP の違いを表にまとめます。

操作消えるものテーブル構造主な用途
DELETE条件に合う行残る一部の行を削除( WHERE 必須が原則)
TRUNCATEすべての行残るテーブルを高速に空にする
DROPテーブル自体消えるテーブル定義ごと削除

DROP TABLE は、表のメタデータ・インデックス・トリガー・制約まで丸ごと消す DDL です。よくある事故が「行を全部消したかっただけなのに DROP TABLE してテーブル定義ごと失った」というもので、TRUNCATEDROP を取り違えないように注意します。

外部キー制約と DELETE

orders.customer_idcustomers.id を参照する外部キー制約を持っている、という状態を考えます。逆向きに、customers 側の行を DELETE しようとすると、orders から参照されている行はそのままでは削除できません(外部キー違反のエラー)。

対処は次のいずれかです。

  • 先に orders 側で対応する行を削除してから、customers を削除する
  • 外部キー制約に ON DELETE CASCADE を指定しておく(親を消すと、参照していた子も連鎖削除)
  • 外部キー制約に ON DELETE SET NULL を指定しておく(子の参照列を NULL に書き換える)

ON DELETE CASCADE は便利ですが、意図しない大量削除を引き起こす可能性が高いため、運用ルールと合わせて慎重に設計してください。詳しい外部キー制約の話は別記事「主キーと外部キーとは?違いと設定方法」で扱います。

トランザクションとロールバック

DELETE を本番で実行するときは、明示的なトランザクションで囲み、件数を確認してから確定する、というのが安全です。

-- PostgreSQL の例
BEGIN;
DELETE FROM orders WHERE status = 'cancelled';
SELECT COUNT(*) FROM orders WHERE status = 'cancelled'; -- 0 になるはず
COMMIT; -- 確定
-- ROLLBACK; -- 間違っていたら戻す

製品差

3 製品とも、DELETE を明示的なトランザクションで囲めば ROLLBACK できます。書き方は少し異なります。

製品トランザクションの書き方
PostgreSQLBEGIN;COMMIT; / BEGIN;ROLLBACK;
MySQLSTART TRANSACTION;COMMIT; または BEGIN;COMMIT; (InnoDB エンジンで動作)
SQL ServerBEGIN TRANSACTION;COMMIT; または ROLLBACK;

なお、3 製品とも既定では自動コミット(autocommit)が有効です。明示的にトランザクションを開始しないと、DELETE を実行した時点で確定してしまい、ROLLBACK できません。詳しいトランザクションの話は別記事で扱います。

落とし穴・注意点

以下は本番環境で実際に問題になりやすいポイントです。実行前に必ず確認してください。

WHERE を書き忘れると全件削除

最も多い事故が、DELETE FROM orders WHERE … と書こうとして WHERE 以降を打つ前に Enter を押し、DELETE FROM orders; を実行してしまうパターンです。

予防策として有効なのは次のような習慣です。

  • SELECT で件数を確認してから DELETE に置き換える
  • 必ず明示的なトランザクション( BEGIN;COMMIT; )で囲む
  • 本番ではアプリケーション経由でしか実行しない/レビュー必須にする

NULL の扱い

WHERE 句で NULL を比較するときは、= NULL ではなく IS NULL を使います。これは DELETE でも同じです。

-- amount が NULL の行を削除したい
DELETE FROM orders WHERE amount IS NULL; -- 正しい
DELETE FROM orders WHERE amount = NULL; -- ヒットしない(UNKNOWN になる)

大量削除のロックとパフォーマンス

大量の行を一度に DELETE すると、テーブルや行に対するロックが長時間取られ、他のクエリが待たされることがあります。本番運用では、WHERE で対象を絞り、バッチ単位で繰り返し削除するのが定石です。書き方は製品で異なります。

製品バッチ削除の書き方
MySQLDELETE FROM orders WHERE status = 'cancelled' LIMIT 1000; を繰り返す
SQL ServerDELETE TOP (1000) FROM orders WHERE status = 'cancelled'; を繰り返す
PostgreSQLDELETE 文に直接 LIMIT は付かないため、CTE で対象を絞る: WITH t AS (SELECT id FROM orders WHERE status = 'cancelled' LIMIT 1000) DELETE FROM orders USING t WHERE orders.id = t.id;

その他、次の点もセットで考えます。

  • 大量削除のあと、製品によってはインデックスや統計情報の再構築が必要になる
  • 本当に全件で十分なら、DELETE ではなく TRUNCATE TABLE を検討する

トリガーと連鎖削除

BEFORE DELETE / AFTER DELETE トリガーが設定されていると、DELETE のたびにトリガーが発火します。ON DELETE CASCADE の連鎖削除も同様に他テーブルに影響します。意図しない削除が起きていないかを、トリガー定義と外部キー設定で必ず確認してください。

文字列リテラルはシングルクォート

WHERE status = "cancelled" のようにダブルクォートを使うと、製品によっては「識別子(列名)」と解釈されエラーになります。文字列はシングルクォート ' で囲みます。詳しくは別記事「SQL WHERE 句の使い方」を参照。

まとめ

DELETE 文はテーブルから行を消す基本構文ですが、WHERE の書き忘れによる全件削除・ TRUNCATEDROP との違い(特に TRUNCATE はロールバック可否や自動採番リセットで製品差が大きい)・外部キー制約・トランザクションでのロールバック、の 4 点を押さえれば、現場の削除はほとんど安全に行えます。本番環境では、バックアップ → SELECT で確認 → BEGIN; DELETE; COMMIT; の流れを毎回守ることをおすすめします。

目次