如何删除具有外键依赖项的重复行?

时间:2015-06-20 20:34:41

标签: database postgresql exception-handling foreign-keys plpgsql

我确定这是常见的地方,但谷歌没有帮助。我试图在PostgreSQL 9.1中编写一个简单的存储过程,它将从父if(true == t.canRecord) 表中删除重复的条目。父表cpt由子表cpt引用,定义为:

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表中删除不必要的记录。

这是(非常错误的)代码:

cpt

2 个答案:

答案 0 :(得分:7)

使用带有data-modifying CTEs单个SQL语句,您可以更有效地执行此操作。

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&lt;&gt;小提琴here (第11页)
SQL Fiddle(第9.6页)

这应该 更多 更快更清洁。循环比较昂贵,异常处理相对更加昂贵 更重要的是,lab中的引用会自动重定向到cpt中的相应主行,但原始代码中却没有。因此,您可以 一次删除所有欺骗

如果愿意,您仍然可以将它包装在plpgsql或SQL函数中。

解释

  1. 在第一个CTE plan中,使用相同的cdesc标识每个分区中的主行。在您的情况下,行具有最小recid

  2. 在第二次CTE upd_lab中,将引用欺骗的所有行重定向到cpt中的主行。

  3. 最后,删除dupes,这不会引发异常,因为依赖行实际上同时链接到剩余的主行。

  4. ON DELETE RESTRICT

    所有CTE和语句的主要查询都在基础表的同一快照上运行,实际上 并发 。他们没有看到彼此对基础表的影响:

    人们可能期望ON DELETE RESTRICT的FK约束引发异常,因为[每个文档] [3]:

      

    NO ACTION支票以外的参考操作无法延期,   即使约束被宣布为可延迟的。

    但是,上述声明是单一命令和[再次手册] [3]:

      

    将立即检查不可延迟的约束   每个命令

    大胆强调我的。当然,也适用于限制较少的默认ON DELETE NO ACTION

    但要注意写入相同表的并发事务,但这是一个普遍的考虑因素,并不是特定于此任务。

    例外适用于UNIQUEPRIMARY KEY约束,但这与案例无关:

答案 1 :(得分:1)

您可以选择所有重复项,然后使用记录变量循环结果。 您将可以访问整个当前记录。以下功能可以作为一个例子:

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 ();