13

結果セットを返す動的に構築されたSQLステートメントを含むストアドプロシージャを作成するにはどうすればよいですか?これが私のサンプルコードです:

CREATE OR REPLACE FUNCTION reporting.report_get_countries_new (
  starts_with varchar,
  ends_with varchar
)
RETURNS TABLE (
  country_id integer,
  country_name varchar
) AS
$body$
DECLARE
  starts_with ALIAS FOR $1;
  ends_with ALIAS FOR $2;
  sql VARCHAR;
BEGIN

    sql = 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name >= ' || starts_with ;

    IF ends_with IS NOT NULL THEN
        sql = sql || ' AND lookups.countries.country_name <= ' || ends_with ;
    END IF;

    RETURN QUERY EXECUTE sql;

END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;

このコードはエラーを返します:

ERROR:  syntax error at or near "RETURN"
LINE 1: RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE o...
        ^
QUERY:  RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE omnipay_lookups.countries.country_name >= r
CONTEXT:  PL/pgSQL function "report_get_countries_new" line 14 at EXECUTE statement

私はこれの代わりに他の方法を試しました:

RETURN QUERY EXECUTE sql;

方法1:

RETURN EXECUTE sql;

方法2:

sql = 'RETURN QUERY SELECT * FROM....
/*later*/
EXECUTE sql;

すべての場合で成功しませんでした。

最終的には、動的SQLステートメントを含み、動的SQLステートメントから結果セットを返すストアドプロシージャを作成したいと思います。

4

2 に答える 2

36

改善の余地があります:

CREATE OR REPLACE FUNCTION report_get_countries_new (starts_with text
                                                   , ends_with   text = NULL)
  RETURNS SETOF lookups.countries AS
$func$
DECLARE
   sql text := 'SELECT * FROM lookups.countries WHERE country_name >= $1';
BEGIN
   IF ends_with IS NOT NULL THEN
      sql := sql || ' AND country_name <= $2';
   END IF;

   RETURN QUERY EXECUTE sql
   USING starts_with, ends_with;
END
$func$ LANGUAGE plpgsql;
-- the rest is default settings

主なポイント

  • PostgreSQL 8.4では、のUSING句が導入されましたEXECUTE。これは、いくつかの理由で役立ちます。マニュアルの要約

    コマンド文字列は、コマンドでなどとして参照されるパラメータ値を使用できます$1, $2。これらの記号は、USING句で指定された値を参照します。この方法は、データ値をテキストとしてコマンド文字列に挿入するよりも望ましい場合がよくあります。値をテキストに変換して戻すという実行時のオーバーヘッドを回避し、引用符や引用符を使用する必要がないため、SQLインジェクション攻撃の可能性がはるかに低くなります。脱出。

    IOW、。でサニタイズした場合でも、パラメータのテキスト表現を使用してクエリ文字列を作成するよりも安全で高速ですquote_literal()。クエリ文字列では、関数パラメータではなく、句で指定された値を参照することに
    注意してください。$1, $2USING

  • を返すときに、次のように宣言SELECT * FROM lookups.countriesを簡略化できます。RETURN

    RETURNS SETOF lookups.countries
    

    PostgreSQLには、すべてのテーブルに対して自動的に定義された複合型があります。これを使って。その結果、関数はタイプによって異なり、テーブルを変更しようとするとエラーメッセージが表示されます。このような場合は、関数を削除して再作成してください。

    これは望ましい場合と望ましくない場合があります-一般的にはそうです!テーブルを変更する場合は、副作用を認識しておく必要があります。あなたがそれを持っている方法では、あなたの関数は静かに壊れて、次の呼び出しで例外を発生させます。

  • 示されているように、宣言の2番目のパラメーターに明示的なデフォルトを指定すると、上限を設定したくない場合に備えて、呼び出しを単純化できます(ただし、そうする必要はありません)ends_with

    SELECT * FROM report_get_countries_new('Zaire');
    

    それ以外の:

    SELECT * FROM report_get_countries_new('Zaire', NULL);
    

    このコンテキストでは、関数のオーバーロードに注意してください。

  • 'plpgsql'(今のところ)許容できる場合でも、言語名を引用しないでください。それは識別子です。

  • 宣言時に変数を割り当てることができます。余分なステップを節約します。

  • パラメータはヘッダーで指定されます。無意味な行を削除します。

     starts_with ALIAS FOR $1;
     ends_with ALIAS FOR $2;
    
于 2012-08-21T01:17:58.707 に答える
6

quote_literal()を使用してSQL インジェクション(!!!)を回避し、引用の問題を修正します。

CREATE OR REPLACE FUNCTION report_get_countries_new (
  starts_with varchar,
  ends_with varchar
)
RETURNS TABLE (
  country_id integer,
  country_name varchar
) AS
$body$
DECLARE
  starts_with ALIAS FOR $1;
  ends_with ALIAS FOR $2;
  sql VARCHAR;
BEGIN

    sql := 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name ' || quote_literal(starts_with) ;

    IF ends_with IS NOT NULL THEN
        sql := sql || ' AND lookups.countries.country_name <= ' || quote_literal(ends_with) ;
    END IF;

    RETURN QUERY EXECUTE sql;

END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;

これはバージョン 9.1 でテストされており、正常に動作します。

于 2012-08-14T08:57:00.307 に答える