Поверніть запис із функцією PL / pgSQL - для прискорення запиту


10

У мене в Perl написаний демон , що не розгортається, який використовує acync запити для запису статистики гравців у базу даних PostgreSQL 9.3. Але коли мені потрібно прочитати щось із бази даних (наприклад, якщо гравця заборонено або якщо гравцю статус VIP), я використовую синхронні запити.

Це змушує гру зупинитися на короткий момент, поки значення не буде прочитане з бази даних.

Я не можу переписати свій ігровий демон, щоб використовувати запити асинхронізації для читання значень (я намагався, але це вимагало занадто багато змін), тому моє запитання : чи було б сенс поєднувати кілька непов'язаних запитів (що мені потрібно робити, коли новий гравець підключається) до 1 процедури і як я можу повернути кілька значень одночасно до моєї програми Perl?

Усі мої поточні запити приймають ідентифікатор гравця як параметр і повертають 1 значення:

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

Для поєднання вищезазначених запитів мені, мабуть, потрібна така процедура, як ця:

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;

Будь ласка, допоможіть мені правильно оголосити вищевказану процедуру.

Відповіді:


13

Використовуючи OUTпараметри, досягайте в основному того ж, що і у відповіді @ klin, але без створення визначених користувачем типів. Просто перемістіть усі змінні з блоку оголошення в список аргументів як OUTпараметри:

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;

Це поверне запис (рівно один), тож ви можете вибрати його значення як звичайний запис:

-- 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 це чудово, дякую. Тільки одне маленьке питання: в даний час у мене є або NULLчи TRUEв моїй is_bannedзмінної з цим твердженням: select true into is_banned from pref_ban where id=_id. Чи є спосіб , щоб змінити його FALSEабо TRUE?
Олександр Фарбер

1
Так, is_banned := exists(select 1 from pref_ban where id=_id)має працювати, але це вже інше питання.
поз

6

Вам слід визначити складений тип. Ви можете використовувати його як тип повернення функції та для запису змінних всередині функції.

Приклад:

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

На мою думку, використання таких функцій є цілком розумним з точки зору як продуктивності, так і логіки додатків.


Визначені користувачем складові типи дуже корисні, якщо ви хочете повернути набір рядків зі своєї функції. Потім слід визначити тип повернення функції як setof composite-typeі використовувати return nextабоreturn query.

Приклад:

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
Використовуючи OUTпараметри, досягайте в основному того ж, але без створення визначених користувачем типів: postgresql.org/docs/current/static/…
pozs

@pozs +1 спасибі, я хотів би використати OUTпараметри - але як SELECTїх у моєму випадку 4 непов'язаних запитів?
Олександр Фарбер

@klin +1 спасибі, я спробував вашу пропозицію, і вона працює. Для створення власного типу я використовував, drop type if exists user_type cascade; create type user_type as(...);оскільки мій скрипт Perl викликає оператори SQL кожного разу під час запуску.
Олександр Фарбер

1
Ти не повинен цього робити. Функції Postgres - це збережені процедури. Після створення готові до використання в будь-якому сеансі. Те саме стосується визначених користувачем типів. Ви повинні скинути складений тип лише в тому випадку, якщо ви збираєтесь його змінити (або взагалі видалити).
клин

+1 для "select * from my_function ()". Я робив "select my_function ()" і мав проблеми.
Ілонпілая
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.