web-dev-qa-db-ja.com

plpgsqlでレコードタイプ変数を使用するには?

同じストアド関数内の別のクエリのレコード型変数に格納されたクエリ結果を使用するにはどうすればよいですか? Postgres 9.4.4を使用します。

このようなテーブルでは:

create table test (id int, tags text[]);
insert into test values (1,'{a,b,c}'),
                        (2,'{c,d,e}');

私は以下のような関数(簡単な)を書きました:

CREATE OR REPLACE FUNCTION func(_tbl regclass)
RETURNS TABLE (t TEXT[], e TEXT[])
LANGUAGE plpgsql AS $$
DECLARE
  t RECORD;
  c INT;
BEGIN
  EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;
  SELECT count(*) FROM t INTO c;
  RAISE NOTICE '% results', c;
  SELECT * FROM t;
END
$$;

...しかし動作しませんでした:

select func('test');
ERROR:  42P01: relation "t" does not exist
LINE 1: SELECT count(*) FROM t
                             ^
QUERY:  SELECT count(*) FROM t
CONTEXT:  PL/pgSQL function func(regclass) line 7 at SQL statement
LOCATION:  parserOpenTable, parse_relation.c:986
14
SG. Nihonbashi

コアの誤解:record変数は、テーブル(よく知られたタイプの0-n行)ではなく、single行(またはNULL)を保持します。 PostgresやPL/pgSQLには「テーブル変数」はありません。タスクに応じて、さまざまな選択肢があります。

したがって、multiple行をrecord型変数に割り当てることはできません。この声明では:

_EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;
_

... Postgresは最初の行のみを割り当て、残りを破棄します。クエリでは「最初の」が適切に定義されていないため、最終的に任意の選択が行われます。明らかに、最初に述べた誤解によるものです。

record変数は、SQLクエリのテーブルの代わりに使用することもできません。これがエラーの主な原因です。

リレーション "t"は存在しません

tは単一のレコード/行であるため、count(*)が最初から意味をなさないことは明らかです。

最後に(残りが機能する場合でも)、戻り値の型が間違っているようです: _(t TEXT[], e TEXT[])_。 _id, tags_をtに選択するので、_(id int, e TEXT[])_のようなものを返したいでしょう。

あなたがしようとしていることは次のように動作します

_CREATE OR REPLACE FUNCTION func(_tbl regclass)
  RETURNS TABLE (id int, e text[]) AS
$func$
DECLARE
   _ct int;
BEGIN
   EXECUTE format(
      'CREATE TEMP TABLE tmp ON COMMIT DROP AS
       SELECT id, tags FROM %s'
    , _tbl);

   GET DIAGNOSTICS _ct = ROW_COUNT; -- cheaper than another count(*)

   -- ANALYZE tmp;  -- if you are going to run multiple queries

   RAISE NOTICE '% results', _ct;

   RETURN QUERY TABLE tmp;
END
$func$ LANGUAGE plpgsql;
_

呼び出し(構文に注意してください!)

_SELECT * FROM func('test');
_

関連:

単なる概念実証。テーブル全体を選択している間は、代わりに基になるテーブルを使用するだけです。実際には、クエリにいくつかのWHERE句があります...

潜んでいる型の不一致に注意してください、count()bigintを返します。これをinteger変数に割り当てることはできません。キャストが必要です:count(*)::int

しかし、それを完全に置き換えました。EXECUTEの直後にこれを実行する方が安価です。

_GET DIAGNOSTICS _ct = ROW_COUNT; 
_

マニュアルの詳細

なぜANALYZE


余談:プレーンSQLのCTEが仕事をすることがよくあります

17