PostgreSQLでのエラー『 table referenced in 』の原因と対処

PostgreSQLでのエラー『 table referenced in 』の原因と対処

他テーブルの外部キー(FOREIGN KEY)が参照している表をDROPしようとすると、依存関係の保全のために拒否される。正しい手順は「依存の見える化 → 参照元のテーブル/制約を先に処理 → 必要ならCASCADEで一括」と段階的に進める。再現用コード、依存の洗い出しSQL、DROP順序の自動生成、CASCADEの安全運用、パーティションやスキーマ跨りの注意までまとめる。

エラーの意味と発生条件(RESTRICTが既定)

・DROP TABLEは既定でRESTRICT。参照されている表は、参照元FOREIGN KEYが残っている限りDROP不可
・参照は同一スキーマに限らない。他スキーマ・他データベース(dblink/FDWは除く)からのFKは同一DB内で成立
・FKはカタログ依存(pg_constraint/pg_depend)として管理。トリガ無効化やDEFERRED指定では依存チェックを回避できない

最短再現(NG→OK)

-- 例: customers を orders が参照
CREATE TABLE customers (id bigint PRIMARY KEY, name text NOT NULL);
CREATE TABLE orders (
  id bigint PRIMARY KEY,
  customer_id bigint NOT NULL REFERENCES customers(id),
  amount numeric NOT NULL
);

-- NG: 参照されているcustomersをDROP
DROP TABLE customers;
-- ERROR:  cannot drop table customers because other objects depend on it
-- DETAIL: constraint orders_customer_id_fkey on table orders depends on table customers

-- OK1: 参照元テーブルを先にDROP
DROP TABLE orders;
DROP TABLE customers;

-- OK2: 参照元のFKだけ先に外してからDROP
ALTER TABLE orders DROP CONSTRAINT orders_customer_id_fkey;
DROP TABLE customers;

依存関係の可視化(参照元FKを一覧)

-- 指定テーブルを参照しているFKを洗い出す(テーブル名はschema付で)
WITH target AS (
  SELECT 'public.customers'::regclass AS rel
)
SELECT
  con.conname                             AS constraint_name,
  con.conrelid::regclass                  AS referencing_table,
  con.confrelid::regclass                 AS referenced_table,
  pg_get_constraintdef(con.oid)           AS constraint_def
FROM pg_constraint con
JOIN target t ON con.confrelid = t.rel
WHERE con.contype = 'f'
ORDER BY con.conrelid::regclass::text;

解決策1:参照元テーブルを先にDROP(順序を正す)

・「参照元→参照先」の順にDROP
・他にも連鎖がある場合は、上記の一覧で連鎖を辿るか、CASCADEを検討

解決策2:参照元の外部キーのみを先に解除

-- 参照元FKのDROP文を自動生成
WITH target AS (SELECT 'public.customers'::regclass AS rel)
SELECT 'ALTER TABLE '||con.conrelid::regclass||' DROP CONSTRAINT '||quote_ident(con.conname)||';' AS ddl
FROM pg_constraint con JOIN target t ON con.confrelid=t.rel
WHERE con.contype='f';

-- 実行例(生成したDDLを流す)
ALTER TABLE public.orders DROP CONSTRAINT orders_customer_id_fkey;
DROP TABLE public.customers;

・後で元に戻す必要がある場合、pg_get_constraintdefで定義を保存しておく

解決策3:DROP TABLE … CASCADE の使い所と注意

-- 依存する制約やオブジェクトも連鎖的に削除
DROP TABLE public.customers CASCADE;
-- NOTICEで落ちる対象が表示される → 直後にROLLBACKすれば「何が消えるかのプレビュー」に使える
BEGIN;
DROP TABLE public.customers CASCADE;
-- NOTICEを確認
ROLLBACK;  -- 本番前の安全確認として有用

・CASCADEは便利だが影響範囲が広い。必ずトランザクション内で確認→必要ならROLLBACK

効かない回避策(落とし穴)

・ALTER TABLE … DISABLE TRIGGERやSET CONSTRAINTS DEFERREDは実行時チェックの制御。カタログ上の依存関係は残るためDROPの阻害要因は解消しない
・session_replication_role = replica でも同様。DROP時の依存チェックは回避不可

