Trigger — BEFORE, AFTER, INSTEAD OF, trigger funksiya

~25 daq · PostgreSQL · Funksiyalar va Protseduralar

Muammodan boshlaylik

Foydalanuvchi emailini o'zgartirdi. Siz bu o'zgarishni audit_log jadvaliga yozmoqchisiz — kim, qachon, nima o'zgartirdi. Buning uchun ilovada har qayerda "email o'zgartirilganda log yozish" kodini qo'shish kerak. Lekin kimdir unutsa-chi? Yoki boshqa ilova ham bir xil jadvalni o'zgartirsachi?

Yechim: bu logikani bazaning o'ziga qo'yish. Jadval qanday o'zgartirilishidan qat'i nazar — bazaning o'zi log yozsin. Bu — Trigger.


Trigger nima?

Trigger — jadvalda biror hodisa (INSERT, UPDATE, DELETE) bo'lganda avtomatik ishlaydigan funksiya. Siz chaqirmaysiz, baza o'zi chaqiradi.

Hayotiy o'xshatma: eshikdagi sensor. Odam kirganda chiroq yonadi — siz tugmani bosmaysiz, sensor o'zi sezadi va chiroqni yoqadi. Trigger — shu sensor. Jadvalda o'zgarish — sensor sezdi. Trigger funksiya — chiroq yondi.

Oddiy yondashuv:                 Trigger bilan:
──────────────────────────────   ──────────────────────────────
Ilova kodi → UPDATE              Ilova kodi → UPDATE
Ilova kodi → log yozish          Trigger ← baza o'zi yozadi
Ilova kodi 2 → UPDATE            Ilova kodi 2 → UPDATE
Ilova kodi 2 → log yozish        Trigger ← baza o'zi yozadi
Unutilsa → log yo'q ❌            Har doim yoziladi ✅

Trigger qanday ishlaydi

Trigger — ikki qismdan iborat:

1. Trigger funksiya — nima qilish kerakligini bildiradi. Oddiy funksiyaga o'xshash, lekin RETURNS TRIGGER qaytaradi.

2. Trigger o'zi — qachon va qaysi jadvalda ishlatilishini bildiradi.

Jadvalda hodisa
      ↓
Trigger ishga tushadi
      ↓
Trigger funksiya chaqiriladi
      ↓
Funksiya natijasiga qarab asl operatsiya davom etadi yoki bekor bo'ladi

Trigger funksiya

Trigger funksiya oddiy funksiyadan ikki narsa bilan farqlanadi:

  1. RETURNS TRIGGER — qaytish turi har doim shu
  2. Maxsus o'zgaruvchilar — NEW, OLD, TG_OP va boshqalar
CREATE OR REPLACE FUNCTION trigger_funksiya_nomi()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $
BEGIN
  -- NEW  → yangi qiymatlar (INSERT va UPDATE da bor)
  -- OLD  → eski qiymatlar (UPDATE va DELETE da bor)
  -- TG_OP → operatsiya turi: 'INSERT', 'UPDATE', 'DELETE'

  RETURN NEW;   -- BEFORE triggerda bu muhim
END;
$;

Trigger yaratish

CREATE TRIGGER trigger_nomi
  {BEFORE | AFTER | INSTEAD OF}     -- qachon
  {INSERT | UPDATE | DELETE | ...}  -- qaysi hodisa
  ON jadval_nomi
  FOR EACH {ROW | STATEMENT}        -- har qator uchun yoki bir marta
  EXECUTE FUNCTION trigger_funksiya_nomi();

BEFORE trigger — operatsiyadan oldin

BEFORE trigger operatsiya bajarilishidan oldin ishga tushadi. Funksiyadan qaytgan NEW qiymati asl jadvalga yoziladi — shu sababli BEFORE da ma'lumotni o'zgartirib, tozalab, validatsiya qilish mumkin.

Misol: email ni kichik harfga o'girish

Foydalanuvchi emailni qanday yozmaysin — baza doim kichik harfda saqlasin:

CREATE OR REPLACE FUNCTION email_kichik_harf()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $
BEGIN
  NEW.email := LOWER(TRIM(NEW.email));  -- ← NEW ni o'zgartirdik
  RETURN NEW;  -- ← o'zgartirilgan NEW jadvalga yoziladi
END;
$;

