Restituisce un record con la funzione PL / pgSQL per accelerare la query


10

Ho un demone di gioco non forking scritto in Perl , che utilizza query acync per scrivere le statistiche dei giocatori in un database PostgreSQL 9.3. Ma quando ho bisogno di leggere qualcosa dal database (come se un giocatore è bandito o se il giocatore ha uno stato VIP), allora uso le query sincrone.

Questo fa fermare il gioco per un breve momento, fino a quando il valore non viene letto dal database.

Non riesco a riscrivere il mio demone di gioco per usare query asincrone per leggere i valori (ci ho provato, ma ha richiesto troppe modifiche), quindi mia domanda è : avrebbe senso combinare diverse query non correlate (che devo fare quando un nuovo giocatore si collega) a 1 procedura e come posso restituire più valori contemporaneamente al mio programma Perl?

Le mie query attuali prendono tutti un ID giocatore come parametro e restituiscono 1 valore:

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

Per combinare le domande di cui sopra probabilmente ho bisogno di una procedura come questa:

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;

Aiutatemi a dichiarare correttamente la procedura sopra descritta.

Risposte:


13

L'uso dei OUTparametri raggiunge sostanzialmente lo stesso risultato della risposta di @ klin, ma senza creare tipi definiti dall'utente. Basta spostare tutte le variabili dal blocco declare nell'elenco degli argomenti come OUTparametri:

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;

Questo restituirà un record (esattamente uno), quindi puoi selezionarne i valori come un normale record:

-- 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 funziona alla grande, grazie. Solo una piccola domanda: Attualmente ho due NULLo TRUEnel mio is_bannedvariabile con questa affermazione: select true into is_banned from pref_ban where id=_id. C'è un modo per cambiarlo in FALSEo TRUE?
Alexander Farber,

1
Sì, is_banned := exists(select 1 from pref_ban where id=_id)dovrebbe funzionare, ma questa è una domanda diversa.
Pozs,

6

È necessario definire un tipo composto. È possibile utilizzarlo come tipo di ritorno di funzione e per le variabili record all'interno di una funzione.

Esempio:

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

A mio avviso, utilizzare funzioni come questa è abbastanza ragionevole in termini sia di prestazioni che di logica applicativa.


I tipi compositi definiti dall'utente sono molto utili se si desidera restituire un set di righe dalla propria funzione. Quindi è necessario definire il tipo restituito della funzione come setof composite-typee usare return nextoreturn query.

Esempio:

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
L'uso dei OUTparametri raggiunge sostanzialmente la stessa cosa, ma senza creare tipi definiti dall'utente: postgresql.org/docs/current/static/…
pozs

@pozs +1 grazie, vorrei utilizzare i OUTparametri, ma come SELECTnel mio caso di 4 query non correlate?
Alexander Farber,

@klin +1 grazie, ho provato il tuo suggerimento e funziona. Per creare il mio tipo personalizzato l'ho usato drop type if exists user_type cascade; create type user_type as(...);perché il mio script Perl chiama le istruzioni SQL ogni volta all'avvio.
Alexander Farber,

1
Non dovresti farlo. Le funzioni in Postgres sono stored procedure. Una volta creati, sono pronti per l'uso in qualsiasi sessione. Lo stesso vale per i tipi definiti dall'utente. Devi eliminare un tipo composito solo se hai intenzione di cambiarlo (o rimuoverlo del tutto).
klin,

+1 per "selezionare * da my_function ()". Stavo facendo "select my_function ()" e avevo dei problemi.
Ilonpilaaja,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.