使用PL / pgSQL函数返回记录-加快查询速度


10

我有一个用Perl编写的非分叉游戏守护进程,该守护进程使用acync查询将玩家统计信息写入PostgreSQL 9.3数据库。但是,当我需要从数据库中读取某些内容时(例如,如果某个播放器被禁止或该播放器具有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;

请帮助我正确声明以上过程。

Answers:


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;

这将返回一条记录(恰好是一条),因此您可以选择其值作为普通记录:

-- 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效果很好,谢谢。只是一个小问题:当前,我的语句中包含NULL或。有没有办法将其更改为或?TRUEis_bannedselect true into is_banned from pref_ban where id=_idFALSETRUE
亚历山大·法伯

1
是的,is_banned := exists(select 1 from pref_ban where id=_id)应该可以,但这是另一个问题。
pozs

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-type并使用return nextreturn 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个无关查询的情况下如何使用它们?
亚历山大·法伯

@klin +1谢谢,我已经尝试过您的建议,它可以工作。创建自定义类型之所以使用,drop type if exists user_type cascade; create type user_type as(...);是因为Perl脚本每次在启动时都会调用SQL语句。
亚历山大·法伯

1
你不应该那样做。Postgres中的函数是存储过程。创建后即可在任何会话中使用。用户定义的类型也是如此。仅在要更改(或完全删除)复合类型时,才必须删除它。
klin 2014年

+1表示“从my_function()选择*”。我在做“选择my_function()”并遇到麻烦。
Ilonpilaaja
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.