postgresql 级联删除仅一次

0pizxfdo  于 2022-12-18  发布在  PostgreSQL
关注(0)|答案(9)|浏览(178)

我有一个Postgresql数据库,我想对它执行一些级联删除。但是,表没有设置ON DELETE CASCADE规则。有什么方法可以执行删除并告诉Postgresql只级联它一次吗?类似于

DELETE FROM some_table CASCADE;

this older question的答案似乎不存在这样的解决方案,但我想我应该明确地问这个问题,以确保万无一失。

monwx1rj

monwx1rj1#

不需要。如果只执行一次,只需为要级联的表编写delete语句。

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

ibrsph3r2#

* 此命令将删除具有指向指定表的外键的所有表中的所有数据,以及指向这些表的外键的所有内容,依此类推。请格外小心。*

如果您真的需要DELETE FROM some_table CASCADE;(表示“* 从表some_table中删除所有行 *"),您可以使用TRUNCATE代替DELETE,并且CASCADE始终受支持。但是,如果您想使用带有where子句的选择性删除,TRUNCATE就不够好了。
USE WITH CARE-这将删除在some_table上具有外键约束的所有表的所有行,以及在这些表上具有约束的所有表,等等。

Postgres支持CASCADETRUNCATE command

TRUNCATE some_table CASCADE;

这是事务性的(也就是说可以回滚),尽管它没有完全与其他并发事务隔离,并且有一些其他的警告。

gorkyyrv

gorkyyrv3#

我写了一个(递归)函数来删除基于主键的任何行。我编写这个函数是因为我不想创建“on delete cascade”约束。我希望能够删除复杂的数据集(作为一个DBA),但不允许我的程序员能够级联删除而不考虑所有的影响。我仍然在测试这个函数,因此可能会有一些bug--但是如果数据库有多列主数据,请不要尝试(因此也是外部)键。另外,所有键都必须能够以字符串形式表示,但它可以用一种没有这种限制的方式来编写。无论如何,我非常稀疏地使用这个函数,我对数据的评价太高,以至于不能对所有内容启用级联约束。(字符串形式),它将从查找该表上的任何外键开始,并确保数据不存在--如果数据存在,它递归地调用它自己找到的数据。它使用一个数组的数据已经标记为删除,以防止无限循环。请测试一下,让我知道它如何为你工作。注意:有点慢。我是这么叫的: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;
a2mppw5e

a2mppw5e4#

如果我理解正确的话,您应该能够通过删除外键约束、添加一个新的外键约束(将级联)、做您自己的事情以及重新创建限制外键约束来完成您想要做的事情。
例如:

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

当然,为了你的心理健康,你应该把这样的事情抽象成一个程序。

o4tp2gmn

o4tp2gmn5#

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

  • 显式执行所有删除操作,一次一个查询,从子表开始(不过如果有循环引用,这就行不通了);或
  • 在单个(可能是大规模的)查询中显式地执行所有删除;或
  • 假设您的非级联外键约束创建为“ON DELETE NO ACTION DEFERRABLE”,请在单个事务中显式执行所有删除操作;或
  • 临时删除图形中的“无操作”和“限制”外键约束,将它们重新创建为CASCADE,删除有问题的祖先,再次删除外键约束,最后将它们重新创建为原来的样子(从而临时削弱数据的完整性);或
  • 一些可能同样有趣的事情。

我想,这是有意使绕过外键约束变得不方便;但我确实理解在特定情况下您为什么要这样做,如果您经常要这样做,并且如果您愿意无视各地DBA的智慧,那么您可能希望通过一个过程来自动化它。
几个月前,我来到这里寻找“级联删除一次”问题的答案(最初是在十多年前问的!)我从乔·洛夫的聪明解决方案中得到了一些好处(以及托马斯. G. deVilhena的变体),但最终我的用例有特殊的需求(例如,处理表内循环引用),这迫使我采取了一种不同的方法,这种方法最终变成了recursively_delete(PG 10.10)。
我在生产中使用recursively_delete已经有一段时间了,现在我终于(谨慎地)有足够的信心将它提供给其他可能在这里寻找想法的人。与Joe Love的解决方案一样,它允许您删除整个数据图,就好像数据库中的所有外键约束都被暂时设置为CASCADE一样,但它提供了一些额外的特性:

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

ikfrs5lh6#

我无法评论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;

如果列上有索引,并且数据集比几条记录大,则速度会更快。

6psbrbz9

6psbrbz97#

我接受了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;
uinbv5nw

uinbv5nw8#

您可以使用来自动执行此操作,您可以使用ON DELETE CASCADE定义外键约束。
我引用外键约束手册:
CASCADE指定删除引用行时,引用该行的行也应自动删除。

6ojccjat

6ojccjat9#

带有级联选项的删除仅适用于定义了外键的表。如果执行删除操作,但由于会违反外键约束条件而显示不能执行,则级联将导致删除违规的行。
如果要以这种方式删除关联行,则需要首先定义外键。另外,请记住,除非显式指示它开始事务处理,或者更改默认值,否则它将执行自动提交,而这可能非常耗时。

相关问题