Zwróć rekord za pomocą funkcji PL / pgSQL - aby przyspieszyć zapytanie

10

Mam demona gry bez rozwidlania napisanego w Perlu , który używa zapytań acync do zapisywania statystyk graczy w bazie danych PostgreSQL 9.3. Ale kiedy muszę przeczytać coś z bazy danych (np. Jeśli gracz zostanie zbanowany lub jeśli ma status VIP), korzystam z zapytań synchronicznych.

To powoduje, że gra zatrzymuje się na chwilę, dopóki wartość nie zostanie odczytana z bazy danych.

Nie mogę przepisać mojego demona gry, aby używał zapytań asynchronicznych do odczytu wartości (próbowałem, ale wymagało to zbyt wielu zmian), więc moje pytanie brzmi : czy warto połączyć kilka niepowiązanych zapytań (które muszę zadać, gdy nowy gracz łączy) do 1 procedury i jak mogę zwrócić kilka wartości jednocześnie do mojego programu Perl?

Moje bieżące zapytania przyjmują identyfikator odtwarzacza jako parametr i zwracają 1 wartość:

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

Aby połączyć powyższe zapytania, prawdopodobnie potrzebuję takiej procedury:

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;

Pomóż mi poprawnie zadeklarować powyższą procedurę.

Alexander Farber
źródło

Odpowiedzi:

13

Używanie OUTparametrów pozwala osiągnąć w zasadzie to samo, co w odpowiedzi @ klin, ale bez tworzenia typów zdefiniowanych przez użytkownika. Po prostu przenieś wszystkie zmienne z bloku deklaracji na listę argumentów jako OUTparametry:

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;

Zwróci to rekord (dokładnie jeden), dzięki czemu możesz wybrać jego wartości jako normalny rekord:

-- 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
źródło
+1 to działa świetnie, dzięki. Tylko jedno małe pytanie: obecnie mam albo NULLczy TRUEw moim is_bannedzmiennej z tym stwierdzeniem select true into is_banned from pref_ban where id=_id. Czy istnieje sposób, aby to zmienić na FALSElub TRUE?
Alexander Farber,
1
Tak, is_banned := exists(select 1 from pref_ban where id=_id)powinno działać, ale to inne pytanie.
pozs
6

Powinieneś zdefiniować typ złożony. Można go użyć jako typu zwracanego funkcji i do rejestrowania zmiennych wewnątrz funkcji.

Przykład:

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

Moim zdaniem używanie takich funkcji jest dość rozsądne zarówno pod względem wydajności, jak i logiki aplikacji.


Zdefiniowane przez użytkownika typy złożone są bardzo przydatne, jeśli chcesz zwrócić zestaw wierszy z funkcji. Następnie powinieneś zdefiniować typ zwracanej funkcji jako setof composite-typei użyj return nextlubreturn query.

Przykład:

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
źródło
1
Używając OUTparametrów osiągnij w zasadzie to samo, ale bez tworzenia typów zdefiniowanych przez użytkownika: postgresql.org/docs/current/static/…
poz
@pozs +1 dzięki, chciałbym użyć OUTparametrów - ale jak SELECTw przypadku 4 niepowiązanych zapytań?
Alexander Farber,
@klin +1 dzięki, próbowałem twojej sugestii i działa. Użyłem do tworzenia niestandardowego typu, drop type if exists user_type cascade; create type user_type as(...);ponieważ mój skrypt Perla wywołuje instrukcje SQL za każdym razem podczas uruchamiania.
Alexander Farber,
1
Nie powinieneś tego robić. Funkcje w Postgres są procedurami przechowywanymi . Po utworzeniu są gotowe do użycia w dowolnej sesji. To samo dotyczy typów zdefiniowanych przez użytkownika. Musisz usunąć typ złożony tylko wtedy, gdy zamierzasz go zmienić (lub w ogóle go usunąć).
klin
+1 dla „wybierz * z mojej_funkcji ()”. Robiłem „select my_function ()” i miałem problemy.
Ilonpilaaja