web-dev-qa-db-ja.com

PL / pgSQLを使用して、複数のフィールドをPostgreSQLのレコードとして返します

PL/pgSQLを使用してSPを作成しています。
複数の異なるテーブルのフィールドで構成されるレコードを返します。次のようになります。

CREATE OR REPLACE FUNCTION get_object_fields(name text)
  RETURNS RECORD AS $$
BEGIN
  -- fetch fields f1, f2 and f3 from table t1
  -- fetch fields f4, f5 from table t2
  -- fetch fields f6, f7 and f8 from table t3
  -- return fields f1 ... f8 as a record
END
$$ language plpgsql; 

さまざまなテーブルのフィールドを単一のレコードのフィールドとして返すにはどうすればよいですか?

[編集]

上記の例があまりにも単純すぎることがわかりました。取得する必要があるフィールドの一部は、クエリ対象のデータベーステーブルに個別の行として保存されますが、「フラット化された」レコード構造でそれらを返します。

以下のコードは、さらに説明するのに役立ちます。

CREATE TABLE user (id int, school_id int, name varchar(32));

CREATE TYPE my_type (
  user1_id   int,
  user1_name varchar(32),
  user2_id   int,
  user2_name varchar(32)
);

CREATE OR REPLACE FUNCTION get_two_users_from_school(schoolid int)
  RETURNS my_type AS $$
DECLARE
  result my_type;
  temp_result user;
BEGIN
  -- for purpose of this question assume 2 rows returned
  SELECT id, name INTO temp_result FROM user where school_id = schoolid LIMIT 2;
  -- Will the (pseudo)code below work?:
  result.user1_id := temp_result[0].id ;
  result.user1_name := temp_result[0].name ;
  result.user2_id := temp_result[1].id ;
  result.user2_name := temp_result[1].name ;
  return result ;
END
$$ language plpgsql
63
skyeagle

新しい型を定義し、その型を返す関数を定義する必要があります。

CREATE TYPE my_type AS (f1 varchar(10), f2 varchar(10) /* , ... */ );

CREATE OR REPLACE FUNCTION get_object_fields(name text) 
RETURNS my_type 
AS 
$$

DECLARE
  result_record my_type;

BEGIN
  SELECT f1, f2, f3
  INTO result_record.f1, result_record.f2, result_record.f3
  FROM table1
  WHERE pk_col = 42;

  SELECT f3 
  INTO result_record.f3
  FROM table2
  WHERE pk_col = 24;

  RETURN result_record;

END
$$ LANGUAGE plpgsql; 

複数のレコードを返す場合は、関数をreturns setof my_typeとして定義する必要があります


更新

別のオプションは、Postgres 8.4で導入されたTYPEを作成する代わりにRETURNS TABLE()を使用することです

CREATE OR REPLACE FUNCTION get_object_fields(name text) 
  RETURNS TABLE (f1 varchar(10), f2 varchar(10) /* , ... */ )
...
56

多態的な結果を返すために CREATE TYPE を使用しないでください。代わりに RECORD type を使用して乱用してください。見てみな:

CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE 
  ret RECORD;
BEGIN
  -- Arbitrary expression to change the first parameter
  IF LENGTH(a) < LENGTH(b) THEN
      SELECT TRUE, a || b, 'a shorter than b' INTO ret;
  ELSE
      SELECT FALSE, b || a INTO ret;
  END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;

オプションでtwoまたはthree列を返すことができることに注意してください入力に応じて。

test=> SELECT test_ret('foo','barbaz');
             test_ret             
----------------------------------
 (t,foobarbaz,"a shorter than b")
(1 row)

test=> SELECT test_ret('barbaz','foo');
             test_ret             
----------------------------------
 (f,foobarbaz)
(1 row)

これはコードに大混乱をもたらすので、一貫した数の列を使用しますが、操作の成功を返す最初のパラメーターでオプションのエラーメッセージを返すのはとてつもなく便利です。一貫した数の列を使用して書き換えられました:

CREATE FUNCTION test_ret(a TEXT, b TEXT) RETURNS RECORD AS $$
DECLARE 
  ret RECORD;
BEGIN
  -- Note the CASTING being done for the 2nd and 3rd elements of the RECORD
  IF LENGTH(a) < LENGTH(b) THEN
      ret := (TRUE, (a || b)::TEXT, 'a shorter than b'::TEXT);
  ELSE
      ret := (FALSE, (b || a)::TEXT, NULL::TEXT);
   END IF;
RETURN ret;
END;$$ LANGUAGE plpgsql;

ほとんど壮大な辛さ:

test=> SELECT test_ret('foobar','bar');
   test_ret    
----------------
 (f,barfoobar,)
(1 row)

test=> SELECT test_ret('foo','barbaz');
             test_ret             
----------------------------------
 (t,foobarbaz,"a shorter than b")
(1 row)

しかし、選択したORMレイヤーが値を選択した言語のネイティブデータ型に変換できるように、それを複数の行にどのように分割するのでしょうか?辛さ:

test=> SELECT a, b, c FROM test_ret('foo','barbaz') AS (a BOOL, b TEXT, c TEXT);
 a |     b     |        c         
---+-----------+------------------
 t | foobarbaz | a shorter than b
(1 row)

