PL / pgSQL関数でレコードを返す-クエリを高速化する


10

私が持っているPerlで書かれた非フォークゲームデーモンのPostgreSQL 9.3データベースに書き込みプレーヤー統計にacyncクエリを使用しています、。しかし、データベースから何かを読み取る必要がある場合(プレーヤーが禁止されている場合や、プレーヤーにVIPステータスがある場合など)は、同期クエリを使用します。

これにより、データベースから値が読み取られるまで、ゲームが一瞬停止します。

値を読み取るために非同期クエリを使用するようにゲームデーモンを書き換えることはできません(試してみましたが、変更が多すぎました)。私の質問は、いくつかの無関係なクエリを組み合わせることは理にかなっています(新しいプレーヤーの場合に行う必要があることです)接続)を1つのプロシージャに追加し、Perlプログラムに同時にいくつかの値を返すにはどうすればよいですか?

現在のクエリはすべて、プレーヤーIDをパラメーターとして取り、1つの値を返します。

-- Has the player been banned?
select true from pref_ban where id=?

-- What is the reputation of this player?
select
count(nullif(nice, false)) -
count(nullif(nice, true)) as rep
from pref_rep where id=?

-- Is he or she a special VIP player?
select vip > now() as vip from pref_users where id=?

-- How many games has the player played to the end?
select completed from pref_match where id=?

上記のクエリを組み合わせるには、おそらく次のような手順が必要です。

create or replace function get_user_info(_id varchar) returns XXX as $BODY$
    declare
        is_banned boolean;
        reputation integer;
        is_vip boolean;
        completed_games integer;
    begin

        select 1 into is_banned from pref_ban where id=_id;

        select
        count(nullif(nice, false)) -
        count(nullif(nice, true)) 
        into reputation
        from pref_rep where id=_id;

        select vip > now() into is_vip from pref_users where id=_id;

        select completed into completed_games from pref_match where id=_id;

        return XXX; /* How to return 4 values here? */

    end;
$BODY$ language plpgsql;

上記の手順を適切に宣言するのを手伝ってください。

回答:


13

OUTパラメータを使用すると、基本的に@klinの回答と同じことを実現できますが、ユーザー定義型を作成する必要はありません。すべての変数を宣言ブロックからOUTパラメーターとして引数リストに移動するだけです。

create or replace function get_user_info(
    IN  _id varchar,
    OUT is_banned boolean,
    OUT reputation integer,
    OUT is_vip boolean,
    OUT completed_games integer
)
-- no returns clause necessary, output structure controlled by OUT parameters
-- returns XXX
as $BODY$
begin
    select true into is_banned from pref_ban where id=_id;

    select
    count(nullif(nice, false)) -
    count(nullif(nice, true)) 
    into reputation
    from pref_rep where id=_id;

    select vip > now() into is_vip from pref_users where id=_id;

    select completed into completed_games from pref_match where id=_id;

    -- no return statement necessary, output values already stored in OUT parameters
    -- return XXX;
end
$BODY$ language plpgsql;

これはレコード(正確には1つ)を返すため、その値を通常のレコードとして選択できます。

-- this will return all properties (columns) from your function:
select * from get_user_info();

-- these will return one property (column) from your function:
select is_banned from get_user_info();
select (get_user_info()).is_banned;

+1これは非常に効果的です。ありがとうございます。ただ一つの小さな質問:現在、私はどちらか持っているNULLTRUE、私にはis_bannedこの文で変数:select true into is_banned from pref_ban where id=_idFALSEまたはに変更する方法はありTRUEますか?
Alexander Farber 14年

1
はい、動作するis_banned := exists(select 1 from pref_ban where id=_id)はずですが、それは別の質問です。
pozs 14年

6

複合タイプを定義する必要があります関数の戻り値の型として、および関数内のレコード変数に使用できます。

例:

create type user_type as (
    is_banned boolean,
    reputation integer,
    is_vip boolean,
    completed_games integer);

create or replace function check_user_type ()
returns user_type language plpgsql as $$
declare
    rec user_type;
begin
    select true into rec.is_banned;
    select 100 into rec.reputation;
    select false into rec.is_vip;
    select 22 into rec.completed_games;
--  you can do the same in a little bit nicer way:
--  select true, 100, false, 22 into rec
    return rec;
end $$;

select * from check_user_type();

私の意見では、このような関数を使用することは、パフォーマンスとアプリケーションロジックの両方の点でかなり合理的です。


ユーザー定義の複合型は、関数から行のセットを返したい場合に非常に役立ちます。次に、関数の戻り値の型を次のように定義してsetof composite-typereturn nextまたはreturn query.

例:

create or replace function check_set_of_user_type ()
returns setof user_type language plpgsql as $$
declare
    rec user_type;
begin
    for rec in
        select i/2*2 = i, i, i < 3, i+ 20
        from generate_series(1, 4) i
    loop
        return next rec;
    end loop;

    return query 
        select true, 100+ i, true, 100+ i
        from generate_series(1, 2) i;
end $$;

select * from check_set_of_user_type();

 is_banned | reputation | is_vip | completed_games
-----------+------------+--------+-----------------
 f         |          1 | t      |              21
 t         |          2 | t      |              22
 f         |          3 | f      |              23
 t         |          4 | f      |              24
 t         |        101 | t      |             101
 t         |        102 | t      |             102

1
使用するOUTが、ユーザー定義型を作成することなく、パラメータは基本的に同じことを達成:postgresql.org/docs/current/static/...は
pozs

@pozs +1ありがとうございOUTます。パラメータを使用したいと思いますがSELECT、4つの無関係なクエリの場合、どのように使用しますか?
Alexander Farber

@klin +1ありがとうございます。私はあなたの提案を試しましたが、うまくいきました。drop type if exists user_type cascade; create type user_type as(...);私のPerlスクリプトは起動時に毎回SQLステートメントを呼び出すので、カスタムタイプを作成するために使用しました。
Alexander Farber 14年

1
あなたはそれをすべきではありません。Postgresの関数はストアドプロシージャです。作成したら、どのセッションでも使用できます。同じことがユーザー定義型にも関係します。複合型を削除する必要があるのは、それを変更する(または削除する)場合のみです。
klin

「select * from my_function()」の+1。「select my_function()」を実行していて、問題が発生しました。
Ilonpilaaja
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.