更新引用时删除行

rom*_*aia 6 postgresql delete dynamic-sql referential-integrity update

假设我有一个person带有 id 和其他一些列的表。其他表person使用ON UPDATE CASCADE. 例如:

CREATE TABLE person (id int PRIMARY KEY);
CREATE TABLE sale (id int PRIMARY KEY,
    person_id int REFERENCES person(id) ON UPDATE CASCADE);
Run Code Online (Sandbox Code Playgroud)

现在,我想要做的是:从 person 表中删除重复的条目,修复referencessale引用。例如,使用此数据:

INSERT INTO person values (1), (2), (3);
INSERT INTO sale VALUES (11, 1), (12, 2), (13, 3);
Run Code Online (Sandbox Code Playgroud)

Person12实际上是一样的,所以他们应该被“合并”。通过合并我的意思是不包括人2在更新的人引用1sale表。

由于ON UPDATE CASCADE表上有一个引用person,我想知道是否有某种方法可以在不必手动更新sale表的情况下,在删除人的同时导致级联更新2

我在这里试图实现的是避免使用专用程序来更新引用,因为引用表的数量很可能会增加,需要在此过程中进行维护(并且已经相当大了)。

Erw*_*ter 5

不,没有用于将一个键合并到另一个键的内置功能,自动更新所有引用行。您可能需要解决各种复杂情况。

简单案例

您需要UPDATE在所有引用表和DELETE被引用表上运行。在简单的情况是这样的:

BEGIN;

--  1. update references
UPDATE sale
SET    person_id = _org_id
WHERE  person_id = _dupe_id;

-- 2. kill dupe 
DELETE FROM person
WHERE  id = _dupe_id;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

但是可能有各种各样的...

并发症

想象一个person_tagperson和之间实现 n:m 关系的表,在tag上具有唯一约束(person_id, tag_id)。你不能UPDATE行会导致重复的标签。您需要解决冲突:仅更新与唯一约束不冲突的行并删除其余行。

-- example for conflict resolution
UPDATE person_tag pt
SET    person_id = _org_id
WHERE  person_id = _dupe_id
AND NOT EXISTS (
   SELECT 1 FROM person_tag
   WHERE  person_id = _org_id
   AND    tag_id = pt.tag_id
   );

DELETE FROM person_tag
WHERE  person_id = _dupe_id;
-- end person_tag
Run Code Online (Sandbox Code Playgroud)

可能还有更复杂的星座……

全自动化

如果您有许多可以以相同方式(或变化的数字)处理的引用表,您可以使用动态 SQL自动执行该过程。

对于具有单列 fk 约束且没有复杂性的简单情况

CREATE OR REPLACE FUNCTION f_merge_id(_tbl regclass, _col text, _org_id int, _dupe_id int)
  RETURNS void AS
$func$
DECLARE
   rec record;
BEGIN
   FOR rec IN  -- find all referencing columns
      SELECT c.conrelid::regclass AS tbl, quote_ident(a2.attname) AS col
      FROM   pg_catalog.pg_attribute  a1
      JOIN   pg_catalog.pg_constraint c  ON c.confrelid = a1.attrelid
                                        AND c.confkey   = ARRAY[a1.attnum]
      JOIN   pg_catalog.pg_attribute  a2 ON a2.attrelid = c.conrelid
                                        AND a2.attnum   = c.conkey[1]
      WHERE  a1.attrelid = _tbl
      AND    a1.attname  = _col
      AND    c.contype   = 'f'  -- fk constraint
   LOOP        -- Redirect to _org_id all references to dupe_id
      EXECUTE format('
         UPDATE %1$s
         SET    %2$s = $1
         WHERE  %2$s = $2'
        ,rec.tbl, rec.col)
      USING _org_id, _dupe_id;
   END LOOP;

   -- Finally kill (now orphaned) dupe 
   EXECUTE format('DELETE FROM %s WHERE %s = $1', _tbl, _col)
   USING _dupe_id;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT f_merge_id('person', 'person_id', _org_id := 1, _dupe_id := 2);
Run Code Online (Sandbox Code Playgroud)

这将更新具有引用主表的外键的任意数量的表。

在调用中使用命名参数,这不是必需的,但您要确保 不要在此处混淆原始参数和重复参数。

SQL小提琴。

要点