CASCADE DELETE只需一次

Eli*_*ght 175 postgresql

我有一个Postgresql数据库,我想在其上进行一些级联删除.但是,表未使用ON DELETE CASCADE规则进行设置.有什么方法可以执行删除并告诉Postgresql只将它级联一次吗?相当于的东西

DELETE FROM some_table CASCADE;
Run Code Online (Sandbox Code Playgroud)

这个旧问题的答案似乎没有这样的解决方案存在,但我想我明确地问这个问题只是为了确定.

pal*_*rse 153

不能.只需编写一个想要级联的表的delete语句即可.

DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;
Run Code Online (Sandbox Code Playgroud)

  • 这不一定有效,因为可能存在从原始级联(递归)级联的其他外键.你甚至可以进入一个循环,其中表a引用b指的是a.要在一般意义上实现这一点,请参阅下表,但它有一些限制.如果你有一个简单的表设置然后尝试上面的代码,它更容易理解你正在做什么. (9认同)
  • 简单,安全。如果有密度插入,则应在单个事务中运行它们。 (2认同)

Dan*_*anC 43

如果您真的想要 DELETE FROM some_table CASCADE; " 删除表中的所有行some_table ",则可以使用TRUNCATE而不是DELETE并且CASCADE始终支持.但是,如果要使用带有where子句的选择性删除,TRUNCATE则不够好.

USE WITH CARE - 这将删除所有具有外键约束的表的所​​有行以及some_table对这些表有约束的所有表等.

Postgres的支持CASCADETRUNCATE命令:

TRUNCATE some_table CASCADE;
Run Code Online (Sandbox Code Playgroud)

