web-dev-qa-db-ja.com

追加の列を持つテーブルを返す関数

テーブルと追加の列を返すPostgreSQL関数を書きたいのですが。 RETURNS TABLE (col1 type, col2 type, ...)を手動で指定せずにこれを行う方法はありますか?

たとえば、次の関数について考えます。

CREATE FUNCTION get_users_with_most_videos_since_time(ts TIMESTAMPTZ)
  RETURNS SETOF "user" AS $$
    SELECT
      u.*,
      count(v.id) AS vids_since

    FROM "user" AS u
    INNER JOIN "video" AS v ON v.creator_id = u.id
    WHERE v.created_at > ts

    GROUP BY u.id
    ORDER BY vids_since DESC;
$$ LANGUAGE SQL;

これはエラーで失敗します:

ERROR:  return type mismatch in function declared to return "user"
DETAIL:  Final statement returns too many columns.

公平に言えば、vids_sinceテーブルに存在しない"user"列が含まれています。

これを修正するには、次のように変更します。

CREATE FUNCTION get_users_with_most_videos_since_time(ts TIMESTAMPTZ)
  RETURNS 
    TABLE (<<< all columns from table "user" >>>, vids_since BIGINT) 
  AS $$
    ...

ここに"user"テーブルのスキーマ全体を再コピーせずにこれを行う方法はありますか?

6
bcattle

AIUI、あなたの望みは、関数のRETURNS節を短くすることです。同時にテーブルの行タイプへの依存関係を確立する必要があるかどうかはわかりませんが、ここでも意味があります。

RETURNS SETOFrettypeの形式は、システムカタログに格納される使用されたタイプに依存します。 マニュアル:

戻り値の型は、基本型、複合型、またはドメイン型であるか、テーブル列の型を参照できます。

したがって、必要な行タイプをシステムに登録するだけです(一度だけ)。 CREATE TYPE を使用して複合型を明示的に作成できます。または、別のテーブル、ビュー、またはマテリアライズドビューを作成して、暗黙的に行うこともできます。一時的な機能のための一時的なビューまたはテーブルでさえ(セッションの最後で死にます)。 @ Abelistoがコメントしたように:

CREATE VIEW user_plus AS 
SELECT *, null::bigint AS vids_since
FROM   "user"
WHERE  false;

ただしSELECT *ビューの作成時間で列のリストに解決されます( "早期バインディング")。後で基になるテーブルに列を追加しても、ビューとその行の型は更新されず、関数の実行時に別の型の不一致が発生します。ただし、基になるテーブルから列を削除しようとすると、その列に依存するビューの列に不満があります。そして、あなたは、ビュー(および関数)を変更します

関数は簡略化され、高速化できます。

CREATE FUNCTION get_users_with_most_videos_since_time(_ts timestamptz)
  RETURNS SETOF user_plus AS
$func$
   SELECT *   -- effectively the same as: u.*, v.vids_since
   FROM   "user" AS u
   JOIN  (    -- aggregate *before* you join
      SELECT creator_id AS id, count(*) AS vids_since  -- note the column alias
      FROM   video v
      WHERE  created_at > _ts
      GROUP  BY 1
      ) v USING (id)  -- USING only retains one id column
   ORDER  BY v.vids_since DESC;
$func$  LANGUAGE SQL;

この関数を持つことは実際に意味があります。結果に含まれていない列でフィルタリングしたい。プレーンなVIEWでは不可能です。

余談:userのような予約語を識別子として使用することはお勧めしません。それはロードされたフットガンです。

3