EXECUTE ... INTO ... PL / pgSQL中的USING语句无法执行到记录中?

时间:2013-07-09 11:59:38

标签: postgresql record plpgsql dynamic-sql composite

我正在尝试在PL / pgSQL中编写一个函数区域,循环遍历hstore并将记录的列(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"。我一直在寻找一个解决方案,但我尝试达到相同结果的其他一切都没有用。

2 个答案:

答案 0 :(得分:7)

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

此函数从给定表(in_table_name)和主键值(in_row_pk)中检索一行,并将其作为新行插入到同一个表中,并替换一些值({{1 }})。返回默认的新主键值(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;

SQL Fiddle.

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

  • 使用SELECT f_clone_row('t', 1, '"col1"=>"foo_new","col2"=>"bar_new"'::hstore); 参数(OUT)来简化语法。

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

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

  • 我构建了隐含的假设,允许表格具有单个主键列,类型为整数,列为默认。通常是serial列。

  • 该函数的关键元素是最终pk_new

    • 使用子选择中的#= operator将覆盖值与现有行合并,并立即分解生成的行。
    • 然后,您只能选择主INSERT中的相关列。
    • 让Postgres为PK分配默认值,并使用SELECT子句将其恢复。
    • 直接将返回的值写入RETURNING参数。
    • 所有在一个SQL命令中完成,通常最快。

答案 1 :(得分: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$;

这比我想象的要长一点,但它确实有效并且实际上非常快。