Stored Procedure — Function dan farqi, CALL

~20 daq · PostgreSQL · Funksiyalar va Protseduralar

Muammodan boshlaylik

Hisobdan hisobga pul o'tkazish kerak. Ikkita operatsiya: biridan yeching, ikkinchisiga qo'shing. Agar birontasi muvaffaqiyatsiz bo'lsa — ikkalasi ham bekor bo'lishi kerak.

Funksiya bilan yozsangiz — muammo chiqadi. Funksiya ichida COMMIT va ROLLBACK yozib bo'lmaydi. Funksiya bitta tranzaksiya ichida ishlaydi, uni o'zi boshqara olmaydi.

Aynan shu holat uchun Stored Procedure yaratilgan: u tranzaksiyani o'zi boshqaradi — ichida COMMIT va ROLLBACK yozish mumkin.


Function va Procedure — asosiy farq

Ikkisi juda o'xshash — lekin bir muhim farqi bor.

Function:                         Procedure:
──────────────────────────────    ──────────────────────────────
Qiymat qaytaradi                  Qiymat qaytarmaydi
SELECT bilan chaqiriladi          CALL bilan chaqiriladi
Tranzaksiyani boshqara olmaydi    COMMIT / ROLLBACK yoza oladi
SQL so'rov ichida ishlatiladi     Mustaqil ishlaydi

Hayotiy o'xshatma: funksiya — kalkulyator. Siz raqam berasiz, u hisoblaydi, natija qaytaradi. Procedure — muhandis. U ish qiladi, moslamalarni sozlaydi, jarayonni boshqaradi — lekin qo'lida raqam ushlayotgani yo'q.

SELECT chegirma_hisobla(1000);   ← function, natija qaytaradi
CALL pul_otkazish(1, 2, 50000);  ← procedure, ish qiladi

Procedure yaratish

CREATE OR REPLACE PROCEDURE pul_otkazish(
  p_kimdan  INT,
  p_kimga   INT,
  p_summa   NUMERIC
)
LANGUAGE plpgsql
AS $
BEGIN
  -- 1. Balansni tekshirish
  IF (SELECT balans FROM hisoblar WHERE id = p_kimdan) < p_summa THEN
    RAISE EXCEPTION 'Balans yetarli emas';
  END IF;

  -- 2. Pul yechish
  UPDATE hisoblar
  SET balans = balans - p_summa
  WHERE id = p_kimdan;

  -- 3. Pul qo'shish
  UPDATE hisoblar
  SET balans = balans + p_summa
  WHERE id = p_kimga;

  -- 4. Ikkalasi bajarildi — tasdiqlash
  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    RAISE;   -- xatoni yuqoriga uzatish
END;
$;

Chaqirish:

CALL pul_otkazish(1, 2, 50000);

COMMIT va ROLLBACK — procedure ning kuchi

Function ichida tranzaksiya boshqaruvi yo'q — u tashqaridan boshlangantranzaksiyada ishlaydi. Procedure esa o'zi boshqaradi.

Bu qachon kerak? Bir procedure ichida bir nechta mustaqil tranzaksiya kerak bo'lganda. Masalan: mingta foydalanuvchiga xabar yuborish. Biri muvaffaqiyatsiz bo'lsa — qolganlarini bekor qilmaslik kerak.

CREATE OR REPLACE PROCEDURE ommaviy_xabar_yuborish()
LANGUAGE plpgsql
AS $
DECLARE
  foydalanuvchi RECORD;
  yuborildi     INT := 0;
  xato          INT := 0;
BEGIN
  FOR foydalanuvchi IN SELECT id, email FROM foydalanuvchilar LOOP

    BEGIN
      -- Har bir foydalanuvchi uchun alohida urinish
      INSERT INTO xabarlar (foydalanuvchi_id, matn)
      VALUES (foydalanuvchi.id, 'Yangiliklar chiqdi!');

      yuborildi := yuborildi + 1;
      COMMIT;   -- ← har muvaffaqiyatli xabar alohida saqlanadi

    EXCEPTION
      WHEN OTHERS THEN
        xato := xato + 1;
        ROLLBACK;   -- ← faqat shu xabar bekor, qolganlar saqlanib qoladi
    END;

  END LOOP;

  RAISE NOTICE 'Yuborildi: %, Xato: %', yuborildi, xato;
END;
$;

Agar bu function bilan yozilganida — bitta xato hammani bekor qilardi. Procedure — har xabarni mustaqil saqlaydi.


OUT parametrlar — natija qaytarish

Procedure qiymat qaytarmaydi, lekin OUT parametrlar orqali natijani tashqariga chiqarish mumkin:

CREATE OR REPLACE PROCEDURE hisobot_yaratish(
  p_yil     INT,
  OUT p_jami        NUMERIC,
  OUT p_buyurtmalar BIGINT
)
LANGUAGE plpgsql
AS $
BEGIN
  SELECT
    SUM(summa),
    COUNT(*)
  INTO p_jami, p_buyurtmalar
  FROM buyurtmalar
  WHERE EXTRACT(YEAR FROM sana) = p_yil;
END;
$;

Chaqirish va natijani olish:

