エラーの内容
一見シンプルな DROP TABLE users; を実行すると、PostgreSQL が急ブレーキをかけます:
ERROR: cannot drop table users because other objects depend on it
DETAIL: constraint orders_user_id_fkey on table orders depends on table users
HINT: Use DROP ... CASCADE to drop the dependent objects too.
これは PostgreSQL が意地悪をしているのではなく、データ整合性の大惨事からあなたを守っているのです。orders テーブルには users を参照する外部キーがあります。users が消えてしまうと、それらの orders 行は何も参照しない状態になります。PostgreSQL はドロップをブロックし、依存オブジェクトをどう扱うかあなたが判断するのを待ちます。
まず影響範囲を把握する
推測しないでください。何かを実行する前に、このクエリでテーブルを参照しているすべてのオブジェクトを確認しましょう:
SELECT
tc.table_name AS dependent_table,
tc.constraint_name,
tc.constraint_type,
kcu.column_name,
ccu.table_name AS referenced_table
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND ccu.table_name = 'users';
出力例:
dependent_table | constraint_name | constraint_type | column_name | referenced_table
-----------------+-----------------------+-----------------+-------------+------------------
orders | orders_user_id_fkey | FOREIGN KEY | user_id | users
profiles | profiles_user_id_fkey | FOREIGN KEY | user_id | users
sessions | sessions_user_id_fkey | FOREIGN KEY | user_id | users
3 つのテーブルが判明しました。何が妨げになっているかが正確にわかります。
方法 1:DROP TABLE ... CASCADE(最速・最も危険)
開発環境やテスト環境で依存オブジェクトを気にしない場合は、1 行で済みます:
DROP TABLE users CASCADE;
CASCADE は users をドロップし、それを参照するすべての外部キー制約を自動的に削除します。依存テーブル — orders、profiles、sessions — は残ります。それらのデータも保持されます。削除されるのは users を参照している FK 制約のみです。
**本番環境での注意:**上記の依存関係クエリを先に実行してください。依存テーブルが 15〜20 個あるスキーマでは、存在を忘れていたリレーションシップを通じてカスケードが波及し、他のクエリが依存する制約が暗黙のうちに削除される可能性があります。実行前に必ず確認しましょう。
方法 2:先に外部キー制約を削除する(より安全)
これは外科的なアプローチです。ブロックしている制約だけを削除します — 依存テーブルとそのデータには一切手をつけません:
-- 各外部キー制約を明示的に削除する
ALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;
ALTER TABLE profiles DROP CONSTRAINT profiles_user_id_fkey;
ALTER TABLE sessions DROP CONSTRAINT sessions_user_id_fkey;
-- これでドロップが成功する
DROP TABLE users;
スキーマを再構成しながら依存テーブルの構造を保持したい場合に有効です。記述量は増えますが、何が変更されたかを正確に把握できます。
方法 3:正しい順序ですべてを削除する
マイグレーションをロールバックする場合は、親テーブルの前に子テーブルを削除します:
-- 依存テーブルを先に、次に親テーブル
DROP TABLE sessions;
DROP TABLE profiles;
DROP TABLE orders;
DROP TABLE users;
トランザクションでラップしてアトミックにしましょう — いずれかのステップが失敗しても、何も削除されません:
BEGIN;
DROP TABLE sessions;
DROP TABLE profiles;
DROP TABLE orders;
DROP TABLE users;
COMMIT;
方法 4:スキーマ全体を一括削除する
これらのテーブルがすべて同じスキーマにあり、完全にリセットしたい場合は、個別の削除をスキップできます:
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
スキーマ内のすべてが消去されます。開発・テスト環境や完全リセット時に限定して使用してください — バックアップなしでは元に戻せません。
修正の確認
ドロップ後、テーブルとその制約が実際に削除されたことを確認します:
-- テーブルが存在しないことを確認
SELECT table_name
FROM information_schema.tables
WHERE table_name = 'users';
-- 0 行が返るはず
-- 'users' を指す孤立した FK 制約が残っていないことを確認
SELECT constraint_name, table_name
FROM information_schema.table_constraints
WHERE constraint_name LIKE '%user%'
AND constraint_type = 'FOREIGN KEY';
-- 0 行が返るはず(または無関係な制約のみ)
方法 2 を選択した場合は、依存テーブルがデータを保持していることを確認しましょう:
SELECT COUNT(*) FROM orders; -- データは残っている
SELECT COUNT(*) FROM profiles; -- データは残っている
次回以降のヒント
- **本番環境でのスキーマ変更前には必ず依存関係クエリを実行する。**後付けではなく習慣にしましょう — 3 秒のクエリが 3 時間のインシデントを防ぎます。
- **マイグレーションスクリプトでは
DROP TABLE IF EXISTS ... CASCADEを使用する。**新規 DB ではエラーにならず、環境をまたいで制約名を手動で管理する必要もありません。 - 制約には明示的な名前をつける。
CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id)と書いておけば、自動生成されたorders_user_id_fkey1のような名前よりALTER TABLE DROP CONSTRAINTで対象を指定しやすくなります。 - **マイグレーションはまずステージング環境でテストする。**依存テーブルが 20 個ある本番スキーマでは CASCADE の挙動が予想外の結果をもたらすことがあり、ステージングで事前に発見できたはずです。
- **依存関係グラフが複雑な場合は GUI を活用する。**pgAdmin や DBeaver でテーブルを右クリック → 「依存関係」から、DDL を実行する前に依存ツリー全体を視覚的に確認できます。

