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
新しい型を定義し、その型を返す関数を定義する必要があります。
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) /* , ... */ )
...
多態的な結果を返すために 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つです。言葉を広めてください。
これは、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つの関数で複数のインスタンスを使用して、出力に行を追加できます。
関数が動的に異なる行タイプで結果を返すことになっている場合、入力に応じて、こちらをご覧ください。
この正確なレコードレイアウトを持つテーブルがある場合は、その名前をタイプとして使用します。そうでない場合は、タイプを明示的に宣言する必要があります。
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;
リターンクエリを使用して、単にレコードのリターンセットとして使用することで、これを実現できます。
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);
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)