Kembalikan catatan dengan fungsi PL / pgSQL - untuk mempercepat permintaan

10

Saya memiliki daemon permainan non-forking yang ditulis dalam Perl , yang menggunakan kueri acync untuk menulis statistik pemain ke dalam database PostgreSQL 9.3. Tetapi ketika saya perlu membaca sesuatu dari basis data (seperti jika pemain dilarang atau jika pemain memiliki status VIP), maka saya menggunakan kueri sinkron.

Ini membuat game berhenti sejenak, hingga nilainya dibaca dari database.

Saya tidak bisa menulis ulang daemon gim saya untuk menggunakan kueri async untuk membaca nilai (saya sudah mencoba, tetapi itu membutuhkan terlalu banyak perubahan), jadi pertanyaan saya adalah : apakah masuk akal untuk menggabungkan beberapa kueri yang tidak terkait (yang perlu saya buat ketika pemain baru menghubungkan) ke 1 prosedur dan bagaimana saya bisa mengembalikan beberapa nilai pada saat yang sama ke program Perl saya?

Kueri saya saat ini semua menggunakan ID pemain sebagai parameter dan mengembalikan nilai 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=?

Untuk menggabungkan pertanyaan di atas, saya mungkin perlu prosedur seperti ini:

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;

Tolong bantu saya untuk mendeklarasikan prosedur di atas dengan benar.

Alexander Farber
sumber

Jawaban:

13

Menggunakan OUTparameter pada dasarnya mencapai hal yang sama seperti pada jawaban @ klin, tetapi tanpa membuat tipe yang ditentukan pengguna. Pindahkan semua variabel Anda dari blok menyatakan ke daftar argumen sebagai OUTparameter:

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;

Ini akan mengembalikan catatan (tepat satu), sehingga Anda dapat memilih nilainya sebagai rekaman 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
sumber
+1 ini bekerja dengan baik, terima kasih. Hanya satu pertanyaan kecil: saya miliki saat ini baik NULLatau TRUEdi saya is_bannedvariabel dengan pernyataan ini: select true into is_banned from pref_ban where id=_id. Apakah ada cara untuk mengubahnya ke FALSEatau TRUE?
Alexander Farber
1
Ya, is_banned := exists(select 1 from pref_ban where id=_id)harus bekerja, tapi itu pertanyaan yang berbeda.
pozs
6

Anda harus mendefinisikan tipe komposit. Anda bisa menggunakannya sebagai tipe pengembalian fungsi dan untuk merekam variabel di dalam suatu fungsi.

Contoh:

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

Menurut pendapat saya menggunakan fungsi seperti ini cukup masuk akal baik dari segi kinerja dan logika aplikasi.


Jenis komposit yang ditentukan pengguna sangat berguna jika Anda ingin mengembalikan rangkaian baris dari fungsi Anda. Maka Anda harus mendefinisikan kembali jenis fungsi sebagai setof composite-typedan menggunakan return nextataureturn query.

Contoh:

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
sumber
1
Menggunakan OUTparameter pada dasarnya mencapai hal yang sama, tetapi tanpa membuat tipe yang ditentukan pengguna: postgresql.org/docs/current/static/…
pozs
@pozs +1 terima kasih, saya ingin menggunakan OUTparameter - tetapi bagaimana SELECTmereka dalam kasus saya dari 4 kueri yang tidak terkait?
Alexander Farber
@klin +1 terima kasih, saya telah mencoba saran Anda dan berhasil. Untuk membuat jenis kustom saya, saya telah menggunakan drop type if exists user_type cascade; create type user_type as(...);karena skrip Perl saya memanggil pernyataan SQL setiap kali saat start up.
Alexander Farber
1
Anda seharusnya tidak melakukan itu. Fungsi dalam Postgres adalah prosedur tersimpan . Setelah dibuat siap digunakan dalam sesi apa pun. Hal yang sama menyangkut jenis yang ditentukan pengguna. Anda harus membuang tipe komposit hanya jika Anda akan mengubahnya (atau menghapusnya sama sekali).
klin
+1 untuk "select * from my_function ()". Saya sedang melakukan "select my_function ()" dan mengalami masalah.
Ilonpilaaja