PL/pgSQL — asoslar, o'zgaruvchilar, shartlar, sikllar
Muammodan boshlaylik
Oddiy SQL — deklarativ til. Siz "nima kerak" deb yozasiz, bazaning o'zi "qanday olishni" hal qiladi. Lekin ba'zan bu yetarli emas.
Masalan: foydalanuvchi ro'yxatdan o'tmoqda. Siz quyidagilarni qilishingiz kerak — email allaqachon borligini tekshirish, yo'q bo'lsa foydalanuvchi yaratish, profilni yaratish, xush kelibsiz xabari yuborish uchun navbatga qo'shish. Agar biror qadam muvaffaqiyatsiz bo'lsa — hamma narsani bekor qilish.
Bu to'rt qadam — biri ikkinchisiga bog'liq, shartlar bor, xatolarni ushlash kerak. Oddiy SQL bilan bir so'rovda hal qilib bo'lmaydi.
Shu sababli PostgreSQL ichida to'liq dasturlash tili bor — PL/pgSQL. U SQL bilan birga o'zgaruvchilar, shartlar, sikllar, xatolarni ushlash imkonini beradi.
PL/pgSQL nima?
PL/pgSQL (Procedural Language / PostgreSQL) — PostgreSQL ning protsedural dasturlash tili. JavaScript yoki Python kabi — o'zgaruvchi, shart, sikl yozish mumkin. Lekin u to'g'ridan-to'g'ri bazaning ichida ishlaydi — ma'lumotga tarmoq orqali borish shart emas.
Hayotiy o'xshatma: oshxonada bosh oshpaz va oddiy oshpaz. Oddiy SQL — mehmonxona manager: "menga 3 ta plov keltir" deydi. PL/pgSQL — bosh oshpaz: piyozni qovuradi, go'shtni qo'shadi, guruchni soladi, suv qo'shadi, vaqtni kuzatadi, tayyor bo'lganda chiqaradi. Bir joyda, to'liq nazorat ostida.
Oddiy SQL: PL/pgSQL:
────────────────────────── ──────────────────────────────
SELECT, INSERT, UPDATE + o'zgaruvchilar
Bitta so'rov + IF/ELSIF/ELSE
Shartlar yo'q + LOOP, FOR, WHILE
Xatolarni ushlab bo'lmaydi + EXCEPTION (xatolarni ushlash)
+ ko'p qadam bir blokda
Asosiy tuzilma — DO bloki
PL/pgSQL kodni DO bloki bilan sinab ko'rish mumkin. Funksiya yaratmasdan, to'g'ridan-to'g'ri kodni bajarish:
DO $
DECLARE
-- o'zgaruvchilar shu yerda e'lon qilinadi
BEGIN
-- asosiy kod shu yerda
END;
$ LANGUAGE plpgsql;
$ — dollar quoting. Matn ichida apostrof ishlatish muammosini hal qiladi.
DECLARE — ixtiyoriy. O'zgaruvchi kerak bo'lmasa yozilmaydi.
BEGIN ... END — kod bloki.
O'zgaruvchilar
O'zgaruvchilar DECLARE qismida e'lon qilinadi, BEGIN da ishlatiladi.
DO $
DECLARE
ism TEXT := 'Ali'; -- e'lon + qiymat
yosh INT := 25;
togri BOOLEAN; -- faqat e'lon, qiymat yo'q (NULL)
narx NUMERIC(10, 2) := 0.0;
BEGIN
RAISE NOTICE 'Ism: %, Yosh: %', ism, yosh;
-- RAISE NOTICE — debug uchun xabar chiqarish
END;
$ LANGUAGE plpgsql;
RAISE NOTICE — konsolga xabar chiqaradi. % — qiymat joylanadi.
So'rovdan o'zgaruvchiga qiymat olish — INTO
DO $
DECLARE
foydalanuvchilar_soni INT;
BEGIN
SELECT COUNT(*) INTO foydalanuvchilar_soni
FROM foydalanuvchilar;
RAISE NOTICE 'Jami foydalanuvchilar: %', foydalanuvchilar_soni;
END;
$ LANGUAGE plpgsql;
SELECT ... INTO o'zgaruvchi — so'rov natijasini o'zgaruvchiga yozadi.
%TYPE — ustun turini olish
O'zgaruvchi turini jadval ustunidan avtomatik olish. Ustun turi o'zgarse, o'zgaruvchi ham o'zgaradi — moslik kafolati:
DECLARE
f_ism foydalanuvchilar.ism%TYPE; -- ism ustunining turi nima bo'lsa, shu
f_id foydalanuvchilar.id%TYPE;
%ROWTYPE — butun qatorni olish
DECLARE
foydalanuvchi foydalanuvchilar%ROWTYPE;
BEGIN
SELECT * INTO foydalanuvchi FROM foydalanuvchilar WHERE id = 1;
RAISE NOTICE 'Ism: %', foydalanuvchi.ism;
END;
Shartlar — IF / ELSIF / ELSE
Oddiy if/else — sintaksis farqli, mantiq bir xil:
DO $
DECLARE
ball INT := 75;
BEGIN
IF ball >= 90 THEN
RAISE NOTICE 'A';
ELSIF ball >= 75 THEN
RAISE NOTICE 'B';
ELSIF ball >= 60 THEN
RAISE NOTICE 'C';
ELSE
RAISE NOTICE 'D';
END IF;
END;
$ LANGUAGE plpgsql;
-- NOTICE: B
ELSIF — SQL da ELSEIF emas, ELSIF. END IF — shart blokini yopadi.
Amaliy misol — chegirma hisoblash
DO $
DECLARE
narx NUMERIC := 1500000;
chegirma NUMERIC;
yakuniy NUMERIC;
BEGIN
IF narx > 5000000 THEN
chegirma := 0.20; -- 20%
ELSIF narx > 2000000 THEN
chegirma := 0.10; -- 10%
ELSIF narx > 1000000 THEN
chegirma := 0.05; -- 5%
ELSE
chegirma := 0;
END IF;
yakuniy := narx * (1 - chegirma);
RAISE NOTICE 'Narx: %, Chegirma: %, Yakuniy: %', narx, chegirma, yakuniy;
END;
$ LANGUAGE plpgsql;
-- NOTICE: Narx: 1500000, Chegirma: 0.05, Yakuniy: 1425000
Sikllar
LOOP — cheksiz sikl
Asosiy sikl. EXIT bilan to'xtatiladi:
DO $
DECLARE
i INT := 1;
BEGIN
LOOP
RAISE NOTICE 'Qadam: %', i;
i := i + 1;
EXIT WHEN i > 5; -- shart bajarilganda chiqish
END LOOP;
END;
$ LANGUAGE plpgsql;
WHILE — shart bilan sikl
Shart rost bo'lguncha davom etadi:
DO $
DECLARE
i INT := 1;
BEGIN
WHILE i <= 5 LOOP
RAISE NOTICE 'Raqam: %', i;
i := i + 1;
END LOOP;
END;
$ LANGUAGE plpgsql;
FOR — eng qulay sikl
Raqamlar bo'yicha:
DO $
BEGIN
FOR i IN 1..5 LOOP
RAISE NOTICE 'i = %', i;
END LOOP;
-- Teskari tartibda
FOR i IN REVERSE 5..1 LOOP
RAISE NOTICE 'teskari: %', i;
END LOOP;
END;
$ LANGUAGE plpgsql;
So'rov natijalari bo'yicha — bu eng ko'p ishlatiladigani:
DO $
DECLARE
qator foydalanuvchilar%ROWTYPE;
BEGIN
FOR qator IN SELECT * FROM foydalanuvchilar WHERE aktiv = true LOOP
RAISE NOTICE 'Foydalanuvchi: % — %', qator.id, qator.ism;
-- har bir foydalanuvchi uchun nimadir qilish
END LOOP;
END;
$ LANGUAGE plpgsql;
CONTINUE — keyingi iteratsiyaga o'tish
DO $
BEGIN
FOR i IN 1..10 LOOP
CONTINUE WHEN MOD(i, 2) = 0; -- juft raqamlarni o'tkazib yuborish
RAISE NOTICE 'Toq raqam: %', i;
END LOOP;
END;
$ LANGUAGE plpgsql;
-- 1, 3, 5, 7, 9
Xatolarni ushlash — EXCEPTION
Bu PL/pgSQL ning eng kuchli tomonlaridan biri. BEGIN ... EXCEPTION ... END — xatoni ushlab, munosib javob berish:
DO $
DECLARE
natija NUMERIC;
BEGIN
natija := 100 / 0; -- nolga bo'lish xatosi
RAISE NOTICE 'Natija: %', natija;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Nolga bo''lish mumkin emas!';
WHEN OTHERS THEN
RAISE NOTICE 'Kutilmagan xato: %', SQLERRM;
-- SQLERRM — xato xabari
END;
$ LANGUAGE plpgsql;
-- NOTICE: Nolga bo'lish mumkin emas!
WHEN OTHERS — ushlanmagan barcha xatolar. SQLERRM — xato matni, SQLSTATE — xato kodi.
Amaliy misol — xavfsiz qo'shish
Foydalanuvchini qo'shish: email allaqachon mavjud bo'lsa — xato bermasdan, mavjudligini bildirish:
DO $
DECLARE
yangi_id INT;
BEGIN
INSERT INTO foydalanuvchilar (ism, email)
VALUES ('Ali', 'ali@mail.com')
RETURNING id INTO yangi_id;
RAISE NOTICE 'Yangi foydalanuvchi yaratildi, ID: %', yangi_id;
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE 'Bu email allaqachon ro''yxatdan o''tgan: ali@mail.com';
END;
$ LANGUAGE plpgsql;
unique_violation — UNIQUE constraint buzilganda. RETURNING id INTO yangi_id — qo'shilgan qatorning ID sini o'zgaruvchiga olish.
Tez-tez uchraydigan xatolar
:= o'rniga = ishlatish:
-- ❌ — SQL dagi tenglik, PL/pgSQL da tayinlash emas
i = i + 1;
-- ✅ — PL/pgSQL da tayinlash := bilan
i := i + 1;
PL/pgSQL da = — solishtirish operatori, := — tayinlash. Ko'pchilik boshida adashadi.
SELECT INTO da qator topilmaganida:
-- ❌ — foydalanuvchi topilmasa, o'zgaruvchi NULL bo'ladi, lekin xato bermaydi
SELECT * INTO foydalanuvchi FROM foydalanuvchilar WHERE id = 999;
-- foydalanuvchi.ism = NULL — bu sezilmaydi
-- ✅ — topilmaganini tekshirish
SELECT * INTO foydalanuvchi FROM foydalanuvchilar WHERE id = 999;
IF NOT FOUND THEN
RAISE EXCEPTION 'Foydalanuvchi topilmadi: %', 999;
END IF;
FOUND — maxsus o'zgaruvchi: oxirgi SELECT INTO qator topsa TRUE, topmasas FALSE.
Xulosa
PL/pgSQL — SQL ni dasturlash tiliga aylantiradi. Oddiy SQL yetarli bo'lmagan murakkab mantiq uchun.
DECLARE → o'zgaruvchilar e'loni
BEGIN/END → kod bloki
:= → tayinlash (= emas!)
INTO → so'rov natijasini o'zgaruvchiga
IF/ELSIF → shartlar
LOOP/WHILE/FOR → sikllar
EXCEPTION → xatolarni ushlash
FOUND → so'rov qator topdi/topmadilarni tekshirish
RAISE NOTICE → debug xabari
Keyingi darsda bu bilimlarni funksiya ichiga joylashtiramiz — qayta ishlatib bo'ladigan, parametrlar qabul qiladigan funksiyalar.
Keyingi dars
32-dars: Function yaratish — RETURNS, LANGUAGE, SECURITY DEFINER