PostgreSQL行削除完全ガイド!DELETE文の基本から安全な削除方法まで徹底解説

データベース・SQL

「特定の条件に合うデータだけ削除したい」
「間違って全部消しちゃわないか心配…」
「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の比較

特徴DELETETRUNCATE
条件指定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を使いこなしてください!

慎重に、でも恐れずに。正しい知識があれば大丈夫です!

コメント

タイトルとURLをコピーしました