这是事务性的(即可以回滚),尽管它与其他并发事务没有完全隔离,并且还有其他一些注意事项.阅读文档了解详细信息.

  • 显然"一些级联删除"≠丢弃表中的所有数据...... (194认同)
  • 谨防.这是一个鲁莽的答案. (47认同)
  • 这将删除some_table上具有外键约束的所有表的所有行以及对这些表具有约束的所有表等...这可能非常危险. (28认同)
  • 我只想提一下,我在一家相当大的初创公司(> 1 亿资金)的生产环境中执行了此操作,它删除了我们所有的用户以及他们的所有订单历史记录。我什至不知道为什么 Postgres 允许这种语法。至少应将其命名为 CASCADE FOOTGUN TRUNCATE ALL TABLES FOREIGN KEYS。我们从备份中恢复,但这是痛苦和有压力的,因为新用户不断加入和订购,所以我们必须对涉及的每个表增量地执行此操作,并手动解决冲突。我没有失去工作(尊重!),但我确实向整个部门提交了详细的事后分析。 (7认同)
  • 对于在生产中运行此线程但不了解它的人;请出于对所有神圣事物的热爱 - 在您的产品环境中运行任何内容(尤其是删除)之前先进行自我教育!还; 在提交之前使用事务并验证结果! (4认同)
  • 有人[将这个答案标记为删除](https://stackoverflow.com/review/low-quality-posts/20215041)-大概是因为他们不同意。在这种情况下,正确的做法是降票而不是标记。 (3认同)
  • 他上面有警告。如果您选择忽略这一点,那么没有人可以帮助您。我认为您的“ copyPaste”用户是这里的真正危险。 (3认同)
  • 如果有这种用例,我会找出需要截断的表的列表,并为每个表编写一个单独的TRUNCATE。为了非常明确地说明正在完成的所有操作。 (3认同)
  • 测试拆解太棒了! (3认同)
  • 我从字面上搜索了“DELETE FROM some_table CASCADE”,因为这就是我想要做的,并找到了最准确的答案。从我这里投票。 (2认同)

Joe*_*ove 25

我写了一个(递归)函数来删除任何基于其主键的行.我写这个是因为我不想创建我的约束作为"删除级联".我希望能够删除复杂的数据集(作为DBA),但不允许我的程序员能够在不考虑所有影响的情况下级联删除.我还在测试这个功能,因此可能存在错误 - 但如果您的数据库具有多列主要(因此是外部)键,请不要尝试它.此外,键都必须能够以字符串形式表示,但它可以以没有该限制的方式编写.我无论如何都非常谨慎地使用这个函数,我过分重视我的数据以启用对所有内容的级联约束.基本上这个函数在模式,表名和主值(以字符串形式)中传递,它将首先在该表上找到任何外键并确保数据不存在 - 如果存在,则以递归方式调用自己查找的数据.它使用已标记为删除的数据数组来防止无限循环.请测试一下,让我知道它对你有用.注意:它有点慢.我称之为: 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;
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你的解决方案.我写了一些测试,我需要删除一条记录,但我无法级联删除.你的功能非常好用! (2认同)

Rys*_*opa 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
Run Code Online (Sandbox Code Playgroud)

当然,为了您的心理健康,您应该将这样的东西抽象到程序中.

  • 假设外键可以防止做事使数据库不一致,这不是处理方法.您现在可以删除"讨厌的"条目,但是您将留下许多可能在将来导致问题的僵尸碎片 (4认同)

TRL*_*TRL 10

是的,正如其他人所说,没有方便的 'DELETE FROM my_table ... CASCADE'(或等效的)。要删除非级联外键保护的子记录及其引用的祖先,您的选项包括:

  • 显式执行所有删除操作,一次一个查询,从子表开始(尽管如果您有循环引用,这将不会生效);或者
  • 在单个(可能是大量)查询中显式执行所有删除;或者
  • 假设您的非级联外键约束创建为“ON DELETE NO ACTION DEFERRABLE”,则在单个事务中显式执行所有删除;或者
  • 暂时删除图中的“no action”和“restrict”外键约束,将它们重新创建为 CASCADE,删除违规的祖先,再次删除外键约束,最后重新创建它们原来的样子(从而暂时削弱了您的数据);或者
  • 一些东西可能同样有趣。

我认为,绕过外键约束是故意不方便的;但我确实理解为什么在特定情况下你会想要这样做。如果这是您经常做的事情,并且如果您愿意在任何地方蔑视 DBA 的智慧,那么您可能希望通过一个过程使其自动化。

几个月前我来到这里寻找“仅一次级联删除”问题的答案(最初是在十多年前提出的!)。我从 Joe Love 的聪明解决方案(以及 Thomas CG de Vilhena 的变体)中获得了一些进展,但最终我的用例有特殊要求(例如处理表内循环引用),这迫使我采取不同的方法。这种方法最终变成了recursively_delete (PG 10.10)。

我已经在生产中使用 recursively_delete 一段时间了,现在,终于感到(谨慎地)有足够的信心将它提供给可能最终在这里寻找想法的其他人。与 Joe Love 的解决方案一样,它允许您删除整个数据图,就好像数据库中的所有外键约束都暂时设置为 CASCADE,但提供了一些附加功能:

  • 提供删除目标及其依赖关系图的 ASCII 预览。
  • 使用递归 CTE 在单个查询中执行删除。
  • 处理循环依赖、表内和表间。
  • 处理复合键。
  • 跳过“设置默认值”和“设置空值”约束。


Grz*_*bek 6

我无法评论苍白的答案,所以我添加了自己的答案.Palehorse logick可以,但是大数据集的效率可能会很差.

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;
Run Code Online (Sandbox Code Playgroud)

如果您在列上有索引并且数据集大于几个记录则会更快.


ati*_*ruz 5

您可以使用自动执行此操作,您可以使用ON DELETE CASCADE.
我引用了外键约束手册

CASCADE 指定当删除引用的行时,引用它的行也应自动删除。

  • 我发现,如果您的应用程序删除了具有大量同级的记录,并且您永久删除了一个巨大的数据集,而不是一个小错误,那么此解决方案可能非常危险。 (3认同)
  • 尽管这没有解决OP问题,但是对于何时需要删除具有外键的行来说这是一个很好的计划。正如本·富兰克林所说,“一分预防胜于一分治疗”。 (2认同)

Tho*_*ena 5

我采用了 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;
Run Code Online (Sandbox Code Playgroud)

  • 我将不得不看看这个,看看它在自引用约束等方面的工作效果如何。我尝试做类似的事情,但未能完全发挥作用。如果您的解决方案对我有用,我将实施它。这是应该打包并放在 github 或其他地方的众多 dba 工具之一。 (2认同)