CREATE TRIGGER foydalanuvchi_email_trigger
  BEFORE INSERT OR UPDATE ON foydalanuvchilar
  FOR EACH ROW
  EXECUTE FUNCTION email_kichik_harf();

Endi:

INSERT INTO foydalanuvchilar (ism, email) VALUES ('Ali', '  ALI@MAIL.COM  ');
SELECT email FROM foydalanuvchilar WHERE ism = 'Ali';
-- ali@mail.com  ← avtomatik tozalandi va kichik harfga o'girildi

Misol: BEFORE da bekor qilish

Agar BEFORE trigger NULL qaytarsa — operatsiya umuman bajarilmaydi:

CREATE OR REPLACE FUNCTION narx_tekshir()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $
BEGIN
  IF NEW.narx < 0 THEN
    RAISE EXCEPTION 'Narx manfiy bo''lishi mumkin emas: %', NEW.narx;
    -- yoki: RETURN NULL;  -- xatosiz bekor qilish
  END IF;
  RETURN NEW;
END;
$;

CREATE TRIGGER mahsulot_narx_trigger
  BEFORE INSERT OR UPDATE ON mahsulotlar
  FOR EACH ROW
  EXECUTE FUNCTION narx_tekshir();

AFTER trigger — operatsiyadan keyin

AFTER trigger operatsiya bajarilgandan keyin ishga tushadi. Ma'lumot allaqachon yozilgan — uni o'zgartirish mumkin emas. Log yozish, boshqa jadvalni yangilash, xabar yuborish uchun ishlatiladi.

Misol: o'zgarishlarni log qilish

Kim, qachon, nima o'zgartirganini saqlash:

-- Avval log jadvali
CREATE TABLE foydalanuvchi_log (
  id         SERIAL PRIMARY KEY,
  amal       TEXT,              -- INSERT, UPDATE, DELETE
  eski_email TEXT,
  yangi_email TEXT,
  sana       TIMESTAMPTZ DEFAULT NOW()
);

CREATE OR REPLACE FUNCTION foydalanuvchi_log_yoz()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $
BEGIN
  INSERT INTO foydalanuvchi_log (amal, eski_email, yangi_email)
  VALUES (
    TG_OP,                           -- 'INSERT', 'UPDATE', 'DELETE'
    CASE WHEN TG_OP != 'INSERT' THEN OLD.email END,  -- eski qiymat
    CASE WHEN TG_OP != 'DELETE' THEN NEW.email END   -- yangi qiymat
  );
  RETURN NULL;   -- AFTER trigger da bu ahamiyatsiz
END;
$;

CREATE TRIGGER foydalanuvchi_log_trigger
  AFTER INSERT OR UPDATE OR DELETE ON foydalanuvchilar
  FOR EACH ROW
  EXECUTE FUNCTION foydalanuvchi_log_yoz();

Endi:

INSERT INTO foydalanuvchilar (ism, email) VALUES ('Ali', 'ali@mail.com');
UPDATE foydalanuvchilar SET email = 'ali2@mail.com' WHERE ism = 'Ali';
DELETE FROM foydalanuvchilar WHERE ism = 'Ali';

SELECT * FROM foydalanuvchi_log;
 id │  amal  │   eski_email   │   yangi_email
────┼────────┼────────────────┼────────────────
  1 │ INSERT │ NULL           │ ali@mail.com
  2 │ UPDATE │ ali@mail.com   │ ali2@mail.com
  3 │ DELETE │ ali2@mail.com  │ NULL

FOR EACH ROW va FOR EACH STATEMENT

FOR EACH ROW:                         FOR EACH STATEMENT:
──────────────────────────────────    ───────────────────────────────
Har bir o'zgartirilgan qator uchun    Butun operatsiya uchun bir marta
NEW va OLD bor                        NEW va OLD yo'q
100 qator o'zgarsa — 100 marta        100 qator o'zgarsa — 1 marta

Ko'pchilik holatlarda FOR EACH ROW ishlatiladi. FOR EACH STATEMENT — ko'p qatorli operatsiyaning umumiy holatini bilish kerak bo'lganda.


INSTEAD OF trigger — VIEW uchun

Oddiy VIEW ga INSERT/UPDATE/DELETE qilib bo'lmaydi. INSTEAD OF trigger VIEW ga yozishni real jadvalga yo'naltiradi.

