Devuelve un registro con la función PL / pgSQL para acelerar la consulta

10

Tengo un demonio de juego sin bifurcación escrito en Perl , que utiliza consultas acync para escribir estadísticas de jugador en una base de datos PostgreSQL 9.3. Pero cuando necesito leer algo de la base de datos (como si un jugador está prohibido o si el jugador tiene un estado VIP), entonces uso consultas sincrónicas.

Esto hace que el juego se detenga por un breve momento, hasta que el valor haya sido leído de la base de datos.

No puedo reescribir mi demonio del juego para usar consultas asíncronas para leer valores (lo intenté, pero requirió demasiados cambios), así que mi pregunta es : ¿tendría sentido combinar varias consultas no relacionadas (que debo hacer cuando un nuevo jugador conecta) a 1 procedimiento y ¿cómo podría devolver varios valores al mismo tiempo a mi programa Perl?

Todas mis consultas actuales toman una ID de jugador como parámetro y devuelven 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 las consultas anteriores, probablemente necesito un procedimiento 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, ayúdame a declarar el procedimiento anterior correctamente.

Alexander Farber
fuente

Respuestas:

13

El uso de OUTparámetros logra básicamente lo mismo que en la respuesta de @ klin, pero sin crear tipos definidos por el usuario. Simplemente mueva todas sus variables del bloque declarar a la 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;

Esto devolverá un registro (exactamente uno), por lo que puede seleccionar sus valores como un 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
fuente
+1 esto funciona muy bien, gracias. Sólo una pequeña pregunta: ¿En este momento tengo ya sea NULLo TRUEen mi is_bannedvariable, con esta declaración: select true into is_banned from pref_ban where id=_id. ¿Hay alguna manera de cambiarlo a FALSEo TRUE?
Alexander Farber
1
Sí, is_banned := exists(select 1 from pref_ban where id=_id)debería funcionar, pero esa es una pregunta diferente.
Pozs
6

Debe definir un tipo compuesto. Puede usarlo como tipo de función de retorno y para registrar variables dentro de una función.

Ejemplo:

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

En mi opinión, usar funciones como esta es bastante razonable en términos de rendimiento y lógica de aplicación.


Los tipos compuestos definidos por el usuario son muy útiles si desea devolver un conjunto de filas de su función. Luego debe definir el tipo de retorno de la función como setof composite-typey usar return nextoreturn query.

Ejemplo:

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
fuente
1
El uso de OUTparámetros logra básicamente lo mismo, pero sin crear tipos definidos por el usuario: postgresql.org/docs/current/static/…
pozs
@pozs +1 gracias, me gustaría usar los OUTparámetros, pero ¿cómo SELECTlos uso en mi caso de 4 consultas no relacionadas?
Alexander Farber
@klin +1 gracias, he probado tu sugerencia y funciona. Para crear mi tipo personalizado, lo he usado drop type if exists user_type cascade; create type user_type as(...);porque mi script Perl llama a las declaraciones SQL cada vez que se inicia.
Alexander Farber
1
No deberías hacer eso. Las funciones en Postgres son procedimientos almacenados . Una vez creados están listos para usar en cualquier sesión. Lo mismo se refiere a los tipos definidos por el usuario. Debe soltar un tipo compuesto solo si va a cambiarlo (o eliminarlo).
klin
+1 para "seleccionar * de mi_función ()". Estaba haciendo "select my_function ()" y tenía problemas.
Ilonpilaaja