Trigger — BEFORE, AFTER, INSERT, UPDATE, DELETE

~20 daq · MySQL · View, Stored Procedure, Trigger

Muammodan boshlaylik

Ikki talab:

  1. Audit: mahsulot narxi qachon, qanchadan qanchaga o'zgarganini saqlash kerak. Narx admin paneldan, import skriptidan, API dan va qo'lda Workbench dan o'zgaradi.
  2. Himoya: narx hech qachon nolga yoki manfiyga tushmasin.

Har bir joyda "narx o'zgarsa — tarixga yoz" kodini qo'shish mumkin. Lekin bitta joyda unutilsa — tarix to'liq bo'lmaydi. Va Workbench dan qo'lda o'zgartirishni ilova umuman ko'rmaydi.

Trigger — jadvalda ma'lum hodisa (INSERT, UPDATE, DELETE) bo'lganda bazaning o'zi avtomatik bajaradigan kod. Ma'lumot qaysi yo'l bilan o'zgarmasin — trigger ishlaydi.


Trigger tuzilishi

CREATE TRIGGER nom
    {BEFORE | AFTER} {INSERT | UPDATE | DELETE}
    ON jadval
    FOR EACH ROW
BEGIN
    ...
END;
  • BEFORE — o'zgarishdan oldin: qiymatlarni tekshirish yoki o'zgartirish
  • AFTER — o'zgarishdan keyin: boshqa jadvallarga yozish (audit, hisoblagichlar)
  • FOR EACH ROW — har bir ta'sirlangan qator uchun alohida (MySQL da faqat shu)

OLD va NEW

Trigger ichida qatorning eski va yangi qiymatlari:

Hodisa OLD NEW
INSERT — Qo'shilayotgan qator
UPDATE O'zgarishdan oldingi O'zgarishdan keyingi
DELETE O'chirilayotgan qator —

1-misol: audit — narx tarixi

CREATE TABLE narx_tarixi (
    id           INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    mahsulot_id  INT UNSIGNED NOT NULL,
    eski_narx    INT UNSIGNED NOT NULL,
    yangi_narx   INT UNSIGNED NOT NULL,
    kim          VARCHAR(100) NOT NULL,
    qachon       DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

DELIMITER //

CREATE TRIGGER trg_mahsulot_narx_audit
AFTER UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
    IF NEW.narx <> OLD.narx THEN
        INSERT INTO narx_tarixi (mahsulot_id, eski_narx, yangi_narx, kim)
        VALUES (NEW.id, OLD.narx, NEW.narx, CURRENT_USER());
    END IF;
END //

DELIMITER ;
UPDATE mahsulotlar SET narx = 11900000 WHERE id = 1;
UPDATE mahsulotlar SET soni = 10 WHERE id = 1;        -- narx o'zgarmadi — yozilmaydi

SELECT * FROM narx_tarixi;
+----+-------------+-----------+------------+----------------+---------------------+
| id | mahsulot_id | eski_narx | yangi_narx | kim            | qachon              |
+----+-------------+-----------+------------+----------------+---------------------+
|  1 |           1 |  12500000 |   11900000 | root@localhost | 2026-11-04 15:10:22 |
+----+-------------+-----------+------------+----------------+---------------------+

IF NEW.narx <> OLD.narx — faqat narx haqiqatan o'zgarganda. Aks holda har bir soni o'zgarishi ham tarixga tushardi.

[!INFO] CURRENT_USER() — MySQL foydalanuvchisi (dokon_app@%), saytdagi admin emas. Haqiqiy foydalanuvchini bilish uchun ilova sessiya o'zgaruvchisini o'rnatadi: SET @app_user = 'admin:42'; va trigger @app_user ni yozadi.


2-misol: tekshirish va tuzatish — BEFORE

DELIMITER //

CREATE TRIGGER trg_mahsulot_tekshir
BEFORE UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
    IF NEW.narx = 0 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Narx nol bo''lishi mumkin emas';
    END IF;

    -- Nomni avtomatik tozalash
    SET NEW.nom = TRIM(NEW.nom);

    -- Narx 50% dan ko'proq tushsa — ehtimol xato
    IF NEW.narx < OLD.narx * 0.5 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Narx 50% dan ortiq tushirildi — tasdiqlash kerak';
    END IF;
END //

DELIMITER ;
UPDATE mahsulotlar SET narx = 1000 WHERE id = 3;
-- ERROR 1644 (45000): Narx 50% dan ortiq tushirildi — tasdiqlash kerak

BEFORE triggerda SET NEW.ustun = ... — yoziladigan qiymatni o'zgartirish mumkin. AFTER da — yo'q (qator allaqachon yozilgan).

Oddiy qoidalar uchun CHECK cheklovi (6-dars) soddaroq. Trigger — OLD va NEW ni solishtirish kerak bo'lganda (CHECK faqat yangi qiymatni ko'radi).


3-misol: hisoblagichni yangilash

Mahsulotlar sonini toifa jadvalida saqlash (denormalizatsiya, 47-dars):

ALTER TABLE toifalar ADD COLUMN mahsulotlar_soni INT UNSIGNED NOT NULL DEFAULT 0;

DELIMITER //

CREATE TRIGGER trg_mahsulot_qoshildi AFTER INSERT ON mahsulotlar
FOR EACH ROW
BEGIN
    UPDATE toifalar SET mahsulotlar_soni = mahsulotlar_soni + 1 WHERE id = NEW.toifa_id;
END //