-- Natijani o'zgaruvchiga olish
DO $
DECLARE
  jami        NUMERIC;
  buyurtmalar BIGINT;
BEGIN
  CALL hisobot_yaratish(2024, jami, buyurtmalar);
  RAISE NOTICE 'Jami: %, Buyurtmalar: %', jami, buyurtmalar;
END;
$;

INOUT parametr — kirish va chiqish

INOUT — parametr ham kirish, ham chiqish sifatida ishlaydi. Qiymat bilan kiradi, o'zgartirilgan qiymat bilan chiqadi:

CREATE OR REPLACE PROCEDURE soliq_qosh(INOUT narx NUMERIC, soliq_foiz NUMERIC DEFAULT 12)
LANGUAGE plpgsql
AS $
BEGIN
  narx := narx * (1 + soliq_foiz / 100.0);
END;
$;

DO $
DECLARE
  men_narxi NUMERIC := 100000;
BEGIN
  CALL soliq_qosh(men_narxi);
  RAISE NOTICE 'Soliqlari bilan: %', men_narxi;   -- 112000
END;
$;

Function vs Procedure — qaysi birini tanlash

Function tanlang:
  → Hisoblash kerak, natija kerak  (SELECT bilan chaqirish)
  → SQL so'rov ichida ishlatish  (WHERE, JOIN, SELECT)
  → Qiymat qaytarish muhim

Procedure tanlang:
  → Ish bajarish kerak, natija shart emas
  → Tranzaksiyani o'zi boshqarish kerak  (COMMIT/ROLLBACK)
  → Uzoq jarayon, bir necha bosqich
  → Ommaviy operatsiyalar (batch processing)

Shubha bo'lsa — Function. Ko'pchilik holatlarda Function yetarli. Procedure faqat tranzaksiyani o'zi boshqarish kerak bo'lganda kerak.


Amaliy misol — buyurtma yakunlash

Buyurtma yakunlash jarayoni: balansni kamaytirish, buyurtma holatini yangilash, tarix yozish — va agar biri muvaffaqiyatsiz bo'lsa, faqat o'sha bosqichni qaytarish:

CREATE OR REPLACE PROCEDURE buyurtma_yakunla(
  p_buyurtma_id  INT,
  p_summa        NUMERIC
)
LANGUAGE plpgsql
AS $
DECLARE
  v_foydalanuvchi_id INT;
BEGIN
  -- Buyurtma kimga tegishli
  SELECT foydalanuvchi_id INTO v_foydalanuvchi_id
  FROM buyurtmalar WHERE id = p_buyurtma_id;

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Buyurtma topilmadi: %', p_buyurtma_id;
  END IF;

  -- Balansdan yechish
  UPDATE hisoblar
  SET balans = balans - p_summa
  WHERE foydalanuvchi_id = v_foydalanuvchi_id
    AND balans >= p_summa;   -- yetarli bo'lsagina

  IF NOT FOUND THEN
    RAISE EXCEPTION 'Balans yetarli emas';
  END IF;

  -- Buyurtma holatini yangilash
  UPDATE buyurtmalar
  SET holat = 'paid', to_langan_sana = NOW()
  WHERE id = p_buyurtma_id;

  -- Tarix
  INSERT INTO to_lovlar (buyurtma_id, summa, sana)
  VALUES (p_buyurtma_id, p_summa, NOW());

  COMMIT;   -- hammasi muvaffaqiyatli

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    RAISE NOTICE 'Xato: %', SQLERRM;
    RAISE;
END;
$;

Tez-tez uchraydigan xatolar

Procedure ni SELECT bilan chaqirishga urinish:

-- ❌ — procedure SELECT bilan chaqirilmaydi
SELECT pul_otkazish(1, 2, 50000);
-- ERROR: pul_otkazish() is a procedure

-- ✅ — CALL bilan
CALL pul_otkazish(1, 2, 50000);

Function ichida COMMIT yozishga urinish:

-- ❌ — function ichida transaction boshqarish mumkin emas
CREATE FUNCTION ... AS $
BEGIN
  UPDATE ...;
  COMMIT;   -- ERROR: invalid transaction termination
END;
$;

-- ✅ — bu PROCEDURE da mumkin
CREATE PROCEDURE ... AS $
BEGIN
  UPDATE ...;
  COMMIT;   -- ishlaydi
END;
$;

Xulosa

Procedure — Function ga o'xshash, lekin ish bajarish uchun, natija qaytarish uchun emas.

CREATE OR REPLACE PROCEDURE nom(parametrlar)
LANGUAGE plpgsql AS $ ... $;

CALL nom(qiymatlar);   -- chaqirish

Asosiy farq yodda bo'lsin:

Function  → SELECT, qiymat qaytaradi, tranzaksiya boshqara olmaydi
Procedure → CALL,   ish bajaradi,     COMMIT/ROLLBACK yoza oladi

OUT va INOUT parametrlar orqali procedure ham "natija" qaytarishi mumkin — lekin bu qiymat qaytarish emas, parametr o'zgartirish.


Keyingi dars

34-dars: Trigger — BEFORE, AFTER, INSTEAD OF, trigger funksiya