web-dev-qa-db-ja.com

行または空のセットを返すPostgreSQLストアドプロシージャ

私はRETURNS SETOF ct_custom_typeと内部で行うストアドプロシージャを持っています

RETURN QUERY EXECUTE 'some dynamic query'

そして、私はこれをしたいです:この '動的クエリ'が> = 10行を返す場合、それらを返しますが、それが10行未満しか返さない場合、何も返しません(ct_custom_typeの空のセット)。

私は試した:

RETURN QUERY EXECUTE 'some dynamic query'

GET DIAGNOSTICS variable = ROW_COUNT;
IF variable < 10 THEN

    # I don't know what to do here or how to accomplish this

END IF;

IFブロックでRETURN QUERY SELECT 0, 0, ''::text;を実行すると(ct_custom_type(integer, integer, text)の複合型であるため、前のクエリ結果にこの「空の行」を追加するだけですが、この場合は何も返しません。 RETURN;を実行できますが、以前の結果が返されるので、破棄したいと思います。

私はそれを次のように持っています:

EXECUTE 'dynamic query';

GET DIAGNOSTICS variable = ROW_COUNT;
IF variable >= 10 THEN
    RETURN QUERY EXECUTE 'dynamic query';
END IF;

動作しますが、このクエリを2回実行したくありませんでした。

5
boobiq

次の行に沿って何かを行うことができます。

test=> CREATE OR REPLACE FUNCTION temptabl(cnt integer)
RETURNS SETOF integer AS
$body$
BEGIN
    CREATE TEMPORARY TABLE tmp_container ON COMMIT DROP AS
    SELECT a
    FROM generate_series(1, cnt) t(a);

    IF (SELECT count(1) FROM tmp_container) > 5
    THEN
        RETURN QUERY SELECT a FROM tmp_container;
    END IF;
END;
$body$
LANGUAGE plpgsql;


test=> SELECT * FROM temptabl(4);
 temptabl 
----------
(0 rows)

test=> SELECT * FROM temptabl(6);
 temptabl 
----------
        1
        2
        3
        4
        5
        6
(6 rows)

この方法では、元のクエリを1回だけ実行する必要があります。他のすべてのステートメントは一時テーブルで機能します。

7
dezso

返品セット

ドキュメントごと:

_RETURN NEXT_と_RETURN QUERY_は、実際には関数から返されません。関数の結果セットに0個以上の行を追加するだけです。その後、実行はPL/pgSQL関数の次のステートメントから続行されます。連続する_RETURN NEXT_または_RETURN QUERY_コマンドが実行されると、結果セットが作成されます。引数なしの最後のRETURNは、制御を関数から終了させます(または、制御を関数の最後に到達させることができます)。

代わりにEXCEPTIONを調達

そのため、EXCEPTIONを発生させることで操作をキャンセルでき、クライアントは行を取得しません。
それより安くはなりません:

_CREATE OR REPLACE FUNCTION f_min_records(min_ct integer = 10)  -- default minimum 10
  RETURNS SETOF tbl AS
$func$
DECLARE
   row_ct int;
BEGIN
   RETURN QUERY EXECUTE 'some dynamic query (matching return type)';

   GET DIAGNOSTICS row_ct = ROW_COUNT;

   IF row_ct < min_ct THEN
      RAISE EXCEPTION 'Only % rows! Requested minimum was %.', row_ct, min_ct;
   END IF;
END
$func$  LANGUAGE plpgsql;
_

呼び出し(デフォルトで最小10行):

_SELECT * FROM f_min_records_wrapper();
_

マニュアルに コード例(get_available_flightid() を含めました。

EXCEPTIONを調達しないでください

私の知る限り、戻り値セットが実際に返されないようにする唯一の方法は、例外を発生させることです。例外を発生させたくない場合は、現在のplpgsqlを少し使っているだけです。

  • 同じ関数で EXCEPTION をトラップしても、結果セットは引き続き返されます。

  • ネストされたブロック で解決しようとしましたが失敗しました。戻り値セットに違いはないようです。

  • しかし、関数呼び出しを外部関数にネストして、そこで例外をキャッチできます。それは期待通りに機能します:

上記の拡張機能に加えて

_CREATE OR REPLACE FUNCTION f_min_records(min_ct integer = 10)  -- default minimum 10
  RETURNS SETOF t AS
$func$
DECLARE
   row_ct int;
BEGIN
   RETURN QUERY EXECUTE 'SELECT * from t';   -- some dynamic query (matching return type)

   GET DIAGNOSTICS row_ct = ROW_COUNT;

   IF row_ct < min_ct THEN
      RAISE SQLSTATE 'BRRRR'   -- 5 ASCII chars
      USING MESSAGE = format('Only %s rows! Requested minimum was %s.', row_ct, min_ct);
   END IF;

END
$func$  LANGUAGE plpgsql;
_

実際に呼び出すラッパー関数を作成します。

_CREATE OR REPLACE FUNCTION f_min_records_wrapper(min_ct integer = 10)
  RETURNS SETOF t AS
$func$
BEGIN
   RETURN QUERY
   SELECT * from f_min_records(min_ct);

EXCEPTION 
   WHEN SQLSTATE 'BRRRR' THEN
   RAISE NOTICE '%', SQLERRM;  -- optionally pass error msg
END
$func$  LANGUAGE plpgsql;
_

コール:

_SELECT * FROM f_min_records_wrapper(17);
_

一時テーブルを使用した代替

deszoの答え と同じ基本的な考え方ですが、個別のカウントとその他の機能は避けてください。

_CREATE OR REPLACE FUNCTION f_temptbl(min_ct integer = 10)
  RETURNS SETOF t AS
$func$
DECLARE
   row_ct int;
BEGIN
   DROP TABLE IF EXISTS _temptbl;                      -- for mult. calls in 1 transaction
   CREATE TEMP TABLE _temptbl (LIKE t) ON COMMIT DROP; -- match RETURNS type

   EXECUTE 'INSERT INTO _temptbl SELECT * FROM t';     -- text with dyn SQL

   GET DIAGNOSTICS row_ct = ROW_COUNT;

   IF row_ct >= min_ct THEN
      RETURN QUERY TABLE _temptbl;
   END IF;
END;
$func$  LANGUAGE plpgsql;
_

SQL Fiddleすべてを示しています。

機能の希望-現在存在しない(9.4を含む)

リターンセットをキャンセルするコマンドはすばらしいでしょう。

_RETURN CANCEL;
_

または、「ロールバック」するオプションの行数を使用することもできます(デフォルトはすべて):

_RETURN CANCEL 10;
_
4