「特定の条件に合うデータだけ削除したい」
「間違って全部消しちゃわないか心配…」
「DELETE文とTRUNCATEの違いがよく分からない」
こんな悩みはありませんか?
データベースの行削除は、一見簡単そうに見えて、実は注意点がたくさんあります。間違えると取り返しのつかないことになるかもしれません。
この記事では、PostgreSQLでの行削除について、基本的なDELETE文の使い方から、安全に削除する方法、パフォーマンスを考慮した削除、よくある失敗の防ぎ方まで、実例を交えて分かりやすく解説していきます。
DELETE文の基本構文

最も基本的な削除
PostgreSQLで行を削除する基本的な構文です。
基本構文:
DELETE FROM テーブル名
WHERE 条件;
具体例:
-- usersテーブルからid=1の行を削除
DELETE FROM users
WHERE id = 1;
-- ordersテーブルから2023年より前の注文を削除
DELETE FROM orders
WHERE order_date < '2023-01-01';
重要な注意点:
WHERE句を忘れると全行削除されてしまいます!必ずWHERE句を確認してから実行しましょう。
削除前の確認(超重要!)
削除する前に、必ず対象データを確認する習慣をつけましょう。
削除前の確認手順:
-- STEP1: まずSELECTで確認
SELECT * FROM users
WHERE status = 'inactive';
-- STEP2: 件数も確認
SELECT COUNT(*) FROM users
WHERE status = 'inactive';
-- STEP3: 問題なければDELETE
DELETE FROM users
WHERE status = 'inactive';
プロのテクニック:
-- トランザクションで安全に実行
BEGIN;
-- 削除実行
DELETE FROM users
WHERE status = 'inactive';
-- 削除件数を確認(PostgreSQLは削除件数を返す)
-- DELETE 5 のように表示される
-- 問題なければコミット、問題があればロールバック
COMMIT; -- または ROLLBACK;
WHERE句の様々な条件指定
基本的な条件指定
等価条件:
-- 特定の値と一致
DELETE FROM products
WHERE category = 'discontinued';
-- 複数条件(AND)
DELETE FROM users
WHERE status = 'inactive'
AND last_login < '2023-01-01';
-- 複数条件(OR)
DELETE FROM logs
WHERE level = 'debug'
OR created_at < NOW() - INTERVAL '30 days';
範囲指定での削除
数値範囲:
-- 価格が100円未満の商品を削除
DELETE FROM products
WHERE price < 100;
-- 在庫が0〜5の商品を削除
DELETE FROM products
WHERE stock BETWEEN 0 AND 5;
日付範囲:
-- 1年以上前のログを削除
DELETE FROM access_logs
WHERE created_at < CURRENT_DATE - INTERVAL '1 year';
-- 特定期間のデータを削除
DELETE FROM temp_data
WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31';
IN句を使った複数値指定
-- 複数のIDを指定して削除
DELETE FROM users
WHERE id IN (1, 3, 5, 7, 9);
-- サブクエリを使った削除
DELETE FROM orders
WHERE user_id IN (
SELECT id FROM users
WHERE status = 'deleted'
);
LIKE演算子でのパターンマッチ
-- メールアドレスが特定ドメインのユーザーを削除
DELETE FROM users
WHERE email LIKE '%@example.com';
-- 特定のプレフィックスを持つデータを削除
DELETE FROM temp_files
WHERE filename LIKE 'tmp_%';
NULLの扱い
-- NULL値の行を削除
DELETE FROM users
WHERE phone_number IS NULL;
-- NULL以外の行を削除
DELETE FROM products
WHERE description IS NOT NULL
AND status = 'draft';
JOINを使った削除(USING句)
他テーブルの条件で削除
PostgreSQLでは、USING句を使って他のテーブルと結合して削除できます。
基本構文:
DELETE FROM テーブル1
USING テーブル2
WHERE テーブル1.カラム = テーブル2.カラム
AND 条件;
実例:
-- 退会したユーザーの注文を削除
DELETE FROM orders
USING users
WHERE orders.user_id = users.id
AND users.status = 'deleted';
-- 在庫切れ商品のカート項目を削除
DELETE FROM cart_items
USING products
WHERE cart_items.product_id = products.id
AND products.stock = 0;
サブクエリを使った削除
-- 最も古い10件以外を残して削除
DELETE FROM logs
WHERE id NOT IN (
SELECT id FROM logs
ORDER BY created_at DESC
LIMIT 10
);
-- 重複データの削除(最新の1件だけ残す)
DELETE FROM users a
USING users b
WHERE a.email = b.email
AND a.id < b.id;
RETURNING句で削除データを取得
削除したデータを確認
PostgreSQL独自の便利な機能です!
-- 削除したデータを表示
DELETE FROM users
WHERE status = 'inactive'
RETURNING *;
-- 特定のカラムだけ返す
DELETE FROM products
WHERE expired_at < CURRENT_DATE
RETURNING id, name, price;
-- 削除したIDをリストで取得
DELETE FROM orders
WHERE created_at < '2023-01-01'
RETURNING id;
削除データのバックアップ
-- 削除する前にバックアップテーブルに保存
WITH deleted AS (
DELETE FROM users
WHERE last_login < CURRENT_DATE - INTERVAL '2 years'
RETURNING *
)
INSERT INTO users_archive
SELECT * FROM deleted;
TRUNCATEとDELETEの違い
TRUNCATE文の特徴
全行削除する場合の高速な方法です。
基本構文:
-- テーブルの全データを削除
TRUNCATE TABLE table_name;
-- 複数テーブルを一度に削除
TRUNCATE TABLE table1, table2, table3;
-- カスケードオプション(外部キー制約も考慮)
TRUNCATE TABLE users CASCADE;
DELETEとTRUNCATEの比較
特徴 | DELETE | TRUNCATE |
---|---|---|
条件指定 | WHERE句で可能 | 不可(全行削除のみ) |
速度 | 遅い(行単位) | 速い(テーブル単位) |
トランザクション | ロールバック可能 | ロールバック可能※ |
トリガー | 実行される | 実行されない |
RETURNING | 使用可能 | 使用不可 |
自動採番リセット | リセットされない | リセットされる |
※PostgreSQLではTRUNCATEもロールバック可能(他のDBMSと異なる)
使い分けの指針:
-- 条件付き削除や少量削除:DELETE
DELETE FROM logs WHERE created_at < '2023-01-01';
-- テーブル全体のクリア:TRUNCATE
TRUNCATE TABLE temp_data;
-- 自動採番を維持したい場合:DELETE
DELETE FROM users; -- IDの連番は継続
-- 自動採番もリセットしたい:TRUNCATE
TRUNCATE TABLE users RESTART IDENTITY;
カスケード削除と外部キー制約
外部キー制約の設定
-- ON DELETE CASCADEで自動削除
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT NOW()
);
-- ON DELETE SET NULLでNULLに設定
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
author_id INT REFERENCES users(id) ON DELETE SET NULL,
content TEXT
);
-- ON DELETE RESTRICTで削除を制限(デフォルト)
CREATE TABLE products (
id SERIAL PRIMARY KEY,
category_id INT REFERENCES categories(id) ON DELETE RESTRICT
);
カスケード削除の注意点
-- 親テーブルの削除で子テーブルも削除される
DELETE FROM users WHERE id = 1;
-- → ordersテーブルのuser_id=1の行も自動削除
-- カスケードを一時的に無効化
ALTER TABLE orders
DROP CONSTRAINT orders_user_id_fkey;
-- 削除実行
DELETE FROM users WHERE id = 1;
-- 制約を再追加
ALTER TABLE orders
ADD CONSTRAINT orders_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
トランザクションで安全に削除
基本的なトランザクション処理
-- トランザクション開始
BEGIN;
-- 削除処理
DELETE FROM orders
WHERE order_date < '2023-01-01';
-- 確認(まだコミットされていない)
SELECT COUNT(*) FROM orders;
-- 問題なければコミット
COMMIT;
-- 問題があればロールバック
-- ROLLBACK;
セーブポイントの活用
BEGIN;
-- セーブポイント作成
SAVEPOINT before_delete;
-- 1回目の削除
DELETE FROM logs WHERE level = 'debug';
-- やっぱり取り消したい
ROLLBACK TO before_delete;
-- 別の削除を実行
DELETE FROM logs WHERE level = 'trace';
-- 最終的にコミット
COMMIT;
パフォーマンスを考慮した削除
大量データの削除戦略
バッチ削除(少しずつ削除):
-- 一度に1000件ずつ削除
DO $$
DECLARE
deleted_count INT;
BEGIN
LOOP
DELETE FROM large_table
WHERE created_at < '2023-01-01'
LIMIT 1000;
GET DIAGNOSTICS deleted_count = ROW_COUNT;
-- 削除する行がなくなったら終了
EXIT WHEN deleted_count = 0;
-- 少し待機(システム負荷軽減)
PERFORM pg_sleep(0.1);
END LOOP;
END $$;
インデックスの活用
-- WHERE句で使うカラムにインデックスを作成
CREATE INDEX idx_created_at ON logs(created_at);
-- 削除が速くなる
DELETE FROM logs WHERE created_at < '2023-01-01';
-- 複合インデックスの活用
CREATE INDEX idx_user_status ON users(status, last_login);
DELETE FROM users
WHERE status = 'inactive'
AND last_login < '2023-01-01';
よくある失敗と対策
WHERE句を忘れる(全削除)
予防策:
-- まずSELECTで書いて
SELECT * FROM users WHERE status = 'inactive';
-- SELECTをDELETEに変更
DELETE FROM users WHERE status = 'inactive';
-- エイリアスを使って事故防止
DELETE FROM users u -- エイリアスがあるとWHERE忘れでエラー
WHERE u.status = 'inactive';
トランザクションの未使用
-- 危険:直接削除
DELETE FROM important_table WHERE id = 1;
-- 安全:トランザクション使用
BEGIN;
DELETE FROM important_table WHERE id = 1;
-- 確認してから
COMMIT;
外部キー制約エラー
-- エラーになるケース
DELETE FROM categories WHERE id = 1;
-- ERROR: update or delete on table "categories" violates foreign key constraint
-- 解決方法1:子テーブルから先に削除
DELETE FROM products WHERE category_id = 1;
DELETE FROM categories WHERE id = 1;
-- 解決方法2:CASCADE使用
DELETE FROM categories WHERE id = 1 CASCADE;
よくある質問と回答
Q1:削除したデータを復元できる?
回答:
COMMITした後は基本的に復元できません。対策:
- トランザクション使用
- バックアップテーブルへの退避
- 論理削除(deleted_atカラム)の検討
Q2:削除が遅い
回答:
- WHERE句のカラムにインデックス作成
- バッチ処理で分割削除
- VACUUMで不要領域を解放
- TRUNCATEの検討(全削除の場合)
Q3:削除してもディスク容量が減らない
回答:
PostgreSQLは削除してもすぐに領域を解放しません。
-- 手動でVACUUM実行
VACUUM FULL table_name;
-- 自動VACUUMの設定確認
SHOW autovacuum;
まとめ:安全で効率的な行削除を!
PostgreSQLでの行削除について、基本から応用まで解説しました。
重要ポイントのおさらい:
- 削除前は必ずSELECTで確認
- トランザクションで安全に実行
- WHERE句を忘れずに!
- 大量削除はバッチ処理で
削除の基本手順:
-- 1. トランザクション開始
BEGIN;
-- 2. 対象確認
SELECT * FROM table_name WHERE condition;
-- 3. 削除実行
DELETE FROM table_name WHERE condition;
-- 4. 結果確認してコミット
COMMIT; -- または ROLLBACK;
用途別の使い分け:
- 条件付き削除:DELETE文
- 全行削除:TRUNCATE文
- 関連データも削除:CASCADE
- 削除せず無効化:論理削除
データの削除は取り返しがつかない操作です。この記事で紹介した安全対策を実践して、安心してPostgreSQLを使いこなしてください!
慎重に、でも恐れずに。正しい知識があれば大丈夫です!
コメント