CREATE TRIGGER trg_mahsulot_ochirildi AFTER DELETE ON mahsulotlar
FOR EACH ROW
BEGIN
    UPDATE toifalar SET mahsulotlar_soni = mahsulotlar_soni - 1 WHERE id = OLD.toifa_id;
END //

CREATE TRIGGER trg_mahsulot_toifa_ozgardi AFTER UPDATE ON mahsulotlar
FOR EACH ROW
BEGIN
    IF NOT (NEW.toifa_id <=> OLD.toifa_id) THEN
        UPDATE toifalar SET mahsulotlar_soni = mahsulotlar_soni - 1 WHERE id = OLD.toifa_id;
        UPDATE toifalar SET mahsulotlar_soni = mahsulotlar_soni + 1 WHERE id = NEW.toifa_id;
    END IF;
END //

DELIMITER ;

Uchta hodisani ham hisobga olish kerak — bittasini unutsangiz, hisoblagich asta-sekin noto'g'ri bo'lib qoladi. <=> — NULL larni ham to'g'ri solishtirish (10-dars).


Trigger va tranzaksiya

Trigger — o'zgarishni keltirib chiqargan so'rov bilan bitta tranzaksiyada ishlaydi:

  • Trigger xato bersa (SIGNAL) — asl UPDATE ham bekor qilinadi
  • Asl tranzaksiya ROLLBACK qilinsa — trigger qilgan o'zgarishlar ham bekor bo'ladi

Audit tarixi va hisoblagichlar asl ma'lumot bilan doim mos.


Boshqarish

SHOW TRIGGERS FROM dokon;
SHOW CREATE TRIGGER trg_mahsulot_narx_audit\G
DROP TRIGGER IF EXISTS trg_mahsulot_narx_audit;

MySQL 8 da bir jadvalda bir xil hodisa uchun bir nechta trigger bo'lishi mumkin — tartibi FOLLOWS / PRECEDES bilan.


Ehtiyot bo'ling

Trigger — kuchli, lekin ko'rinmas vosita.

1. Yashirin xatti-harakat. Dasturchi UPDATE mahsulotlar yozadi va 3 ta boshqa jadval o'zgaradi — buni kodga qarab bilib bo'lmaydi. Debug paytida "bu qator qayerdan paydo bo'ldi?" degan savol soatlab vaqt oladi.

2. Tezlik. Trigger har bir qator uchun ishlaydi. UPDATE mahsulotlar SET narx = narx * 1.1 — 50 000 qator → 50 000 ta trigger chaqiruvi, 50 000 ta INSERT audit jadvaliga.

3. Zanjir. Trigger boshqa jadvalni o'zgartiradi, u yerda ham trigger bor... Trigger o'zi turgan jadvalni o'zgartira olmaydi (ERROR 1442), lekin boshqalar orqali murakkab zanjir hosil bo'lishi mumkin.

4. TRUNCATE va DROP — DELETE trigger larini ishga tushirmaydi. Foreign key CASCADE orqali o'chirilgan qatorlar ham trigger ni ishga tushirmaydi.

Qachon ishlatish

✅ Yaxshi nomzodlar:

  • Audit / o'zgarishlar tarixi — har qanday manbadan
  • Qo'shimcha himoya — OLD va NEW solishtirish kerak bo'lgan qoidalar
  • Oddiy denormalizatsiya hisoblagichlari

❌ Yomon nomzodlar:

  • Biznes jarayonlari (email yuborish, buyurtma holatini o'zgartirish) — ilovada
  • Murakkab mantiq — ilovada yoki procedure da
  • Juda ko'p yoziladigan jadvallar

Trigger yaratsangiz — hujjatlashtiring: jadval izohida yoki loyiha README sida.

ALTER TABLE mahsulotlar COMMENT = 'Triggerlar: narx audit (narx_tarixi), toifa hisoblagich';

Nima uchun shunday?

Nima uchun trigger lar "yomon amaliyot" deb tanqid qilinadi, agar ular bunchalik foydali bo'lsa?

Chunki ular sababni oqibatdan ajratadi. Kod o'qiyotgan odam UPDATE mahsulotlar SET narx = ? ni ko'radi va faqat bitta jadval o'zgaradi deb o'ylaydi. Haqiqatda esa audit yozuvi, hisoblagich yangilanishi va, ehtimol, xato. Bu "kutilmagan ta'sirlar" katta jamoada xatolarga olib keladi. Lekin audit kabi holatlarda aynan shu xususiyat — afzallik: ilova kodini chetlab o'tib bo'lmaydi. Trigger — "majburiy" qoidalar uchun, "qulay" avtomatlashtirish uchun emas.


Xulosa

Eslab qoling:

  • CREATE TRIGGER nom BEFORE|AFTER INSERT|UPDATE|DELETE ON jadval FOR EACH ROW
  • OLD — eski qiymat, NEW — yangi; INSERT da OLD yo'q, DELETE da NEW yo'q
  • BEFORE — tekshirish (SIGNAL) va SET NEW.x = ...; AFTER — boshqa jadvallarga yozish
  • Asl so'rov bilan bitta tranzaksiyada
  • Har bir qator uchun ishlaydi — ommaviy o'zgarishlarda sekin
  • TRUNCATE va CASCADE trigger ni ishga tushirmaydi
  • Audit va qat'iy himoya uchun; biznes jarayonlar — ilovada
  • Hujjatlashtiring — trigger lar ko'rinmas

Keyingi dars

44-dars: Event Scheduler — avtomatik vazifalar