2013-07-09 51 views
2

我试图在PL/pgSQL中编写一个函数区域,它循环访问hstore并将记录的列(hstore的键)设置为特定值(值为hstore)。我正在使用Postgres 9.1。EXECUTE ... INTO ... PL/pgSQL中的USING语句无法执行记录?

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"。我一直在寻找解决方案的一段时间,但我试图达到同样结果的其他方法都没有奏效。

回答

0

因为我不想不得不使用任何外部功能转速的目的,我使用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$; 

这是相当多的比长我已经设想,但它的工作,其实很快。

+0

您可以发布包括标题在内的全部功能。这可以大大简化... –

+0

我已经在必要时添加了标题和更多细节。 – Nuggles

+0

我想我终于明白你现在在做什么。对于这个函数的作用,你可能已经做了一些简单的解释。这不完全是问题所要求的。 –

6

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

该函数从给定表(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 Fiddle.

  • 使用regclass作为输入参数的类型,所以只有有效的表名可用来开始和SQL注入排除。如果你应该提供一个非法的表名,函数也会提前失败并且更优雅。

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

  • 不需要手动计算主键的下一个值。它被自动插入并在事后返回。这不仅更简单,速度更快,还避免了浪费或无序的序列号。

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

  • 我建立在你的隐含假设允许表有单一主键整数类型与列默认列。通常是serial列。该函数的

  • 关键因素是最终INSERT

    • 与现有的行合并覆盖值在子选择使用#= operator并立即分解生成的行。
    • 然后,您只能选择主要的SELECT中的相关列。
    • 让Postgres指定PK的默认值,然后用RETURNING子句返回。
    • 将返回值直接写入OUT参数中。
    • 全部在单个SQL命令中完成,通常是最快的。
+0

这真的很让人印象深刻,欧文!这是我第一个学习SQL的第一个学期,它会花费我一点时间来研究整个解决方案,但我迫不及待地想要尝试一下。我会尽快回答你是否在某个时候增加了速度。 – Nuggles

+0

@Nuggles:在这个PL/pgSQL函数中有*相当多的高级功能。 :) –