Renvoyer un enregistrement avec la fonction PL / pgSQL - pour accélérer la requête

10

J'ai un démon de jeu non-forking écrit en Perl , qui utilise des requêtes acync pour écrire les statistiques des joueurs dans une base de données PostgreSQL 9.3. Mais lorsque j'ai besoin de lire quelque chose dans la base de données (comme si un joueur est banni ou si le joueur a un statut VIP), j'utilise des requêtes synchrones.

Cela arrête le jeu pendant un court instant, jusqu'à ce que la valeur ait été lue dans la base de données.

Je ne peux pas réécrire mon démon de jeu pour utiliser des requêtes asynchrones pour lire des valeurs (j'ai essayé, mais cela nécessitait trop de changements), donc ma question est : serait-il judicieux de combiner plusieurs requêtes non liées (que je dois faire quand un nouveau joueur connects) à 1 procédure et comment pourrais-je retourner plusieurs valeurs en même temps à mon programme Perl?

Mes requêtes actuelles prennent toutes un ID de joueur comme paramètre et renvoient 1 valeur:

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

Pour combiner les requêtes ci-dessus, j'ai probablement besoin d'une procédure comme celle-ci:

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;

Veuillez m'aider à déclarer correctement la procédure ci-dessus.

Alexander Farber
la source

Réponses:

13

L'utilisation de OUTparamètres permet d' obtenir essentiellement la même chose que dans la réponse de @ klin, mais sans créer de types définis par l'utilisateur. Déplacez simplement toutes vos variables du bloc declare dans la liste d'arguments en tant que OUTparamètres:

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;

Cela retournera un enregistrement (exactement un), vous pouvez donc sélectionner ses valeurs comme un enregistrement 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
la source
+1 cela fonctionne très bien, merci. Juste une petite question: actuellement je soit NULLou TRUEdans ma is_bannedvariable cette déclaration: select true into is_banned from pref_ban where id=_id. Y a-t-il un moyen de le changer en FALSEou TRUE?
Alexander Farber
1
Oui, cela is_banned := exists(select 1 from pref_ban where id=_id)devrait fonctionner, mais c'est une question différente.
pozs
6

Vous devez définir un type composite. Vous pouvez l'utiliser comme type de retour de fonction et pour enregistrer des variables à l'intérieur d'une fonction.

Exemple:

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

À mon avis, l'utilisation de fonctions comme celle-ci est tout à fait raisonnable en termes de performances et de logique d'application.


Les types composites définis par l'utilisateur sont très utiles si vous souhaitez renvoyer un ensemble de lignes à partir de votre fonction. Ensuite, vous devez définir le type de retour de la fonction as setof composite-typeet utiliser return nextoureturn query.

Exemple:

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
la source
1
L'utilisation de OUTparamètres permet essentiellement la même chose, mais sans créer de types définis par l'utilisateur: postgresql.org/docs/current/static/…
pozs
@pozs +1 merci, je voudrais utiliser les OUTparamètres - mais comment SELECTles utiliser dans mon cas de 4 requêtes indépendantes?
Alexander Farber
@klin +1 merci, j'ai essayé votre suggestion et ça marche. Pour créer mon type personnalisé que j'ai utilisé drop type if exists user_type cascade; create type user_type as(...);parce que mon script Perl appelle les instructions SQL à chaque fois au démarrage.
Alexander Farber
1
Tu ne devrais pas faire ça. Les fonctions de Postgres sont des procédures stockées . Une fois créés, ils sont prêts à être utilisés dans n'importe quelle session. Il en va de même pour les types définis par l'utilisateur. Vous devez supprimer un type composite uniquement si vous allez le changer (ou le supprimer du tout).
klin
+1 pour "select * from my_function ()". Je faisais "sélectionner ma_fonction ()" et j'avais des problèmes.
Ilonpilaaja