仅更新动态选择的列
Update only dynamically chosen columns
我创建了一个函数 copy_rows_from_table(_tbl regclass)
,它复制 table 中的记录并为它们提供新的主键值。它 return 是一个包含 old_id => new_id 对的 hstore。例如,对于 table books
,我的函数将创建两个额外的记录和 return 一个 hstore。
books
初始:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
books
评估后 copy_rows_from_table('books')
:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 23 | 46
4 | The Trial | 10.00 | 12 | 67
returned hstore:
"1"=>"3", "2"=>"4"
它工作正常。现在我想创建一个函数,从几个 table 中复制记录(在数组中传递),然后使用 returned hstore 更新所有外键。例如,在复制 books
和 authors
之后,我希望 author_id
列在 books
table 中更新。在 books
、authors
和 publishers
上使用我的函数后,如果我有一个包含 "1"=>"3", "2"=>"4","23"=>"167","12"=>"98","46"=>"87","67"=>"102"
的 hstore,我的函数应该更新 books
table这样:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 167 | 87
4 | The Trial | 10.00 | 98 | 102
我想到了这样的东西:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_tbl regclass;
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
BEGIN
FOR _tbl IN SELECT _tbls
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbl)
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR _tbl IN SELECT _tbls
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbl, avals(_id_pairs))
LOOP
EXECUTE (
SELECT format('UPDATE %1$s SET (%2$s) = (%3$s) WHERE id = %4$s'
, _tbl, string_agg(quote_ident(attname), ', '),
string_agg(COALESCE(_id_pairs -> ('_row.' || quote_ident(attname)), '_row.' || quote_ident(attname)), ', '), _row.id)
FROM pg_attribute
WHERE attrelid = _tbl
AND NOT attisdropped
AND attnum > 0
AND attname LIKE '%_id'
);
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
但这并不完全奏效。有没有可能按照我解释的方式更新记录?
PLpgSQL 不是动态更新的好工具。可能还有其他一些可能性,但没有一个是微不足道的。其他方式:
- 使用一些更动态的 PL 语言 - PLPythonu、PLPerl
- 使用扩展 https://github.com/okbob/pltoolbox - 有函数
record_get_fields
和 record_set_fields
我终于找到了一种在 PLpgSQL 中实现它的方法。我只是遍历每条记录的每一列。这是我的工作函数:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
_hs_row record;
BEGIN
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbls[I])
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbls[I], avals(_id_pairs))
LOOP
FOR _hs_row IN SELECT kv."key", kv."value" FROM each(hstore(_row)) kv
LOOP
IF _hs_row."value" = ANY(akeys(_id_pairs)) THEN
EXECUTE format('UPDATE %1$s SET %2$s = ''%3$s'' WHERE id = ''%4$s''',
_tbls[I], _hs_row."key", _id_pairs -> _hs_row."value", _row.id);
END IF;
END LOOP;
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
我创建了一个函数 copy_rows_from_table(_tbl regclass)
,它复制 table 中的记录并为它们提供新的主键值。它 return 是一个包含 old_id => new_id 对的 hstore。例如,对于 table books
,我的函数将创建两个额外的记录和 return 一个 hstore。
books
初始:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
books
评估后 copy_rows_from_table('books')
:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 23 | 46
4 | The Trial | 10.00 | 12 | 67
returned hstore:
"1"=>"3", "2"=>"4"
它工作正常。现在我想创建一个函数,从几个 table 中复制记录(在数组中传递),然后使用 returned hstore 更新所有外键。例如,在复制 books
和 authors
之后,我希望 author_id
列在 books
table 中更新。在 books
、authors
和 publishers
上使用我的函数后,如果我有一个包含 "1"=>"3", "2"=>"4","23"=>"167","12"=>"98","46"=>"87","67"=>"102"
的 hstore,我的函数应该更新 books
table这样:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 167 | 87
4 | The Trial | 10.00 | 98 | 102
我想到了这样的东西:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_tbl regclass;
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
BEGIN
FOR _tbl IN SELECT _tbls
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbl)
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR _tbl IN SELECT _tbls
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbl, avals(_id_pairs))
LOOP
EXECUTE (
SELECT format('UPDATE %1$s SET (%2$s) = (%3$s) WHERE id = %4$s'
, _tbl, string_agg(quote_ident(attname), ', '),
string_agg(COALESCE(_id_pairs -> ('_row.' || quote_ident(attname)), '_row.' || quote_ident(attname)), ', '), _row.id)
FROM pg_attribute
WHERE attrelid = _tbl
AND NOT attisdropped
AND attnum > 0
AND attname LIKE '%_id'
);
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
但这并不完全奏效。有没有可能按照我解释的方式更新记录?
PLpgSQL 不是动态更新的好工具。可能还有其他一些可能性,但没有一个是微不足道的。其他方式:
- 使用一些更动态的 PL 语言 - PLPythonu、PLPerl
- 使用扩展 https://github.com/okbob/pltoolbox - 有函数
record_get_fields
和record_set_fields
我终于找到了一种在 PLpgSQL 中实现它的方法。我只是遍历每条记录的每一列。这是我的工作函数:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
_hs_row record;
BEGIN
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbls[I])
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbls[I], avals(_id_pairs))
LOOP
FOR _hs_row IN SELECT kv."key", kv."value" FROM each(hstore(_row)) kv
LOOP
IF _hs_row."value" = ANY(akeys(_id_pairs)) THEN
EXECUTE format('UPDATE %1$s SET %2$s = ''%3$s'' WHERE id = ''%4$s''',
_tbls[I], _hs_row."key", _id_pairs -> _hs_row."value", _row.id);
END IF;
END LOOP;
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;