仅更新动态选择的列

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 更新所有外键。例如,在复制 booksauthors 之后,我希望 author_id 列在 books table 中更新。在 booksauthorspublishers 上使用我的函数后,如果我有一个包含 "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 不是动态更新的好工具。可能还有其他一些可能性,但没有一个是微不足道的。其他方式:

  1. 使用一些更动态的 PL 语言 - PLPythonu、PLPerl
  2. 使用扩展 https://github.com/okbob/pltoolbox - 有函数 record_get_fieldsrecord_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;