我想更改主键和引用此值的所有表行。
# table master
master_id|name
===============
foo|bar
# table detail
detail_id|master_id|name
========================
1234|foo|blu
如果我提供剧本或功能
table=master, value-old=foo, value-new=abc
我想创建一个SQL代码段,在所有引用表“master”的表上执行更新:
update detail set master_id=value-new where master_id=value-new;
.....
在内省的帮助下,这应该是可能的。
我使用postgres。
更新
问题是,有许多表具有表“master”的外键。我想要一种方法来自动更新所有具有外键到主表的表。
答案 0 :(得分:21)
到目前为止处理主键更改的最简单方法是将ALTER
引用的外键约束设为ON UPDATE CASCADE
。
然后您可以自由更新主键值,更改将级联到子表。由于所有随机I / O,它可能是一个非常缓慢的过程,但它会起作用。
在此过程中,您需要注意不要违反主键列的唯一性限制。
更简单但更快捷的方法是为新PK添加新的UNIQUE
列,填充它,向指向新PK的所有引用表添加新列,删除旧的FK约束和列,然后终于放下旧的PK。
答案 1 :(得分:5)
如果您需要更改PK,可以使用DEFFERED CONSTRAINTS
:
SET CONSTRAINTS设置当前事务中约束检查的行为。在每个语句的末尾检查IMMEDIATE约束。 在事务提交之前不会检查DEFERRED约束。每个约束都有自己的IMMEDIATE或DEFERRED模式。
数据准备:
CREATE TABLE master(master_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10));
INSERT INTO master(master_id, name) VALUES ('foo', 'bar');
CREATE TABLE detail(detail_id INT PRIMARY KEY, master_id VARCHAR(10)
,name VARCHAR(10)
,CONSTRAINT fk_det_mas FOREIGN KEY (master_id) REFERENCES master(master_id));
INSERT INTO detail(detail_id, master_id, name) VALUES (1234,'foo','blu');
在正常情况下,如果您尝试更改主要细节,则最终会出现错误:
update detail set master_id='foo2' where master_id='foo';
-- ERROR: insert or update on table "detail" violates foreign key
-- constraint "fk_det_mas"
-- DETAIL: Key (master_id)=(foo2) is not present in table "master"
update master set master_id='foo2' where master_id='foo';
-- ERROR: update or delete on table "master" violates foreign key
-- constraint "fk_det_mas" on table "detail"
-- DETAIL: Key (master_id)=(foo) is still referenced from table "detail".
但如果你将FK分辨率更改为deffered,则没有问题:
ALTER TABLE detail DROP CONSTRAINT fk_det_mas ;
ALTER TABLE detail ADD CONSTRAINT fk_det_mas FOREIGN KEY (master_id)
REFERENCES master(master_id) DEFERRABLE;
BEGIN TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
UPDATE master set master_id='foo2' where master_id = 'foo';
UPDATE detail set master_id='foo2' where master_id = 'foo';
COMMIT;
<强> DBFiddle Demo 强>
请注意,您可以在事务中执行许多操作,但在COMMIT
期间,所有参照完整性检查都必须保留。
如果要自动执行此过程,可以使用动态SQL和元数据表。这里有一个FK专栏的概念证明:
CREATE TABLE master(master_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10));
INSERT INTO master(master_id, name)
VALUES ('foo', 'bar');
CREATE TABLE detail(detail_id INT PRIMARY KEY, master_id VARCHAR(10),
name VARCHAR(10)
,CONSTRAINT fk_det_mas FOREIGN KEY (master_id)
REFERENCES master(master_id)DEFERRABLE ) ;
INSERT INTO detail(detail_id, master_id, name) VALUES (1234,'foo','blu');
CREATE TABLE detail_second(detail_id INT PRIMARY KEY, name VARCHAR(10),
master_id_second_name VARCHAR(10)
,CONSTRAINT fk_det_mas_2 FOREIGN KEY (master_id_second_name)
REFERENCES master(master_id)DEFERRABLE ) ;
INSERT INTO detail_second(detail_id, master_id_second_name, name)
VALUES (1234,'foo','blu');
代码:
BEGIN TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
DO $$
DECLARE
old_pk TEXT = 'foo';
new_pk TEXT = 'foo2';
table_name TEXT = 'master';
BEGIN
-- update childs
EXECUTE (select
string_agg(FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'' ;'
,c.relname,pa.attname, new_pk,pa.attname, old_pk),CHR(13)) AS sql
from pg_constraint pc
join pg_class c on pc.conrelid = c.oid
join pg_attribute pa ON pc.conkey[1] = pa.attnum
and pa.attrelid = pc.conrelid
join pg_attribute pa2 ON pc.confkey[1] = pa2.attnum
and pa2.attrelid = table_name::regclass
where pc.contype = 'f');
-- update parent
EXECUTE ( SELECT FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'';'
,c.relname,pa.attname, new_pk,pa.attname, old_pk)
FROM pg_constraint pc
join pg_class c on pc.conrelid = c.oid
join pg_attribute pa ON pc.conkey[1] = pa.attnum
and pa.attrelid = pc.conrelid
WHERE pc.contype IN ('p','u')
AND conrelid = table_name::regclass
);
END
$$;
COMMIT;
<强> DBFiddle Demo 2 强>
我尝试过,但它不起作用。如果脚本可以显示SQL,那就太好了。这就够了。查看生成的SQL后,如果psql -f
,我可以执行它 你尝试过吗?它对我不起作用。
是的,我试过了。只需查看上面的现场演示链接。 我使用更多调试信息准备相同的演示:
请确保将FK定义为DEFFERED。
<强> DBFiddle 2 with debug info 强>
然后我想看到sql而不是执行它。我从你的小提琴中删除了“表演”,但后来我收到了一个错误。请参阅:http://dbfiddle.uk/?rdbms=postgres_10&fiddle=b9431c8608e54b4c42b5dbd145aa1458
如果您只想获取SQL代码,可以创建函数:
CREATE FUNCTION generate_update_sql(table_name VARCHAR(100), old_pk VARCHAR(100), new_pk VARCHAR(100))
RETURNS TEXT
AS
$$
BEGIN
RETURN
-- update childs
(SELECT
string_agg(FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'' ;', c.relname,pa.attname, new_pk,pa.attname, old_pk),CHR(13)) AS sql
FROM pg_constraint pc
JOIN pg_class c on pc.conrelid = c.oid
JOIN pg_attribute pa ON pc.conkey[1] = pa.attnum and pa.attrelid = pc.conrelid
JOIN pg_attribute pa2 ON pc.confkey[1] = pa2.attnum and pa2.attrelid = table_name::regclass
WHERE pc.contype = 'f') || CHR(13) ||
-- update parent
(SELECT FORMAT('UPDATE %s SET %s = ''%s'' WHERE %s =''%s'';', c.relname,pa.attname, new_pk,pa.attname, old_pk)
FROM pg_constraint pc
JOIN pg_class c on pc.conrelid = c.oid
JOIN pg_attribute pa ON pc.conkey[1] = pa.attnum and pa.attrelid = pc.conrelid
WHERE pc.contype IN ('p','u')
AND conrelid = table_name::regclass)
;
END
$$ LANGUAGE plpgsql;
执行:
SELECT generate_update_sql('master', 'foo', 'foo');
UPDATE detail SET master_id = 'foo' WHERE master_id ='foo' ;
UPDATE detail_second SET master_id_second_name = 'foo'
WHERE master_id_second_name ='foo' ;
UPDATE master SET master_id = 'foo' WHERE master_id ='foo';
<强> DBFiddle Function Demo 强>
当然,还有一个需要改进的地方,例如处理标识符,例如“名称中有空格的表格”等等。
答案 2 :(得分:2)
我认为你不能更新主键。一种可能的解决方法是可以从表列中删除主键约束。然后更新列值。
更新主键可能会导致一些严重问题。但是如果你还想这样做的话。
请参阅此Thread。(kevchadders已提供解决方案。)
答案 3 :(得分:2)
我找到了一个肮脏的解决方案:在psql
中,命令\d master_table
显示相关信息。通过一些文本魔术,可以提取所需的信息:
echo "UPDATE master_table SET id='NEW' WHERE id='OLD';" > tmp/foreign-keys.txt
psql -c '\d master_table' | grep -P 'TABLE.*CONSTRAINT.*FOREIGN KEY' \
>> tmp/foreign-keys.txt
reprec '.*TABLE ("[^"]*") CONSTRAINT[^(]*\(([^)]*)\).*' \
"UPDATE \1 set \2='NEW' WHERE \2='OLD';" \
tmp/foreign-keys.txt
psql -1 -f tmp/foreign-keys.txt
结果:
UPDATE "master_table" SET id='NEW' WHERE id='OLD';
UPDATE "other_table" SET master_id='NEW' WHERE master_id='OLD';
...
但欢迎更好的解决方案。