CASCADE DELETEは1回だけ


200

いくつかのカスケード削除を実行したいPostgresqlデータベースがあります。ただし、テーブルはON DELETE CASCADEルールで設定されていません。削除を実行してPostgresqlにこれを一度だけカスケードするように指示する方法はありますか?同等のもの

DELETE FROM some_table CASCADE;

この古い質問への答えは、そのような解決策は存在しないように思われますが、念のため、この質問を明示的に尋ねると思いました。


以下の私のカスタム関数を参照してください。特定の制限付きで可能です。
Joe Love

回答:


175

いいえ。1回だけ行うには、カスケードするテーブルの削除ステートメントを記述するだけです。

DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;

12
元のカスケード(再帰)からカスケードされた他の外部キーが存在する可能性があるため、これは必ずしも機能しません。テーブルaがaを参照するbを参照するループに入ることもできます。これを一般的な意味で実現するには、以下の表を参照してください。ただし、いくつかの制限があります。単純なテーブル設定がある場合は、上記のコードを試してください。そうすれば、何をしているのかを理解しやすくなります。
Joe Love

2
シンプルで安全。密度挿入がある場合は、それらを単一のトランザクションで実行する必要があります。
イスマイル・ヤウス

39

あなたが本当にしたい場合 DELETE FROM some_table CASCADE;を意味し、「テーブルからすべての行を削除するsome_tableあなたが使用することができ、」TRUNCATEの代わりにDELETEしてCASCADE、常にサポートされています。ただし、where節で選択的削除を使用する場合TRUNCATEは、十分ではありません。

USE WITH CARE -これはしますすべてのテーブルのすべての行ドロップで外部キー制約持っているsome_tableなど、およびそれらのテーブル上の制約を持っているすべてのテーブルを

PostgresはTRUNCATEコマンドでサポートCASCADEしています:

TRUNCATE some_table CASCADE;

これは、他の同時トランザクションから完全に分離されているわけではありませんが、便利です(つまり、ロールバックできます)。詳細については、ドキュメントをご覧ください。


226
明らかに「いくつかのカスケード削除」≠テーブルからすべてのデータを削除…
lensovet

33
これにより、some_tableに外部キー制約があるすべてのテーブルのすべての行と、それらのテーブルに制約があるすべてのテーブルなどが削除されます。これは非常に危険な可能性があります。
AJP 2013

56
注意してください。これは無謀な答えです。
ヨルダンアルセノ2015

4
誰かがこの回答に削除のフラグを付けました -おそらく彼らがそれに同意しないためです。その場合の正しい行動方針は、フラグではなく、反対票を投じることです。
Wai Ha Lee

7
彼の上に警告があります。それを無視することを選択した場合、誰もあなたを助けることはできません。ここでの「copyPaste」ユーザーは本当の危険だと思います。
BluE

28

主キーに基づいて行を削除する(再帰的な)関数を作成しました。「カスケードの削除時」として制約を作成したくなかったので、これを書きました。(DBAとして)複雑なデータセットを削除できるようにしたいが、プログラマーがすべての影響を熟考せずにカスケード削除できるようにしたくありませんでした。私はまだこの関数をテストしていますので、バグがあるかもしれません-しかし、DBに複数列のプライマリ(したがって外部)キーがある場合は、試さないでください。また、キーはすべて文字列形式で表現できる必要がありますが、その制限がない方法で書き込むこともできます。とにかく、この関数を非常に少しずつ使用していますが、データの値が多すぎるため、すべてのカスケード制約を有効にできません。基本的に、この関数はスキーマ、テーブル名、およびプライマリ値(文字列形式)で渡されます。そして、そのテーブルで外部キーを見つけることから始め、データが存在しないことを確認します。存在する場合は、見つかったデータに対して再帰的に呼び出します。無限ループを防ぐために、すでに削除のマークが付けられているデータの配列を使用します。試してみて、動作を教えてください。注:少し遅いです。私はそれを次のように呼びます: select delete_cascade('public','my_table','1');

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_key varchar, p_recursion varchar[] default null)
 returns integer as $$
declare
    rx record;
    rd record;
    v_sql varchar;
    v_recursion_key varchar;
    recnum integer;
    v_primary_key varchar;
    v_rows integer;
