PL/pgSQL — asoslar, o'zgaruvchilar, shartlar, sikllar

~25 daq · PostgreSQL · Funksiyalar va Protseduralar

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