2024-07-07 07:00:03

级联删除一次

我有一个Postgresql数据库,我想做一些级联删除。但是,这些表不是用ON DELETE CASCADE规则设置的。有没有办法我可以执行一个删除,并告诉Postgresql级联它只是这一次?等价于

DELETE FROM some_table CASCADE;

这个老问题的答案似乎不存在这样的解决方案,但我想我要明确地问这个问题,只是为了确定。


当前回答

我拿了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;

其他回答

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

Perform all the deletions explicitly, one query at a time, starting with child tables (though this won't fly if you've got circular references); or Perform all the deletions explicitly in a single (potentially massive) query; or Assuming your non-cascading foreign key constraints were created as 'ON DELETE NO ACTION DEFERRABLE', perform all the deletions explicitly in a single transaction; or Temporarily drop the 'no action' and 'restrict' foreign key constraints in the graph, recreate them as CASCADE, delete the offending ancestors, drop the foreign key constraints again, and finally recreate them as they were originally (thus temporarily weakening the integrity of your data); or Something probably equally fun.

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

几个月前,我来到这里寻找“CASCADE DELETE only once”问题的答案(最初在十多年前被问到!)。我从Joe Love的聪明的解决方案(以及Thomas C. G. de Vilhena的变体)中获得了一些好处,但最终我的用例有特殊的需求(例如处理表内循环引用),这迫使我采取不同的方法。这种方法最终变成了recursively_delete (PG 10.10)。

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

的图形的删除目标的ASCII预览 家属。 使用递归cte在单个查询中执行删除。 处理表内和表间的循环依赖关系。 处理复合键。 跳过'set default'和'set null'约束。

当你创建新表时,你可以添加一些约束,如UNIQUE,或NOT NULL,也可以告诉SQL当你试图删除行时,它应该做什么动作,它对另一个表有引用

CREATE TABLE company (
                id SERIAL PRIMARY KEY,
                name VARCHAR(128),
                year DATE);
CREATE TABLE employee (
                id SERIAL PRIMARY KEY,
                first_name VARCHAR(128) NOT NULL,
                last_name VARCHAR(128) NOT NULL,
                company_id INT REFERENCES company(id) ON DELETE CASCADE,
                salary INT,
                UNIQUE (first_name, last_name));

所以在这之后,你可以删除任何你需要的行,例如:

DELETE
FROM company
WHERE id = 2;

不。要做到这一点,只需为要级联的表编写delete语句。

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

我不能评论帕勒霍斯的答案,所以我加上了我自己的答案。 帕勒霍斯的逻辑是可以的,但大数据集的效率可能很差。

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;

如果列上有索引,数据集比少数记录大,那么速度会更快。

该命令将删除所有具有指定表外键的表中的所有数据,以及对这些表具有外键的所有内容,依此类推。要极其谨慎地进行。

如果你真的想DELETE FROM some_table CASCADE;这意味着“删除表some_table中的所有行”,您可以使用TRUNCATE而不是DELETE,并且始终支持CASCADE。但是,如果想使用带有where子句的选择性删除,TRUNCATE不够好。

小心使用——这将删除在some_table上有外键约束的所有表的所有行,以及在这些表上有约束的所有表,等等。

Postgres支持使用TRUNCATE命令进行级联:

TRUNCATE some_table CASCADE;

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