begin
    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_sql := 'select '||rx.foreign_table_primary_key||' as key from '||rx.foreign_table_schema||'.'||rx.foreign_table_name||'
            where '||rx.foreign_column_name||'='||quote_literal(p_key)||' for update';
        --raise notice '%',v_sql;
        --found a foreign key, now find the primary keys for any data that exists in any of those tables.
        for rd in execute v_sql
        loop
            v_recursion_key=rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name||'='||rd.key;
            if (v_recursion_key = any (p_recursion)) then
                --raise notice 'Avoiding infinite loop';
            else
                --raise notice 'Recursing to %,%',rx.foreign_table_name, rd.key;
                recnum:= recnum +delete_cascade(rx.foreign_table_schema::varchar, rx.foreign_table_name::varchar, rd.key::varchar, p_recursion||v_recursion_key);
            end if;
        end loop;
    end loop;
    begin
    --actually delete original record.
    v_sql := 'delete from '||p_schema||'.'||p_table||' where '||v_primary_key||'='||quote_literal(p_key);
    execute v_sql;
    get diagnostics v_rows= row_count;
    --raise notice 'Deleting %.% %=%',p_schema,p_table,v_primary_key,p_key;
    recnum:= recnum +v_rows;
    exception when others then recnum=0;
    end;

    return recnum;
end;
$$
language PLPGSQL;

特に自己参照テーブルでは常に発生します。さまざまな部門にさまざまな管理層がある会社や、一般的な階層分類法について考えてみましょう。はい、私はこの機能がスライスされたパン以来の絶対的な最高のものではないことに同意しますが、それは適切な状況で有用なツールです。
Joe Love

書き換えてIDの配列を受け入れ、IN代わりに演算子を使用するクエリを生成する場合=(セットロジックを使用するための手順)ではなく、サブセレクトを使用してクエリを生成すると、はるかに高速になります。
Hubbitus

2
解決策をありがとう。テストをいくつか作成しましたが、レコードを削除する必要があり、その削除をカスケードするのに問題がありました。あなたの機能は本当にうまくいきました!
フェルナンドカマルゴ2017

1
@JoeLoveどのような速度の問題がありますか?そのような状況では、再帰は私の心の単一の正しい解決策です。
ハビタス

1
@arthurあなたはおそらくそれを達成するために行のいくつかのバージョン-> json->テキストを使用することができましたが、私はそれほど遠くに行きませんでした。Iveは長年にわたり、(潜在的なセカンダリキーを持つ)単一のプライマリキーが多くの理由で良いことを発見しました。
ジョー・ラブ

17

私が正しく理解している場合は、外部キー制約を削除し、新しい制約を追加(カスケード)し、必要なことを実行して、制限する外部キー制約を再作成することで、必要なことができるはずです。

例えば:

testing=# create table a (id integer primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "a_pkey" for table "a"
CREATE TABLE
testing=# create table b (id integer references a);
CREATE TABLE

-- put some data in the table
testing=# insert into a values(1);
INSERT 0 1
testing=# insert into a values(2);
INSERT 0 1
testing=# insert into b values(2);
INSERT 0 1
testing=# insert into b values(1);
INSERT 0 1

-- restricting works
testing=# delete from a where id=1;
ERROR:  update or delete on table "a" violates foreign key constraint "b_id_fkey" on table "b"
DETAIL:  Key (id)=(1) is still referenced from table "b".

-- find the name of the constraint
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id)

-- drop the constraint
testing=# alter table b drop constraint b_a_id_fkey;
ALTER TABLE

-- create a cascading one
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete cascade; 
ALTER TABLE

testing=# delete from a where id=1;
DELETE 1
testing=# select * from a;
 id 
----
  2
(1 row)

testing=# select * from b;
 id 
----
  2
(1 row)

-- it works, do your stuff.
-- [stuff]

-- recreate the previous state
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id) ON DELETE CASCADE

testing=# alter table b drop constraint b_id_fkey;
ALTER TABLE
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete restrict; 
ALTER TABLE

もちろん、メンタルヘルスのために、そのようなものを手順に抽象化する必要があります。


4
外部キーがデータベースの一貫性を損なうことを防ぐことができると仮定すると、これは対処する方法ではありません。ここで「厄介な」エントリを削除できますが、将来的に問題を引き起こす可能性のあるゾンビの破片がたくさん残っています
Sprinterfreak

1
正確にはどのような破片ですか?レコードはカスケードによって削除されるため、矛盾はありません。
ペドロボルヘス

1
「厄介な破片」について心配するのではなく(カスケード制約は引き続き一貫しています)、削除されたレコードがさらに削除されたレコードを必要とする場合、それらの制約を変更する必要があります。同様にカスケードを確実にするために。(または、このシナリオを回避するために上で書いた関数を使用します)...いずれにしても、最後の推奨事項:トランザクションを使用して、それがうまくいかなかった場合にロールバックできるようにします。
Joe Love

