ส่งคืนระเบียนด้วยฟังก์ชัน PL / pgSQL - เพื่อเพิ่มความเร็วในการสืบค้น


10

ฉันมีดีมอนที่ไม่ใช่ forking เขียนใน Perlซึ่งใช้การสอบถาม acync เพื่อเขียนสถิติผู้เล่นลงในฐานข้อมูล PostgreSQL 9.3 แต่เมื่อฉันต้องการอ่านบางสิ่งจากฐานข้อมูล (เช่นถ้าผู้เล่นถูกแบนหรือหากผู้เล่นมีสถานะวีไอพี) จากนั้นฉันจะใช้แบบสอบถามแบบซิงโครนัส

สิ่งนี้ทำให้เกมหยุดชั่วขณะหนึ่งจนกว่าจะอ่านค่าจากฐานข้อมูล

ฉันไม่สามารถเขียน daemon เกมของฉันใหม่เพื่อใช้การสืบค้นแบบ async สำหรับการอ่านค่า (ฉันพยายาม แต่มันต้องมีการเปลี่ยนแปลงมากเกินไป) ดังนั้นคำถามของฉันคือ : มันจะเป็นการรวมเข้ากับแบบสอบถามที่ไม่เกี่ยวข้องหลายรายการหรือไม่ เชื่อมต่อ) ถึง 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ไม่?
Alexander Farber

1
ใช่is_banned := exists(select 1 from pref_ban where id=_id)ควรใช้งานได้ แต่นั่นเป็นคำถามที่แตกต่าง
pozs

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 +1 ขอบคุณฉันต้องการใช้OUTพารามิเตอร์ - แต่SELECTพวกเขาในกรณีของฉันสำหรับ 4 แบบสอบถามที่ไม่เกี่ยวข้อง?
Alexander Farber

@klin +1 ขอบคุณฉันลองทำตามคำแนะนำของคุณแล้ว สำหรับการสร้างประเภทที่กำหนดเองที่ฉันใช้drop type if exists user_type cascade; create type user_type as(...);เพราะสคริปต์ Perl ของฉันเรียกคำสั่ง SQL ทุกครั้งที่เริ่มต้น
Alexander Farber

1
คุณไม่ควรทำอย่างนั้น ฟังก์ชั่นใน Postgres เป็นขั้นตอนการจัดเก็บ เมื่อสร้างแล้วพร้อมที่จะใช้ในเซสชันใด ๆ ข้อกังวลที่ผู้ใช้กำหนดประเภทเดียวกัน คุณต้องวางประเภทคอมโพสิตเฉพาะในกรณีที่คุณต้องการเปลี่ยน (หรือลบออกทั้งหมด)
klin

+1 สำหรับ "select * จาก my_function ()" ฉันกำลังทำ "select my_function ()" และมีปัญหา
Ilonpilaaja
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.