如何删除具有外键依赖项的重复行?
How to remove duplicate rows with foreign keys dependencies?
我确定这是常见的地方,但 Google 没有帮助。我正在尝试在 PostgreSQL 9.1 中编写一个简单的存储过程,它将从父 cpt
table 中删除重复条目。父项 table cpt
被子项 table lab
引用,定义为:
CREATE TABLE lab (
recid serial NOT NULL,
cpt_recid integer,
........
CONSTRAINT cs_cpt FOREIGN KEY (cpt_recid)
REFERENCES cpt (recid) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE RESTRICT,
...
);
我遇到的最大问题是如何获取失败的记录,以便我可以在 EXCEPTION
子句中使用它来将子行从 lab
移动到一个 acceptable 键,然后循环并从 cpt
table 中删除不需要的记录。
这是(非常错误的)代码:
CREATE OR REPLACE FUNCTION h_RemoveDuplicateCPT()
RETURNS void AS
$BODY$
BEGIN
LOOP
BEGIN
DELETE FROM cpt
WHERE recid IN (
SELECT recid
FROM (
SELECT recid,
row_number() over (partition BY cdesc ORDER BY recid) AS rnum
FROM cpt) t
WHERE t.rnum > 1)
RETURNING recid;
IF count = 0 THEN
RETURN;
END IF;
EXCEPTION WHEN foreign_key_violation THEN
RAISE NOTICE 'fixing unique_violation';
RAISE NOTICE 'recid is %' , recid;
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
您可以select所有重复项一次并使用记录变量循环结果。
您将可以访问整个当前记录。下面的函数可以作为例子:
create or replace function show_remove_duplicates_in_cpt ()
returns setof text language plpgsql
as $$
declare
rec record;
begin
for rec in
select * from (
select
recid, cdesc,
row_number() over (partition by cdesc order by recid) as rnum
from cpt
) alias
where rnum > 1
loop
return next format ('fixing foreign key for %s %s %s', rec.recid, rec.cdesc, rec.rnum);
return next format ('deleting from cpt where recid = %s', rec.recid);
end loop;
end $$;
select * from show_remove_duplicates_in_cpt ();
您可以使用 单个 SQL 语句 和 data-modifying CTEs 更有效地完成此操作。
WITH plan AS (
SELECT *
FROM (
SELECT recid, min(recid) OVER (PARTITION BY cdesc) AS master_recid
FROM cpt
) sub
WHERE recid <> master_recid -- ... <> self
)
, upd_lab AS (
UPDATE lab l
SET cpt_recid = p.master_recid -- link to master recid ...
FROM plan p
WHERE l.cpt_recid = p.recid
)
DELETE FROM cpt c
USING plan p
WHERE c.recid = p.recid
RETURNING c.recid;
db<>fiddle here(第 11 页)
SQL Fiddle(第 9.6 页)
这应该 much 更快更干净。循环比较昂贵,异常处理比较昂贵。
更重要的是,lab
中的引用会自动重定向到 cpt
中的相应主行,这还不在您的原始代码中。所以你可以一次删除所有的复制品.
如果愿意,您仍然可以将其包装在 plpgsql 或 SQL 函数中。
说明
在第一个 CTE plan
中,用相同的 cdesc
在每个分区中标识一个主行。在您的情况下,具有最小 recid
.
的行
在第 2 个 CTE upd_lab
中将引用欺骗的所有行重定向到 cpt
中的主行。
最后,删除重复项,这不会引发异常,因为依赖行几乎同时链接到剩余的主行。
ON DELETE RESTRICT
语句的所有 CTE 和主查询都对基础表的相同快照进行操作,实际上并发 。他们看不到彼此对基础表的影响:
- Delete parent if it's not referenced by any other child
人们可能期望 ON DELETE RESTRICT
的 FK 约束会引发异常,因为 [per documentation][3]:
Referential actions other than the NO ACTION
check cannot be deferred,
even if the constraint is declared deferrable.
然而,上面的语句是单个命令并且,[再次使用手册][3]:
A constraint that is not deferrable will be checked immediately after
every command.
大胆强调我的。当然,也适用于限制较少的默认 ON DELETE NO ACTION
。
但要警惕写入相同表的并发事务,但这是一般考虑因素,并非特定于此任务。
适用于 UNIQUE
和 PRIMARY KEY
约束的例外情况,但这与 这种 情况无关:
- Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?
我确定这是常见的地方,但 Google 没有帮助。我正在尝试在 PostgreSQL 9.1 中编写一个简单的存储过程,它将从父 cpt
table 中删除重复条目。父项 table cpt
被子项 table lab
引用,定义为:
CREATE TABLE lab (
recid serial NOT NULL,
cpt_recid integer,
........
CONSTRAINT cs_cpt FOREIGN KEY (cpt_recid)
REFERENCES cpt (recid) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE RESTRICT,
...
);
我遇到的最大问题是如何获取失败的记录,以便我可以在 EXCEPTION
子句中使用它来将子行从 lab
移动到一个 acceptable 键,然后循环并从 cpt
table 中删除不需要的记录。
这是(非常错误的)代码:
CREATE OR REPLACE FUNCTION h_RemoveDuplicateCPT()
RETURNS void AS
$BODY$
BEGIN
LOOP
BEGIN
DELETE FROM cpt
WHERE recid IN (
SELECT recid
FROM (
SELECT recid,
row_number() over (partition BY cdesc ORDER BY recid) AS rnum
FROM cpt) t
WHERE t.rnum > 1)
RETURNING recid;
IF count = 0 THEN
RETURN;
END IF;
EXCEPTION WHEN foreign_key_violation THEN
RAISE NOTICE 'fixing unique_violation';
RAISE NOTICE 'recid is %' , recid;
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
您可以select所有重复项一次并使用记录变量循环结果。 您将可以访问整个当前记录。下面的函数可以作为例子:
create or replace function show_remove_duplicates_in_cpt ()
returns setof text language plpgsql
as $$
declare
rec record;
begin
for rec in
select * from (
select
recid, cdesc,
row_number() over (partition by cdesc order by recid) as rnum
from cpt
) alias
where rnum > 1
loop
return next format ('fixing foreign key for %s %s %s', rec.recid, rec.cdesc, rec.rnum);
return next format ('deleting from cpt where recid = %s', rec.recid);
end loop;
end $$;
select * from show_remove_duplicates_in_cpt ();
您可以使用 单个 SQL 语句 和 data-modifying CTEs 更有效地完成此操作。
WITH plan AS (
SELECT *
FROM (
SELECT recid, min(recid) OVER (PARTITION BY cdesc) AS master_recid
FROM cpt
) sub
WHERE recid <> master_recid -- ... <> self
)
, upd_lab AS (
UPDATE lab l
SET cpt_recid = p.master_recid -- link to master recid ...
FROM plan p
WHERE l.cpt_recid = p.recid
)
DELETE FROM cpt c
USING plan p
WHERE c.recid = p.recid
RETURNING c.recid;
db<>fiddle here(第 11 页)
SQL Fiddle(第 9.6 页)
这应该 much 更快更干净。循环比较昂贵,异常处理比较昂贵。
更重要的是,lab
中的引用会自动重定向到 cpt
中的相应主行,这还不在您的原始代码中。所以你可以一次删除所有的复制品.
如果愿意,您仍然可以将其包装在 plpgsql 或 SQL 函数中。
说明
在第一个 CTE
plan
中,用相同的cdesc
在每个分区中标识一个主行。在您的情况下,具有最小recid
. 的行
在第 2 个 CTE
upd_lab
中将引用欺骗的所有行重定向到cpt
中的主行。最后,删除重复项,这不会引发异常,因为依赖行几乎同时链接到剩余的主行。
ON DELETE RESTRICT
语句的所有 CTE 和主查询都对基础表的相同快照进行操作,实际上并发 。他们看不到彼此对基础表的影响:
- Delete parent if it's not referenced by any other child
人们可能期望 ON DELETE RESTRICT
的 FK 约束会引发异常,因为 [per documentation][3]:
Referential actions other than the
NO ACTION
check cannot be deferred, even if the constraint is declared deferrable.
然而,上面的语句是单个命令并且,[再次使用手册][3]:
A constraint that is not deferrable will be checked immediately after every command.
大胆强调我的。当然,也适用于限制较少的默认 ON DELETE NO ACTION
。
但要警惕写入相同表的并发事务,但这是一般考虑因素,并非特定于此任务。
适用于 UNIQUE
和 PRIMARY KEY
约束的例外情况,但这与 这种 情况无关:
- Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?