7

Palehorseの回答にはコメントできないので、自分の回答を追加しました。Palehorseのロジックは問題ありませんが、ビッグデータセットでは効率が悪くなる可能性があります。

DELETE FROM some_child_table sct 
 WHERE exists (SELECT FROM some_Table st 
                WHERE sct.some_fk_fiel=st.some_id);

DELETE FROM some_table;

列にインデックスがあり、データセットが少数のレコードよりも大きい場合は、より高速です。


7

ええ、他の人が言ったように、便利な「DELETE FROM my_table ... CASCADE」(または同等のもの)はありません。カスケード以外の外部キーで保護された子レコードとそれらの参照先の祖先を削除するには、次のオプションがあります。

  • 子テーブルから始めて、一度に1つのクエリで、すべての削除を明示的に実行します(ただし、循環参照がある場合、これは実行されません)。または
  • すべての削除を1つの(潜在的に大規模な)クエリで明示的に実行します。または
  • カスケードされていない外部キー制約が「ON DELETE NO ACTION DEFERRABLE」として作成されたと想定して、すべての削除を1つのトランザクションで明示的に実行します。または
  • グラフ内の「アクションなし」および「制限」外部キー制約を一時的に削除し、CASCADEとして再作成し、問題の祖先を削除し、外部キー制約を再度削除し、最後にそれらを元どおりに再作成します(したがって、一時的に整合性が弱まります)。あなたのデータ); または
  • おそらく同じくらい楽しい何か。

外部キーの制約を回避することが便利にならないのは意図的なものだと思います。しかし、特定の状況でなぜそれを実行したいのかは理解しています。それがある程度の頻度で実行することであり、DBAの知恵をどこでも活用したい場合は、手順を使用して自動化することができます。

私は数か月前にここに来て、「CASCADE DELETE only once」の質問(元々は10年以上前に尋ねられました!)に対する回答を探しました。Joe Loveの巧妙なソリューション(およびThomas CG de Vilhenaのバリアント)からいくらかマイレージを得ましたが、結局、私のユースケースには特定の要件(テーブル内循環参照の処理など)があり、別のアプローチをとらざるを得ませんでした。そのアプローチは最終的にrecursively_delete(PG 10.10)になりました。

私はしばらくの間、本番環境でrecursively_deleteを使用してきましたが、ようやく、アイデアを求めてここにくるくる回る他の人がそれを利用できるようになるのに十分(自信を持って)自信がついてきました。Joe Loveのソリューションと同様に、データベースのすべての外部キー制約が一時的にCASCADEに設定されているかのように、データのグラフ全体を削除できますが、いくつかの追加機能を提供します。

  • 削除ターゲットとその依存関係のグラフのASCIIプレビューを提供します。
  • 再帰CTEを使用して単一のクエリで削除を実行します。
  • テーブル内およびテーブル間の循環依存関係を処理します。
  • 複合キーを処理します。
  • 「デフォルトの設定」および「nullの設定」制約をスキップします。

エラーが発生します:エラー:配列には偶数の要素が必要です場所:PL / pgSQL関数_recursively_delete(regclass、text []、integer、jsonb、integer、text []、jsonb、jsonb)行15の割り当てSQLステートメント"SELECT * FROM _recursively_delete(ARG_table、VAR_pk_col_names)" PL / pgSQL function recursively_delete(regclass、anyelement、boolean)line 73 at SQL statement
Joe Love

@JoeLoveさん、こんにちは。試していただきありがとうございます。再現する手順を教えてもらえますか?そして、あなたのPGのバージョンは何ですか?
TRL

これが役立つかどうかはわかりません。しかし、私はあなたの関数を作成し、次のコードを実行しました:select recursively_delete( 'dallas.vendor'、1094、false)デバッグの後で、これはすぐに死ぬことがわかりました-つまり、最初の呼び出しのようです複数のことをした後ではなく、機能に。参考までに、PG 10.8
Joe Love

@JoeLove、親切にブランチtrl-fix-array_must_have_even_number_of_elementを試してください(github.com/trlorenz/PG-recursively_delete/pull/2)。
TRL

そのブランチを試したところ、元のエラーは修正されました。悲しいことに、それは私の元のバージョンよりも速くはありません(これは最初にこれを書いてあなたのポイントではなかったかもしれません)。「削除カスケード」で重複する外部キーを作成し、元のレコードを削除し、新しく作成されたすべての外部キーを削除する別の試みに取り組んでいます
Joe Love

