web-dev-qa-db-ja.com

ROWTYPEパラメータをEXECUTEに渡す

一連の関数に含まれるチェックの結果の値をクエリの各レコードに対して回復することを目的としたPostgresの関数を開発しています。これらの関数の1つだけが正しい値を返します。これらの関数には、共通のプレフィックス「fn_condition_」があり、タイプ「my_table」のオブジェクトをパラメーターとして受け取ります。

チェックを行う関数の数は不明であるため、テーブルpg_catalog.pg_procからPostgresカタログを調べて、プレフィックス 'fn_condition_'が付いた関数を検索し、 EXECUTE

私の問題は、EXECUTEのパラメーターを渡す正しい形式です。

create or replace function test_conditions()
returns void as 
$$
declare
    v_record my_table%rowtype;
    v_function pg_proc%rowtype;    
begin 
    set search_path = 'pg_catalog';

    for v_record in (select * from my_table where id in (1,2,3)) loop
        for v_function in (
            SELECT  p.proname
            FROM    pg_namespace n
            JOIN    pg_proc p
            ON      p.pronamespace = n.oid
            WHERE   n.nspname = 'operacional'
            and p.proname like ('fn_condition\\_%')
            order by p.proname) 
        loop
         -- execute 'select ' || v_function.proname || '(' || v_record || ')';  -- ???
        end loop;
    end loop;
end;
$$ 
language plpgsql;

上記の関数のコメント化されたEXECUTEコマンドでv_recordを適切に渡す方法は?

execute 'select ' || v_function.proname || '(' || v_record || ')';  -- ???

関数例:

create or replace function fn_condition_1(p_record my_table)
returns bigint as 
$$
begin 
    if ($1.atributo1 > $1.atributo2) then
        return 1;
    end if;
    return null;
end;
$$ 
language plpgsql;
5
Geison Santos

Postgres 8.4以降では、USINGEXECUTE句を使用して、値を安全かつ効率的に渡すことができます。これは、バージョン8.3ではまだ利用できません。あなたのバージョンでは、次のように動作する可能性があります:

_CREATE OR REPLACE FUNCTION test_conditions()
  RETURNS SETOF bigint AS
$func$
DECLARE
   _rec    record;
   _func   text;
   _result bigint;
BEGIN
   FOR _func in
      SELECT  p.proname
      FROM    pg_catalog.pg_namespace n
      JOIN    pg_catalog.pg_proc      p ON p.pronamespace = n.oid
      WHERE   n.nspname = 'operacional'
      AND     p.proname LIKE E'fn\\_condition\\_%'  -- no parens, proper string
      ORDER   BY p.proname  -- no parens
   LOOP
      FOR _rec in
         SELECT * FROM my_table WHERE id IN (1,2,3)  -- no parens needed
      LOOP
         EXECUTE 'SELECT ' || quote_ident(_func) || '(' || quote_literal(_rec) || ')'
         INTO _result;
         RETURN NEXT _result;
      END LOOP;
   END LOOP;
END
$func$  LANGUAGE plpgsql SET search_path = 'public';
_

コール:

_SELECT * FROM test_conditions();
_
  • 関数本体で_set search_path = 'pg_catalog';_を使用すると、publicスキーマのテーブルは表示されなくなります。そして、検索パスをグローバルにSETするのは非常に悪い考えです。効果は設定の期間中持続します。 _SET LOCAL_を使用してそれをトランザクションに含めることができますが、それでも悪い考えです。代わりに、本当に必要な場合は、デモのように関数の環境のみを設定してください。
    Postgresの検索パスの詳細:

  • 結果を代入したり返したりせずにSELECTを実行するだけでは意味がありません。 INTOEXECUTE句を使用し、次に_RETURN NEXT_を使用します。最近のPostgresでは、内部ループを_RETURN QUERY EXECUTE_に置き換えます。

  • quote_ident()およびquote_literal()を使用して、動的クエリ文字列を構築するときに識別子とリテラルを適切にエスケープします。最近のPostgresではformat()を使用します。

  • 行全体を文字列表現にキャストし、エスケープしてキャストバックするのはあまり効率的ではありません。この代替アプローチは、テーブルから繰り返し読み取る必要がありますが、それ以外の場合はよりクリーンです(行は値として直接渡されます)。

    _  FOR i IN
         VALUES (1), (2), (3)
      LOOP
         EXECUTE 'SELECT ' || quote_ident(_func) || '(t) FROM my_table t WHERE id = ' || i
         INTO _result;
         RETURN NEXT _result;
      END LOOP;
    _

関数の例

このSQL関数を使用して、サンプル関数を大幅に簡略化することもできます。

_CREATE OR REPLACE FUNCTION fn_condition_1(p_record my_table)
  RETURNS bigint AS
$func$
   SELECT CASE WHEN $1.atributo1 > $1.atributo2 THEN bigint '1' END
$func$  LANGUAGE sql;
_
4