スキーマ跨り/多数テーブルの依存を一括抽出

-- DB全体で「誰が誰を参照しているか」を一覧化
SELECT
  con.conrelid::regclass   AS referencing_table,
  con.confrelid::regclass  AS referenced_table,
  con.conname,
  pg_get_constraintdef(con.oid) AS def
FROM pg_constraint con
WHERE con.contype='f'
ORDER BY referencing_table::text, referenced_table::text;

・スキーマを跨って依存がある場合、DROPの順序計画を作る材料にする

パーティション/継承テーブルの注意点

・パーティション親を参照するFKは親側に定義(バージョンにより挙動差あり)。親DROPにはそのFKの解除が必要
・親をDROPするには、子パーティションのDETACH→個別DROP、または親DROP CASCADEで連鎖削除

-- 親のDROP手順例(安全側)
ALTER TABLE sales DETACH PARTITION sales_2025q1;
DROP TABLE sales_2025q1;
-- 参照FKを解除後に親DROP
ALTER TABLE some_table DROP CONSTRAINT some_fk;
DROP TABLE sales;

代表的な運用フロー(安全版チェックリスト)

1) 対象表をregclassで特定(schema付)
2) 依存FKをSQLで一覧化し、影響範囲をチームで確認
3) 参照元テーブルを先にDROPするか、FKのみDROPするかを決定
4) 必要ならBEGIN内でDROP … CASCADEを試し、NOTICEで確認→ROLLBACK
5) 本番適用:メンテ時間帯に実施し、アプリ接続を停止/限定
6) 必要に応じてFKの再作成スクリプトを適用

FK再作成用スクリプトの保存と復元

-- 1) 参照先=対象表のFK定義を保存
WITH target AS (SELECT 'public.customers'::regclass AS rel)
SELECT con.conrelid::regclass AS referencing_table,
       con.conname,
       'ALTER TABLE '||con.conrelid::regclass||' ADD CONSTRAINT '||
       quote_ident(con.conname)||' '||pg_get_constraintdef(con.oid)||';' AS add_ddl
INTO TEMP TABLE fk_backup
FROM pg_constraint con JOIN target t ON con.confrelid=t.rel
WHERE con.contype='f';

-- 2) FKをDROPして表をDROP
SELECT 'ALTER TABLE '||con.conrelid::regclass||' DROP CONSTRAINT '||quote_ident(con.conname)||';'
FROM pg_constraint con JOIN target t ON con.confrelid=t.rel
WHERE con.contype='f';

DROP TABLE public.customers;

-- 3) 必要なら後で復元
SELECT add_ddl FROM fk_backup;  -- 出力を実行

依存FKのDROPを自動実行(DOブロック)

DO $$
DECLARE r record;
BEGIN
  FOR r IN
    SELECT conrelid::regclass AS tbl, conname
    FROM pg_constraint
    WHERE contype='f' AND confrelid = 'public.customers'::regclass
  LOOP
    EXECUTE format('ALTER TABLE %s DROP CONSTRAINT %I;', r.tbl, r.conname);
  END LOOP;
END$$;
-- その後 DROP TABLE public.customers;

NG→OK 早見表(よくあるパターン)

-- P1: 直接DROP
-- NG: DROP TABLE customers;
-- OK: ALTER TABLE orders DROP CONSTRAINT orders_customer_id_fkey; DROP TABLE customers;

-- P2: 依存が多すぎる
-- NG: 手作業で探す
-- OK: pg_constraint/pg_get_constraintdef で一覧化→自動生成DDL

-- P3: とりあえずCASCADE
-- NG: いきなり本番でCASCADE
-- OK: BEGIN内でCASCADE→NOTICE確認→ROLLBACK→影響承認→本番適用

-- P4: トリガ無効化で回避できるはず
-- NG: ALTER TABLE ... DISABLE TRIGGER ALL; DROP TABLE ...
-- OK: 依存はカタログ管理。FK解除 or 参照元DROP or CASCADEのいずれか

補足:削除ではなく置換/再設計の選択肢

・データ移行が目的なら、新表作成→データコピー→FK先を切替→旧表DROPの順が安全
・ON DELETE CASCADEへ設計変更しておくと、行削除時の運用は簡易になる(表DROPの可否とは別軸)