Retornar um registro com a função PL / pgSQL - para acelerar a consulta

10

Eu tenho um daemon de jogo não-bifurcado escrito em Perl , que usa consultas acync para gravar estatísticas de jogador em um banco de dados PostgreSQL 9.3. Mas quando preciso ler algo do banco de dados (como se um jogador é banido ou se o jogador tem um status VIP), então uso consultas síncronas.

Isso faz o jogo parar por um breve momento, até que o valor seja lido no banco de dados.

Não consigo reescrever meu daemon de jogo para usar consultas assíncronas para ler valores (tentei, mas exigiu muitas alterações); portanto, minha pergunta é : faria sentido combinar várias consultas não relacionadas (que eu preciso fazer quando um novo jogador conecta) a 1 procedimento e como eu poderia retornar vários valores ao mesmo tempo ao meu programa Perl?

Todas as minhas consultas atuais usam um ID de jogador como parâmetro e retornam 1 valor:

-- 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=?

Para combinar as consultas acima, provavelmente preciso de um procedimento como este:

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;

Por favor, ajude-me a declarar o procedimento acima corretamente.

Alexander Farber
fonte

Respostas:

13

O uso de OUTparâmetros alcança basicamente o mesmo da resposta do @ klin, mas sem a criação de tipos definidos pelo usuário. Apenas mova todas as suas variáveis ​​do bloco declare para a lista de argumentos como OUTparâmetros:

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;

Isso retornará um registro (exatamente um), para que você possa selecionar seus valores como um registro normal:

-- 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
fonte
+1 isso funciona muito bem, obrigado. Apenas uma pequena questão: atualmente tenho tanto NULLou TRUEna minha is_bannedvariável com a seguinte declaração: select true into is_banned from pref_ban where id=_id. Existe uma maneira de mudar para FALSEou TRUE?
Alexander Farber
11
Sim, is_banned := exists(select 1 from pref_ban where id=_id)deve funcionar, mas essa é uma pergunta diferente.
pozs
6

Você deve definir um tipo composto. Você pode usá-lo como tipo de retorno de função e para registrar variáveis ​​dentro de uma função.

Exemplo:

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();

Na minha opinião, o uso de funções como essa é bastante razoável em termos de desempenho e lógica de aplicação.


Tipos compostos definidos pelo usuário são muito úteis se você deseja retornar um conjunto de linhas de sua função. Em seguida, defina o tipo de retorno da função como setof composite-typee use return nextoureturn query.

Exemplo:

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
klin
fonte
11
Usando OUTparâmetros alcançar basicamente a mesma coisa, mas sem criar tipos definidos pelo usuário: postgresql.org/docs/current/static/...
pozs
@pozs +1 obrigado, gostaria de usar os OUTparâmetros - mas como SELECTeles no meu caso de 4 consultas não relacionadas?
Alexander Farber
@linlin obrigado, tentei sua sugestão e ela funciona. Para criar meu tipo personalizado que usei drop type if exists user_type cascade; create type user_type as(...);porque meu script Perl chama as instruções SQL toda vez no momento da inicialização.
Alexander Farber
11
Você não deveria fazer isso. Funções no Postgres são procedimentos armazenados . Uma vez criado, está pronto para uso em qualquer sessão. O mesmo diz respeito aos tipos definidos pelo usuário. Você precisa soltar um tipo composto apenas se quiser alterá-lo (ou removê-lo).
klin
+1 em "selecione * de minha_função ()". Eu estava fazendo "select my_function ()" e tendo problemas.
Ilonpilaaja 16/03/19