3

を使用してこれを自動化できますON DELETE CASCADE。外部キー制約をで定義できます。
私は引用します外部キー制約のマニュアルます

CASCADE 参照された行が削除されると、それを参照している行も自動的に削除されることを指定します。


1
これはOPには対応していませんが、外部キーを持つ行を削除する必要がある場合の計画としては最適です。ベン・フランクリンが言ったように、「1オンスの予防は1ポンドの治療に値する」。
Jesuisme 2017年

1
アプリが多数の兄弟を持つレコードを削除し、小さなエラーの代わりに巨大なデータセットを完全に削除した場合、この解決策は非常に危険であることがわかりました。
ジョー・ラブ

2

私はJoe Loveの答えを受け取り、関数を高速化するのではINなく、サブセレクト付きの演算子を使用してそれを書き直しました=(Hubbitusの提案によると)。

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_keys varchar, p_subquery varchar default null, p_foreign_keys varchar[] default array[]::varchar[])
 returns integer as $$
declare

    rx record;
    rd record;
    v_sql varchar;
    v_subquery varchar;
    v_primary_key varchar;
    v_foreign_key varchar;
    v_rows integer;
    recnum integer;

begin

    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_foreign_key := rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name;
        v_subquery := 'select "'||rx.foreign_table_primary_key||'" as key from '||rx.foreign_table_schema||'."'||rx.foreign_table_name||'"
             where "'||rx.foreign_column_name||'"in('||coalesce(p_keys, p_subquery)||') for update';
        if p_foreign_keys @> ARRAY[v_foreign_key] then
            --raise notice 'circular recursion detected';
        else
            p_foreign_keys := array_append(p_foreign_keys, v_foreign_key);
            recnum:= recnum + delete_cascade(rx.foreign_table_schema, rx.foreign_table_name, null, v_subquery, p_foreign_keys);
            p_foreign_keys := array_remove(p_foreign_keys, v_foreign_key);
        end if;
    end loop;

    begin
        if (coalesce(p_keys, p_subquery) <> '') then
            v_sql := 'delete from '||p_schema||'."'||p_table||'" where "'||v_primary_key||'"in('||coalesce(p_keys, p_subquery)||')';
            --raise notice '%',v_sql;
            execute v_sql;
            get diagnostics v_rows = row_count;
            recnum := recnum + v_rows;
        end if;
        exception when others then recnum=0;
    end;

    return recnum;

end;
$$
language PLPGSQL;

2
これを見て、自己参照制約などとどのようにうまく機能するかを確認する必要があります。私は同様のことを試みましたが、完全に機能させるには至りませんでした。あなたの解決策が私のために働くなら、私はそれを実装するつもりです。これは、パッケージ化してgithubなどに配置する必要がある多くのdbaツールの1つです。
Joe Love

マルチテナントCMS用の中規模データベースがあります(クライアントはすべて同じテーブルを共有します)。私のバージョン( "in"なし)は、実行速度がかなり遅いようです。古いクライアントのすべてのトレースを削除します...速度を比較するために、いくつかのモックアップデータでこれを試してみたいと思います。ユースケースで気付いた速度の違いについて何か言えることはありましたか?
Joe Love

私のユースケースでは、in演算子とサブクエリを使用すると、10倍程度の高速化に気づきました。
Thomas CG de Vilhena

1

カスケードオプションを使用した削除は、外部​​キーが定義されているテーブルにのみ適用されます。削除すると、外部キー制約に違反するために削除できないと表示された場合、カスケードにより問題の行が削除されます。

この方法で関連する行を削除する場合は、最初に外部キーを定義する必要があります。また、トランザクションを開始するように明示的に指示したり、デフォルトを変更したりしない限り、自動コミットが実行されるため、クリーンアップに非常に時間がかかる可能性があることに注意してください。


2
グラントの答えは部分的に間違っています-PostgresqlはDELETEクエリでCASCADEをサポートしていません。postgresql.org/docs/8.4/static/dml-delete.html
Fredrik Wendt

削除クエリでサポートされていない理由はありますか?
Teifion、2010

2
それに応じて設定されていない、つまり外部キー制約がON DELETE CASCADEとして定義されていないテーブルで「カスケードを使用して削除する」方法はありません。これは元々問題でした。
lensovet

この質問に対する答えとして、これは完全に間違っています。一度CASCADEする方法はありません。
ジェレミー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.