Вернуть запись с функцией PL / pgSQL - чтобы ускорить запрос

10

У меня есть не разветвляющийся игровой демон, написанный на Perl , который использует асинхронные запросы для записи статистики игрока в базу данных PostgreSQL 9.3. Но когда мне нужно что-то прочитать из базы данных (например, если игрок забанен или у игрока есть VIP-статус), тогда я использую синхронные запросы.

Это останавливает игру на короткое время, пока значение не будет считано из базы данных.

Я не могу переписать свой игровой демон, чтобы использовать асинхронные запросы для чтения значений (я пытался, но это потребовало слишком много изменений), поэтому мой вопрос : имеет ли смысл объединять несколько несвязанных запросов (что мне нужно сделать, когда новый игрок подключается) к 1 процедуре, и как я могу вернуть несколько значений одновременно моей программе Perl?

Все мои текущие запросы принимают идентификатор игрока в качестве параметра и возвращают 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;

Это вернет запись (ровно одну), поэтому вы можете выбрать ее значения как обычную запись:

-- 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;
pozs
источник
+1 это прекрасно работает, спасибо. Только один маленький вопрос: в настоящее время у меня есть либо NULLили TRUEв моей is_bannedпеременной с этим утверждением: select true into is_banned from pref_ban where id=_id. Есть ли способ изменить это на FALSEили TRUE?
Александр Фарбер
1
Да, is_banned := exists(select 1 from pref_ban where id=_id)должно работать, но это другой вопрос.
Поз
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 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 несвязанных запроса?
Александр Фарбер
@klin +1 спасибо, я попробовал ваше предложение, и оно работает. Для создания своего пользовательского типа я использовал, drop type if exists user_type cascade; create type user_type as(...);потому что мой Perl-скрипт вызывает операторы SQL каждый раз при запуске.
Александр Фарбер
1
Вы не должны этого делать. Функции в Postgres - это хранимые процедуры. После создания готовы к использованию в любой сессии. То же самое касается пользовательских типов. Вы должны отказаться от составного типа, только если вы собираетесь изменить его (или вообще удалить).
Клин
+1 за "выбрать * из my_function ()". Я делал "выберите my_function ()" и возникли проблемы.
Ilonpilaaja