test=> SELECT a, b, c FROM test_ret('foobar','bar') AS (a BOOL, b TEXT, c TEXT);
 a |     b     | c 
---+-----------+---
 f | barfoobar | 
(1 row)

これは、PostgreSQLで最もクールで未使用の機能の1つです。言葉を広めてください。

104
Sean

これは、simplerで、 OUT parameters

CREATE OR REPLACE FUNCTION get_object_fields(
          name text
    ,OUT user1_id   int
    ,OUT user1_name varchar(32)
    ,OUT user2_id   int
    ,OUT user2_name varchar(32)
) AS 
$func$
BEGIN
    SELECT t.user1_id, t.user1_name
    INTO     user1_id,   user1_name
    FROM   tbl1 t
    WHERE  t.tbl1_id = 42;

    user2_id := user1_id + 43; -- some calculation

    SELECT t.user2_name
    INTO     user2_name
    FROM   tbl2 t
    WHERE  t.tbl2_i = user2_id;
END
$func$ LANGUAGE plpgsql;
  • このplpgsql関数のためだけに型を作成する必要はありません。いくつかの関数を同じ型にバインドしたい場合は、が便利です。 OUTパラメーターが追加されたため、これを使用することはほとんどありません。

  • お気づきかもしれませんが、RETURNステートメントはありません。 OUTパラメーターは自動的に返されるため、RETURNステートメントは不要です。

  • OUTパラメーターは関数本体内のすべての場所に表示されるため(他の変数と同じように使用できます)、名前の競合を避けるために、同じ名前の列をテーブル修飾してください。

さらにシンプル-または複数の行を返す

ほとんどの場合、これはさらに簡略化できます。関数本体のクエリを組み合わせることができる場合がありますが、通常は(常にではありませんが)高速です。そして RETURNS TABLE() -Postgres 8.4で導入されました(この質問が尋ねられるずっと前から)。

上記の例は次のように書き換えることができます。

CREATE OR REPLACE FUNCTION get_object_fields(name text)
  RETURNS TABLE (
     user1_id   int
    ,user1_name varchar(32)
    ,user2_id   int
    ,user2_name varchar(32)) AS 
$func$
BEGIN
    RETURN QUERY
    SELECT t1.user1_id, t1.user1_name, t2.user2_id, t2.user2_name
    FROM   tbl1 t1
    JOIN   tbl2 t2 ON t2.user2_id = t1.user1_id + 43
    WHERE  t1.tbl1_id = 42
    LIMIT  1;  -- may be optional
END
$func$ LANGUAGE plpgsql; 
  • RETURNS TABLEは、RETURNS recordと組み合わせたOUTパラメーターの束を持つのと事実上同じで、少し短く/エレガントになります。

  • 主な違いは、最初のバージョンalwaysが1行を返すのに対して、この関数は0、1または多くの行を返すことができることです。
    確認したい場合、これは0または1行のみを返し、示されているようにLIMIT 1を追加します。

  • RETURN QUERYは、クエリから結果を直接返す非常に便利な現代的な方法です。
    1つの関数で複数のインスタンスを使用して、出力に行を追加できます。

さまざまな行タイプ

関数が動的に異なる行タイプで結果を返すことになっている場合、入力に応じて、こちらをご覧ください。

49

この正確なレコードレイアウトを持つテーブルがある場合は、その名前をタイプとして使用します。そうでない場合は、タイプを明示的に宣言する必要があります。

CREATE OR REPLACE FUNCTION get_object_fields
        (
        name text
        )
RETURNS mytable
AS
$$
        DECLARE f1 INT;
        DECLARE f2 INT;
        …
        DECLARE f8 INT;
        DECLARE retval mytable;
        BEGIN
        -- fetch fields f1, f2 and f3 from table t1
        -- fetch fields f4, f5 from table t2
        -- fetch fields f6, f7 and f8 from table t3
                retval := (f1, f2, …, f8);
                RETURN retval;
        END
$$ language plpgsql; 
5
Quassnoi

リターンクエリを使用して、単にレコードのリターンセットとして使用することで、これを実現できます。

CREATE OR REPLACE FUNCTION schemaName.get_two_users_from_school(schoolid bigint)
 RETURNS SETOF record
 LANGUAGE plpgsql
AS $function$
begin

 return query
  SELECT id, name FROM schemaName.user where school_id = schoolid;

end;
$function$

この関数を次のように呼び出します:select * from schemaName.get_two_users_from_school(schoolid) as x(a bigint, b varchar);

2
Ritesh Jha

oUTパラメーターとCROSS JOINを使用してこれを行うことができます

CREATE OR REPLACE FUNCTION get_object_fields(my_name text, OUT f1 text, OUT f2 text)
AS $$
SELECT t1.name, t2.name
FROM  table1 t1 
CROSS JOIN table2 t2 
WHERE t1.name = my_name AND t2.name = my_name;
$$ LANGUAGE SQL;

それをテーブルとして使用します:

select get_object_fields( 'Pending') ;
get_object_fields
-------------------
(Pending,code)
(1 row)

または

select * from get_object_fields( 'Pending');
f1    |   f
---------+---------
Pending | code
(1 row)

または

select (get_object_fields( 'Pending')).f1;
f1
---------
Pending
(1 row)
0
Jerome RIVRON