2013-07-09 55 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函數中有*相當多的高級功能。 :) –