4

我正在尝试在 PL/pgSQL 中编写一个函数区域,该区域循环遍历 anhstore并将记录的列( 的键hstore)设置为特定值( 的值hstore)。我正在使用 Postgres 9.1。

hstore如下所示:' "column1"=>"value1","column2"=>"value2" '

一般来说,这是我想要的一个函数,它接受一个hstore并有一个记录,其中包含要修改的值:

FOR my_key, my_value IN
    SELECT key,
           value
      FROM EACH( in_hstore )
LOOP
    EXECUTE 'SELECT $1'
       INTO my_row.my_key
      USING my_value;
END LOOP;

我收到此代码的错误:

"myrow" has no field "my_key". 我已经寻找了很长一段时间的解决方案,但是我试图达到相同结果的所有其他方法都没有奏效。

4

2 回答 2

10

您发布的答案的更简单替代方案。应该表现得更好。

此函数从给定表 ( in_table_name) 和主键值 ( in_row_pk) 中检索一行,并将其作为新行插入到同一个表中,并替换一些值 ( in_override_values)。返回默认的新主键值 ( pk_new)。

CREATE OR REPLACE FUNCTION f_clone_row(in_table_name regclass
                                     , in_row_pk int
                                     , in_override_values hstore
                                     , OUT pk_new int) AS
$func$
DECLARE
   _pk   text;  -- name of PK column
   _cols text;  -- list of names of other columns
BEGIN

-- Get name of PK column
SELECT INTO _pk  a.attname
FROM   pg_catalog.pg_index     i
JOIN   pg_catalog.pg_attribute a ON a.attrelid = i.indrelid
                                AND a.attnum   = i.indkey[0]  -- 1 PK col!
WHERE  i.indrelid = 't'::regclass
AND    i.indisprimary;

-- Get list of columns excluding PK column
_cols := array_to_string(ARRAY(
      SELECT quote_ident(attname)
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = in_table_name -- regclass used as OID
      AND    attnum > 0               -- exclude system columns
      AND    attisdropped = FALSE     -- exclude dropped columns
      AND    attname <> _pk           -- exclude PK column
      ), ',');

-- INSERT cloned row with override values, returning new PK
EXECUTE format('
   INSERT INTO %1$I (%2$s)
   SELECT %2$s
   FROM  (SELECT (t #= $1).* FROM %1$I t WHERE %3$I = $2) x
   RETURNING %3$I'
 , in_table_name, _cols, _pk)
USING   in_override_values, in_row_pk -- use override values directly
INTO    pk_new;                       -- return new pk directly

END
$func$ LANGUAGE plpgsql;

称呼:

SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore);

SQL小提琴。

  • regclass用作输入参数类型,所以只能使用有效的表名开头,排除SQL注入。如果您应该提供非法的表名,该函数也会更早且更优雅地失败。

  • 使用OUT参数 ( pk_new) 来简化语法。

  • 无需手动计算主键的下一个值。它会自动插入并在事后返回。这不仅更简单、更快捷,还可以避免浪费或乱序的序列号。

  • 用于format()简化动态查询字符串的组装并使其不易出错。请注意我如何分别将位置参数用于标识符和字符串。

  • 我建立在您的隐含假设之上,即允许的表有一个整数类型的主键列和一个列 default。通常是serial列。

  • 函数的关键元素是 final INSERT

    • #=使用子选择中的运算符将覆盖值与现有行合并,并立即分解结果行。
    • 然后您可以只选择 main 中的相关列SELECT
    • RETURNING让 Postgres 为 PK 分配默认值并使用子句将其取回。
    • 将返回值直接写入OUT参数。
    • 所有这些都在一个 SQL 命令中完成,这通常是最快的。
于 2013-07-15T22:34:13.550 回答
1

由于我不想为了提高速度而使用任何外部函数,所以我创建了一个使用 hstores 将记录插入表中的解决方案:

CREATE OR REPLACE FUNCTION fn_clone_row(in_table_name character varying, in_row_pk integer, in_override_values hstore)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE

my_table_pk_col_name    varchar;
my_key                  text;
my_value                text;
my_row                  record;
my_pk_default           text;
my_pk_new               integer;
my_pk_new_text          text;
my_row_hstore           hstore;
my_row_keys             text[];
my_row_keys_list        text;
my_row_values           text[];
my_row_values_list      text;

BEGIN

-- Get the next value of the pk column for the table.
SELECT ad.adsrc,
       at.attname
  INTO my_pk_default,
       my_table_pk_col_name
  FROM pg_attrdef ad
  JOIN pg_attribute at
    ON at.attnum = ad.adnum
   AND at.attrelid = ad.adrelid
  JOIN pg_class c
    ON c.oid = at.attrelid
  JOIN pg_constraint cn
    ON cn.conrelid = c.oid
   AND cn.contype = 'p'
   AND cn.conkey[1] = at.attnum
  JOIN pg_namespace n
    ON n.oid = c.relnamespace
 WHERE c.relname = in_table_name
   AND n.nspname = 'public';

-- Get the next value of the pk in a local variable
EXECUTE ' SELECT ' || my_pk_default
   INTO my_pk_new;

-- Set the integer value back to text for the hstore
my_pk_new_text := my_pk_new::text;


-- Add the next value statement to the hstore of changes to make.
in_override_values := in_override_values || hstore( my_table_pk_col_name, my_pk_new_text );


-- Copy over only the given row to the record.
EXECUTE ' SELECT * '
        '   FROM ' || quote_ident( in_table_name ) ||
        '  WHERE ' || quote_ident( my_table_pk_col_name ) ||
                   '    = ' || quote_nullable( in_row_pk )
   INTO my_row;


-- Replace the values that need to be changed in the column name array
my_row := my_row #= in_override_values;


-- Create an hstore of my record
my_row_hstore := hstore( my_row );


-- Create a string of comma-delimited, quote-enclosed column names
my_row_keys := akeys( my_row_hstore );
SELECT array_to_string( array_agg( quote_ident( x.colname ) ), ',' )
  INTO my_row_keys_list
  FROM ( SELECT unnest( my_row_keys ) AS colname ) x;


-- Create a string of comma-delimited, quote-enclosed column values
my_row_values := avals( my_row_hstore );
SELECT array_to_string( array_agg( quote_nullable( x.value ) ), ',' )
  INTO my_row_values_list
  FROM ( SELECT unnest( my_row_values ) AS value ) x;


-- Insert the values into the columns of a new row
EXECUTE 'INSERT INTO ' || in_table_name || '(' || my_row_keys_list || ')'
        '     VALUES (' || my_row_values_list || ')';


RETURN my_pk_new;

END
$function$;

它比我预想的要长很多,但它确实有效,而且速度非常快。

于 2013-07-11T11:35:45.910 回答