18-bo‘lim
PL/pgSQL funksiyalari va triggerlar
SQL va PL/pgSQL funksiyalari, dollar qavs, shart va sikl, RETURNS TABLE, trigger yozish, NEW va OLD hamda audit jadvali.
Ushbu bo‘lim mundarijasi
Ba'zi mantiqni bazaning ichida saqlash qulay: u har bir dasturga birdek qo'llanadi va ma'lumotga eng yaqin joyda ishlaydi.
CREATE TABLE xodim (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ism text NOT NULL,
lavozim text NOT NULL,
maosh numeric(10,2) NOT NULL CHECK (maosh > 0)
);
INSERT INTO xodim (ism, lavozim, maosh) VALUES
('Husanboy', 'dasturchi', 9000000.00),
('Malika', 'tahlilchi', 7500000.00),
('Nodira', 'dasturchi', 10500000.00),
('Aziza', 'menejer', 6800000.00);
Eng oddiy funksiya #
Agar funksiya bitta SELECT dan iborat bo'lsa, LANGUAGE
sql yetarli:
CREATE FUNCTION yillik_maosh(oylik numeric)
RETURNS numeric
LANGUAGE sql
IMMUTABLE
AS $$
SELECT oylik * 12;
$$;
SELECT ism, maosh, yillik_maosh(maosh) AS yillik
FROM xodim
ORDER BY id;
CREATE FUNCTION
ism | maosh | yillik
----------+-------------+--------------
Husanboy | 9000000.00 | 108000000.00
Malika | 7500000.00 | 90000000.00
Nodira | 10500000.00 | 126000000.00
Aziza | 6800000.00 | 81600000.00
(4 rows)
$$ nimaFunksiya tanasi baza uchun matn. Uni oddiy qo'shtirnoq bilan ham yozish mumkin edi, lekin u holda tana ichidagi har bir qo'shtirnoqni ikkilantirish kerak bo'lar edi:
AS 'SELECT ''salom'';'
Dollar qavs bu muammoni yo'qotadi - ichida hech narsani qochirish shart emas.
Qavs orasiga belgi qo'yish ham mumkin: $tana$ ... $tana$.
Bu tana ichida yana $$ uchrasa qo'l keladi - masalan
funksiya ichida boshqa funksiya yaratilayotganda.
Funksiya turg'unligi #
IMMUTABLE so'zi tasodifiy emas. U bazaga funksiya qanday
tutishini aytadi:
| Belgi | Ma'nosi | Misol |
|---|---|---|
IMMUTABLE | Bir xil kirish - doim bir xil natija | oylik * 12 |
STABLE | Bitta so'rov ichida o'zgarmaydi | Jadvaldan o'qish |
VOLATILE | Har safar o'zgarishi mumkin | random(), now() |
Odatiy qiymat - VOLATILE, ya'ni eng ehtiyotkori. To'g'ri
belgi qo'yilsa, baza funksiyani indeksda ishlatishi va
natijasini qayta ishlatishi mumkin bo'ladi.
PL/pgSQL - mantiq bor funksiya #
Shart, sikl yoki o'zgaruvchi kerak bo'lsa - plpgsql:
CREATE FUNCTION maosh_darajasi(qiymat numeric)
RETURNS text
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
IF qiymat >= 10000000 THEN
RETURN 'yuqori';
ELSIF qiymat >= 7000000 THEN
RETURN 'orta';
ELSE
RETURN 'boshlangich';
END IF;
END;
$$;
SELECT ism, maosh, maosh_darajasi(maosh) AS daraja
FROM xodim
ORDER BY maosh DESC;
CREATE FUNCTION
ism | maosh | daraja
----------+-------------+-------------
Nodira | 10500000.00 | yuqori
Husanboy | 9000000.00 | orta
Malika | 7500000.00 | orta
Aziza | 6800000.00 | boshlangich
(4 rows)
O'zgaruvchi va sikl #
CREATE FUNCTION lavozim_royxati()
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
natija text := '';
qator record;
BEGIN
FOR qator IN
SELECT lavozim, count(*) AS soni
FROM xodim GROUP BY lavozim ORDER BY lavozim
LOOP
natija := natija || qator.lavozim || '=' || qator.soni || ' ';
END LOOP;
RETURN trim(natija);
END;
$$;
SELECT lavozim_royxati();
CREATE FUNCTION
lavozim_royxati
-----------------------------------
dasturchi=2 menejer=1 tahlilchi=1
(1 row)
DECLARE bo'limida o'zgaruvchilar e'lon qilinadi,
:= esa qiymat berish belgisi.
record turi qulay: u qaysi ustunlar kelishini oldindan
bilishni talab qilmaydi.
Jadval qaytaruvchi funksiya #
CREATE FUNCTION maoshi_kop(chegara numeric)
RETURNS TABLE (ism text, lavozim text, maosh numeric)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT x.ism, x.lavozim, x.maosh
FROM xodim x
WHERE x.maosh > chegara
ORDER BY x.maosh DESC;
END;
$$;
SELECT * FROM maoshi_kop(7000000);
CREATE FUNCTION
ism | lavozim | maosh
----------+-----------+-------------
Nodira | dasturchi | 10500000.00
Husanboy | dasturchi | 9000000.00
Malika | tahlilchi | 7500000.00
(3 rows)
RETURNS TABLE (ism text, ...) yozganda ism o'zgaruvchi
bo'lib qoladi. Jadvalda ham ism ustuni bor.
Endi WHERE ism = 'Malika' deb yozsangiz, baza qaysi ism
ni nazarda tutayotganingizni bilmaydi:
ERROR: column reference "ism" is ambiguous
DETAIL: It could refer to either a PL/pgSQL variable
or a table column.
Uchta yechim bor va birinchisi eng yaxshisi:
| Yechim | Yozuv |
|---|---|
| Jadvalga taxallus bering | FROM xodim x ... x.ism |
| Parametrga boshqa nom | p_ism, _ism |
| Funksiyaga taxallus | #variable_conflict use_column |
Yuqoridagi maoshi_kop misolida aynan birinchi yo'l
ishlatilgan - shuning uchun har joyda x. prefiksi bor.
RETURN QUERY esa SELECT natijasini to'g'ridan-to'g'ri
funksiya natijasiga uzatadi.
Trigger #
Trigger - jadvalga INSERT, UPDATE yoki DELETE
bo'lganda avtomatik ishga tushadigan funksiya.
Avtomatik yangilangan_sana #
Eng ko'p ishlatiladigan trigger - vaqt belgisini yangilash:
ALTER TABLE xodim
ADD COLUMN yangilangan timestamptz NOT NULL DEFAULT now();
CREATE FUNCTION vaqtni_yangila()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW.yangilangan := now();
RETURN NEW;
END;
$$;
CREATE TRIGGER xodim_vaqt
BEFORE UPDATE ON xodim
FOR EACH ROW
EXECUTE FUNCTION vaqtni_yangila();
UPDATE xodim SET maosh = maosh + 500000 WHERE ism = 'Malika';
SELECT ism, maosh, yangilangan > now() - interval '1 minute' AS hozir
FROM xodim
WHERE ism IN ('Malika', 'Aziza')
ORDER BY ism;
ALTER TABLE
CREATE FUNCTION
CREATE TRIGGER
UPDATE 1
ism | maosh | hozir
--------+------------+-------
Aziza | 6800000.00 | t
Malika | 8000000.00 | t
(2 rows)
Diqqat: yangilangan ustunining aniq qiymati o'rniga
shart tanlandi. Sabab oddiy - aniq vaqt har safar boshqa
bo'ladi.
Audit jadvali #
AFTER trigger o'zgarishlarni yozib borish uchun ideal:
CREATE TABLE maosh_tarixi (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
xodim_id int NOT NULL,
eski numeric(10,2),
yangi numeric(10,2),
kim text NOT NULL
);
CREATE FUNCTION maoshni_yoz()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.maosh IS DISTINCT FROM OLD.maosh THEN
INSERT INTO maosh_tarixi (xodim_id, eski, yangi, kim)
VALUES (OLD.id, OLD.maosh, NEW.maosh, current_user);
END IF;
RETURN NULL;
END;
$$;
CREATE TRIGGER xodim_maosh_audit
AFTER UPDATE ON xodim
FOR EACH ROW
EXECUTE FUNCTION maoshni_yoz();
UPDATE xodim SET maosh = 11000000 WHERE ism = 'Nodira';
UPDATE xodim SET lavozim = 'katta menejer' WHERE ism = 'Aziza';
SELECT xodim_id, eski, yangi FROM maosh_tarixi ORDER BY id;
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
UPDATE 1
UPDATE 1
xodim_id | eski | yangi
----------+-------------+-------------
3 | 10500000.00 | 11000000.00
(1 row)
Ikkita UPDATE bo'ldi, lekin tarixga faqat bittasi
tushdi - ikkinchisida maosh o'zgarmagan.
IS DISTINCT FROM ni yodda tutingNEW.maosh <> OLD.maosh yozuvi ishlamaydi, agar qiymatlardan
biri NULL bo'lsa.
| Yozuv | NULL va 500 | NULL va NULL |
|---|---|---|
<> | NULL (shart bajarilmaydi) | NULL |
IS DISTINCT FROM | true | false |
Triggerlarda deyarli har doim IS DISTINCT FROM kerak -
aks holda NULL dan songa o'zgarish sezilmay qoladi.
AFTER triggerda RETURN NULL yozish ham odat: qaytgan
qiymat baribir e'tiborga olinmaydi, chunki qator allaqachon
yozilgan.
Qachon trigger ishlatmaslik kerak #
Trigger kuchli, lekin u ko'rinmas ishlaydi - shuning uchun xatoni topish qiyinlashadi.
| Trigger to'g'ri | Trigger noto'g'ri |
|---|---|
yangilangan vaqtini qo'yish | Murakkab biznes mantiq |
| Audit yozuvi | Tashqi tizimga xabar yuborish |
| Hosilaviy ustunni to'ldirish | Bir necha jadvalga zanjir yozuv |
| Oddiy tekshiruv | Uzoq davom etadigan hisob |
Asosiy xavf - zanjir: A jadvalidagi trigger B ga yozadi,
B dagi trigger C ga yozadi. Bir kun kelib bitta UPDATE
nima uchun 3 soniya ishlayotganini hech kim tushunmay qoladi.
LANGUAGE sqlbilan oddiy funksiya yozing.- Unga
IMMUTABLEbelgisini qo'ying va ma'nosini yozing. plpgsqldaIFli funksiya yozing.DECLAREvaFOR ... LOOPishlatgan funksiya tuzing.RETURNS TABLEbilan jadval qaytaring.- Ustun va o'zgaruvchi nomini ataylab bir xil qiling - xatoni ko'ring.
- Uni jadval taxallusi bilan to'g'rilang.
BEFORE UPDATEtrigger yozibNEWni o'zgartiring.AFTER UPDATEtrigger bilan audit jadvalini to'ldiring.<>o'rnigaIS DISTINCT FROMnega kerakligini tushuntiring.
Xulosa #
- Bitta
SELECTuchunLANGUAGE sql, mantiq uchunLANGUAGE plpgsql. $$dollar qavsi tana ichidagi qo'shtirnoqlarni qochirishdan qutqaradi.IMMUTABLE,STABLE,VOLATILE- bazaga funksiya tutimini aytadi.DECLAREda o'zgaruvchi e'lon qilinadi,:=qiymat beradi.RETURNS TABLEfunksiyani jadval kabi ishlatish imkonini beradi.- Ustun va o'zgaruvchi bir xil nomlansa - noaniqlik xatosi; taxallus bilan hal qiling.
- Triggerda
NEWyangi,OLDeski qator qiymati. BEFORENEWni o'zgartira oladi,AFTEResa faqat yon ish uchun.IS DISTINCT FROMNULLni ham to'g'ri solishtiradi - triggerda shart.- Trigger ko'rinmas ishlaydi: uni oddiy vazifalar uchun saqlang, biznes mantiq uchun emas.
Keyingi bo'limda katta jadvallarni bo'laklash, VACUUM va
zaxira nusxani ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.