一連の関数に含まれるチェックの結果の値をクエリの各レコードに対して回復することを目的とした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;
Postgres 8.4以降では、USING
のEXECUTE
句を使用して、値を安全かつ効率的に渡すことができます。これは、バージョン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
を実行するだけでは意味がありません。 INTO
のEXECUTE
句を使用し、次に_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;
_