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)
Person1
和2
实际上是一样的,所以他们应该被“合并”。通过合并我的意思是不包括人2
在更新的人引用1
的sale
表。
由于ON UPDATE CASCADE
表上有一个引用person,我想知道是否有某种方法可以在不必手动更新sale
表的情况下,在删除人的同时导致级联更新2
我在这里试图实现的是避免使用专用程序来更新引用,因为引用表的数量很可能会增加,需要在此过程中进行维护(并且已经相当大了)。
不,没有用于将一个键合并到另一个键的内置功能,自动更新所有引用行。您可能需要解决各种复杂情况。
您需要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_tag
在person
和之间实现 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)
这将更新具有引用主表的外键的任意数量的表。
在调用中使用命名参数,这不是必需的,但您要确保 不要在此处混淆原始参数和重复参数。
如果你不熟悉plpgsql函数中动态SQL的概念,可以参考一些相关的问题:
正确引用列名以避免 SQL 注入。quote_ident()
为你这样做:
如果您必须处理繁重的并发负载,则必须准备好最终DELETE
可能会失败,这将回滚整个事务。并发事务可以输入新行_dupe_id
。有多种方法可以解决这个问题,这超出了本问题的范围。
归档时间: |
|
查看次数: |
3807 次 |
最近记录: |