Geben Sie einen Datensatz mit der PL / pgSQL-Funktion zurück, um die Abfrage zu beschleunigen


10

Ich habe einen nicht perkenden Spieledämon in Perl geschrieben , der Acync-Abfragen verwendet, um Spielerstatistiken in eine PostgreSQL 9.3-Datenbank zu schreiben. Wenn ich jedoch etwas aus der Datenbank lesen muss (z. B. wenn ein Spieler gesperrt ist oder wenn der Spieler einen VIP-Status hat), verwende ich synchrone Abfragen.

Dadurch wird das Spiel für einen kurzen Moment angehalten, bis der Wert aus der Datenbank gelesen wurde.

Ich kann meinen Spieledämon nicht umschreiben, um asynchrone Abfragen zum Lesen von Werten zu verwenden (ich habe es versucht, aber es waren zu viele Änderungen erforderlich). Meine Frage lautet daher : Wäre es sinnvoll, mehrere nicht verwandte Abfragen zu kombinieren (die ich bei einem neuen Spieler durchführen muss)? verbindet) mit 1 Prozedur und wie kann ich mehrere Werte gleichzeitig an mein Perl-Programm zurückgeben?

Meine aktuellen Abfragen verwenden alle eine Spieler-ID als Parameter und geben 1 Wert zurück:

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

Um die obigen Abfragen zu kombinieren, benötige ich wahrscheinlich eine Prozedur wie diese:

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;

Bitte helfen Sie mir, das oben beschriebene Verfahren ordnungsgemäß zu deklarieren.

Antworten:


13

Die Verwendung von OUTParametern erreicht im Grunde das Gleiche wie in der Antwort von @ klin, ohne jedoch benutzerdefinierte Typen zu erstellen. Verschieben Sie einfach alle Ihre Variablen aus dem Deklarationsblock als OUTParameter in die Argumentliste :

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;

Dadurch wird ein Datensatz (genau einer) zurückgegeben, sodass Sie seine Werte als normalen Datensatz auswählen können:

-- 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 das funktioniert super, danke. Nur eine kleine Frage: Derzeit habe ich entweder NULLoder TRUEin meiner is_bannedVariablen mit dieser Aussage : select true into is_banned from pref_ban where id=_id. Gibt es eine Möglichkeit, es in FALSEoder zu ändern TRUE?
Alexander Farber

1
Ja, is_banned := exists(select 1 from pref_ban where id=_id)sollte funktionieren, aber das ist eine andere Frage.
pozs

6

Sie sollten einen zusammengesetzten Typ definieren . Sie können es als Rückgabetyp und für Datensatzvariablen innerhalb einer Funktion verwenden.

Beispiel:

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

Meiner Meinung nach ist die Verwendung solcher Funktionen sowohl hinsichtlich der Leistung als auch der Anwendungslogik durchaus sinnvoll.


Benutzerdefinierte zusammengesetzte Typen sind sehr nützlich, wenn Sie eine Reihe von Zeilen aus Ihrer Funktion zurückgeben möchten. Dann sollten Sie den Rückgabetyp der Funktion als definieren setof composite-typeund return nextoder verwendenreturn query.

Beispiel:

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
Die Verwendung von OUTParametern erreicht im Grunde das Gleiche, ohne jedoch benutzerdefinierte Typen zu erstellen
pozs

@pozs +1 danke, ich möchte die OUTParameter verwenden - aber wie sind SELECTsie in meinem Fall von 4 nicht verwandten Abfragen?
Alexander Farber

@klin +1 danke, ich habe deinen Vorschlag ausprobiert und es funktioniert. Zum Erstellen meines benutzerdefinierten Typs habe ich verwendet, drop type if exists user_type cascade; create type user_type as(...);da mein Perl-Skript die SQL-Anweisungen jedes Mal beim Start aufruft.
Alexander Farber

1
Das solltest du nicht tun. Funktionen in Postgres sind gespeicherte Prozeduren. Einmal erstellt, können sie in jeder Sitzung verwendet werden. Gleiches gilt für benutzerdefinierte Typen. Sie müssen einen zusammengesetzten Typ nur löschen, wenn Sie ihn ändern (oder überhaupt entfernen) möchten.
klin

+1 für "select * from my_function ()". Ich habe "select my_function ()" ausgeführt und Probleme gehabt.
Ilonpilaaja
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.