我有一个Postgresql数据库,我想在该数据库上进行一些级联删除。但是,未使用ON DELETE CASCADE规则设置表。有什么办法可以执行一次删除并告诉Postgresql仅一次将此级联吗?相当于
DELETE FROM some_table CASCADE;
这个老问题的答案似乎似乎不存在这样的解决方案,但我想我想明确地问这个问题只是为了确定。
我有一个Postgresql数据库,我想在该数据库上进行一些级联删除。但是,未使用ON DELETE CASCADE规则设置表。有什么办法可以执行一次删除并告诉Postgresql仅一次将此级联吗?相当于
DELETE FROM some_table CASCADE;
这个老问题的答案似乎似乎不存在这样的解决方案,但我想我想明确地问这个问题只是为了确定。
Answers:
只需一次,您只需为要级联的表编写delete语句即可。
DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;
如果您确实要 DELETE FROM some_table CASCADE;
表示“ 从表中删除所有行some_table
”,则可以使用TRUNCATE
代替,DELETE
并且CASCADE
始终受支持。但是,如果要对where
子句使用选择性删除,TRUNCATE
则不够好。
与护理一起使用 -这将删除所有具有外键约束的表的所有行some_table
以及对该表具有约束的所有表,等等。
Postgres的支持CASCADE
与TRUNCATE命令:
TRUNCATE some_table CASCADE;
方便地,这是事务性的(即可以回滚),尽管它与其他并发事务没有完全隔离,并且还有其他一些警告。阅读文档以获取详细信息。
我编写了一个(递归)函数,以根据其主键删除任何行。我写这篇文章是因为我不想将约束创建为“在删除级联上”。我希望能够删除复杂的数据集(作为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;
IN
与子选择,而不是运营商=
(所以步骤使用了一组逻辑),将成为多快。
如果我理解正确,那么您应该能够通过删除外键约束,添加新的(将层叠的),完成您的工作并重新创建限制性外键约束来做您想要的事情。
例如:
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
当然,为了您的心理健康,您应该将类似的内容抽象到一个过程中。
我无法评论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;
如果在列上具有索引并且数据集大于少数记录,则速度会更快。
是的,正如其他人所说,没有方便的“从my_table ... CASCADE删除”(或等效功能)。要删除非级联的受外键保护的子记录及其引用的祖先,您可以选择以下选项:
我认为绕开外键约束的目的不是很方便。但我确实了解您为什么要在特定情况下这样做。如果您会以某种频率执行某项操作,并且愿意在所有地方炫耀DBA的智慧,那么您可能希望使用一个过程使其自动化。
几个月前,我来这里寻求“仅一次CASCADE DELETE”问题的答案(最初是在十年前提出的!)。我从Joe Love的聪明解决方案(以及Thomas CG de Vilhena的变体)中获得了一些收获,但是最终,我的用例有特殊的要求(对于一个表内循环引用的处理),迫使我不得不采用另一种方法。该方法最终成为递归删除(PG 10.10)。
现在,我已经在生产中使用了recursively_delete一段时间了,终于(谨慎地)有足够的信心将其提供给其他可能会在这里寻找想法的人。与Joe Love的解决方案一样,它允许您删除整个数据图,就好像数据库中的所有外键约束都被暂时设置为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;
in
运算符和子查询时,我注意到速度提高了10倍。
带级联选项的删除仅适用于已定义外键的表。如果您执行删除操作,但说不能,因为它会违反外键约束,则级联将导致它删除有问题的行。
如果要以这种方式删除关联的行,则需要首先定义外键。另外,请记住,除非您明确指示它开始事务,或者更改默认值,否则它将执行自动提交,这可能非常耗时。