-- View: foydalanuvchi + profil birgalikda
CREATE VIEW foydalanuvchi_profil AS
SELECT
  f.id, f.email,
  p.ism, p.bio
FROM foydalanuvchilar f
JOIN profillar p ON f.id = p.foydalanuvchi_id;

-- View ga INSERT qilish uchun trigger
CREATE OR REPLACE FUNCTION foydalanuvchi_profil_insert()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $
DECLARE
  yangi_id INT;
BEGIN
  INSERT INTO foydalanuvchilar (email) VALUES (NEW.email)
  RETURNING id INTO yangi_id;

  INSERT INTO profillar (foydalanuvchi_id, ism, bio)
  VALUES (yangi_id, NEW.ism, NEW.bio);

  RETURN NEW;
END;
$;

CREATE TRIGGER foydalanuvchi_profil_insert_trigger
  INSTEAD OF INSERT ON foydalanuvchi_profil
  FOR EACH ROW
  EXECUTE FUNCTION foydalanuvchi_profil_insert();

Endi:

-- View ga INSERT — trigger ikki jadvalni to'ldiradi
INSERT INTO foydalanuvchi_profil (email, ism, bio)
VALUES ('ali@mail.com', 'Ali', 'Dasturchi');

Trigger ni ko'rish va o'chirish

-- Jadvalning triggerlarini ko'rish
SELECT trigger_name, event_manipulation, action_timing
FROM information_schema.triggers
WHERE event_object_table = 'foydalanuvchilar';

-- O'chirish
DROP TRIGGER trigger_nomi ON jadval_nomi;

-- Vaqtincha o'chirish
ALTER TABLE foydalanuvchilar DISABLE TRIGGER trigger_nomi;
ALTER TABLE foydalanuvchilar ENABLE  TRIGGER trigger_nomi;

Tez-tez uchraydigan xatolar

BEFORE trigger da RETURN NEW ni unutish:

-- ❌ — RETURN yo'q yoki RETURN NULL — operatsiya bekor bo'ladi!
CREATE FUNCTION ... RETURNS TRIGGER AS $
BEGIN
  NEW.email := LOWER(NEW.email);
  -- RETURN yo'q → INSERT/UPDATE bajarilmaydi!
END;
$;

-- ✅ — RETURN NEW shart
BEGIN
  NEW.email := LOWER(NEW.email);
  RETURN NEW;   -- o'zgartirilgan qator yoziladi
END;

BEFORE trigger da RETURN NEW yozilmasa — yoki RETURN NULL qaytarilsa — operatsiya bajarilmaydi. Bu eng ko'p uchraydigan xato.

Trigger funksiyasi cheksiz o'zini chaqirishi:

-- ❌ — trigger UPDATE qiladi, UPDATE trigger ni qayta ishga tushiradi...
CREATE TRIGGER ...
  AFTER UPDATE ON foydalanuvchilar
  FOR EACH ROW
EXECUTE FUNCTION ...;

-- Trigger funksiyasi ichida:
UPDATE foydalanuvchilar SET ...  -- ← yana triggerni ishga tushiradi!

Trigger funksiyasi ichida o'sha jadvalga yozish — cheksiz rekursiya. pg_trigger_depth() bilan tekshirib oldini olish:

IF pg_trigger_depth() > 1 THEN RETURN NEW; END IF;

Xulosa

Trigger — jadvalda hodisa bo'lganda bazaning o'zi chaqiradigan funksiya. Ilova kodidan mustaqil, har doim ishlaydi.

BEFORE  → operatsiyadan oldin, NEW ni o'zgartirish mumkin
AFTER   → operatsiyadan keyin, log yozish, boshqa jadvalni yangilash
INSTEAD OF → VIEW ga yozish uchun

Qachon trigger kerak:

  • Ma'lumotni avtomatik tozalash yoki normallashtirish (email, telefon)
  • Audit log — kim, qachon, nima o'zgartirdi
  • Bog'liq jadvallarni sinxronlashtirish
  • VIEW ga yozish imkonini berish

Qachon trigger kerak emas:

  • Ilova kodi osongina hal qiladigan narsalar — trigger yashirin logika, debugging qiyin
  • Juda murakkab biznes logika — procedure yoki ilova kodi aniqroq

Keyingi dars

35-dars: VIEW va MATERIALIZED VIEW — farqi, REFRESH