Cursor — satr-satr ishlash
Muammodan boshlaylik
SQL to'plamlar bilan ishlaydi: UPDATE mahsulotlar SET narx = narx * 1.1 — barcha qatorlar birdaniga. Bu tez va qulay.
Lekin ba'zan har bir qator uchun alohida, ketma-ket ish qilish kerak bo'ladi:
- Har bir mijoz uchun shaxsiy hisobot jadvalini yaratish
- Har bir qatorga oldingi qatorga bog'liq bo'lgan murakkab qoidani qo'llash
- Har bir jadval uchun
OPTIMIZE TABLEbuyrug'ini dinamik bajarish
Stored procedure ichida natija to'plamini qatorma-qator aylanib chiqish uchun — cursor.
[!WARNING] Bu darsning eng muhim xulosasi oxirida: cursor — oxirgi chora. Deyarli har doim to'plamli (set-based) SQL ancha tez va sodda. Avval cursor qanday ishlashini, keyin nima uchun undan qochish kerakligini ko'ramiz.
Cursor ning to'rt bosqichi
DECLARE cur CURSOR FOR SELECT ...; -- 1. E'lon qilish
OPEN cur; -- 2. Ochish (so'rov bajariladi)
FETCH cur INTO o'zgaruvchilar; -- 3. Navbatdagi qatorni olish (siklda)
CLOSE cur; -- 4. Yopish
Qatorlar tugaganda FETCH "NOT FOUND" holatini qaytaradi. Uni ushlash uchun — handler.
To'liq misol
Har bir mijoz uchun sodiqlik darajasini hisoblab, alohida jadvalga yozamiz, har birini jurnalga qayd qilgan holda:
CREATE TABLE sodiqlik (
mijoz_id INT UNSIGNED PRIMARY KEY,
daraja VARCHAR(20) NOT NULL,
hisoblangan DATETIME NOT NULL
);
DELIMITER //
CREATE PROCEDURE sodiqlik_hisobla()
BEGIN
DECLARE v_tugadi BOOLEAN DEFAULT FALSE;
DECLARE v_mijoz INT;
DECLARE v_jami BIGINT;
DECLARE v_daraja VARCHAR(20);
-- 1. Cursor
DECLARE cur CURSOR FOR
SELECT m.id, COALESCE(SUM(q.soni * q.narx), 0)
FROM mijozlar m
LEFT JOIN buyurtmalar b ON b.mijoz_id = m.id AND b.holat <> 'bekor'
LEFT JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
GROUP BY m.id;
-- Qatorlar tugaganda v_tugadi = TRUE
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_tugadi = TRUE;
-- 2. Ochish
OPEN cur;
tsikl: LOOP
-- 3. Navbatdagi qator
FETCH cur INTO v_mijoz, v_jami;
IF v_tugadi THEN
LEAVE tsikl;
END IF;
SET v_daraja = CASE
WHEN v_jami >= 20000000 THEN 'Oltin'
WHEN v_jami >= 5000000 THEN 'Kumush'
WHEN v_jami > 0 THEN 'Bronza'
ELSE 'Yangi'
END;
INSERT INTO sodiqlik (mijoz_id, daraja, hisoblangan)
VALUES (v_mijoz, v_daraja, NOW())
AS y ON DUPLICATE KEY UPDATE daraja = y.daraja, hisoblangan = y.hisoblangan;
END LOOP;
-- 4. Yopish
CLOSE cur;
END //
DELIMITER ;
CALL sodiqlik_hisobla();
SELECT * FROM sodiqlik;
+----------+--------+---------------------+
| mijoz_id | daraja | hisoblangan |
+----------+--------+---------------------+
| 1 | Kumush | 2026-11-04 15:30:00 |
| 2 | Oltin | 2026-11-04 15:30:00 |
| 3 | Yangi | 2026-11-04 15:30:00 |
| 4 | Yangi | 2026-11-04 15:30:00 |
+----------+--------+---------------------+
Muhim tafsilotlar
E'lon qilish tartibi qat'iy: avval o'zgaruvchilar, keyin cursor lar, keyin handler lar. Boshqa tartibda — sintaksis xatosi.
CONTINUE HANDLER FOR NOT FOUND — FETCH qator topa olmaganda xato emas, faqat bayroqni o'rnatadi va davom etadi. Siklda bayroqni tekshirib, LEAVE bilan chiqasiz.
tsikl: LOOP ... END LOOP — nomlangan sikl. LEAVE tsikl — undan chiqish (break), ITERATE tsikl — keyingi aylanishga o'tish (continue).
[!WARNING] Handler procedure ichidagi boshqa
SELECT ... INTOlar uchun ham ishlaydi. Sikl ichidaSELECT x INTO v FROM ...hech narsa topmasa —v_tugadihamTRUEbo'lib qoladi va sikl erta tugaydi. Bunday holatda ichkiSELECTdan oldin va keyin bayroqni tiklang yoki ichki so'rovni alohidaBEGIN ... ENDblokiga, o'z handler i bilan oling.
MySQL cursor larining cheklovlari
- Faqat o'qish — cursor orqali qatorni to'g'ridan-to'g'ri o'zgartirib bo'lmaydi
- Faqat oldinga — orqaga qaytish yoki sakrash yo'q
- Faqat stored procedure, function, trigger va event ichida
Xuddi shu ish — cursorsiz
Yuqoridagi butun procedure — bitta so'rov:
INSERT INTO sodiqlik (mijoz_id, daraja, hisoblangan)
SELECT * FROM (
SELECT
m.id,
CASE
WHEN COALESCE(SUM(q.soni * q.narx), 0) >= 20000000 THEN 'Oltin'
WHEN COALESCE(SUM(q.soni * q.narx), 0) >= 5000000 THEN 'Kumush'
WHEN COALESCE(SUM(q.soni * q.narx), 0) > 0 THEN 'Bronza'
ELSE 'Yangi'
END AS daraja,
NOW() AS vaqt
FROM mijozlar m
LEFT JOIN buyurtmalar b ON b.mijoz_id = m.id AND b.holat <> 'bekor'
LEFT JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
GROUP BY m.id
) AS y
ON DUPLICATE KEY UPDATE daraja = y.daraja, hisoblangan = y.vaqt;
| Cursor | Set-based | |
|---|---|---|
| 100 000 mijoz | 100 000 ta alohida INSERT |
1 ta so'rov |
| Tezlik | Soniyalar-daqiqalar | Bir necha soniya ichida |
| Kod hajmi | ~40 qator | ~15 qator |
| Optimizator yordami | Yo'q | To'liq |
Qachon cursor haqiqatan kerak
Cursor ni faqat quyidagi holatlarda ko'rib chiqing:
- Har bir qator uchun dinamik SQL — jadval nomi yoki buyruq qatorga bog'liq:
-- Har bir arxiv jadval uchun OPTIMIZE TABLE
DECLARE cur CURSOR FOR
SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'dokon' AND TABLE_NAME LIKE 'arxiv_%';
-- sikl ichida:
SET @sql = CONCAT('OPTIMIZE TABLE ', v_jadval);
PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st;
- Har bir qator uchun procedure chaqirish, uni to'plamli qilib bo'lmasa
- Har bir qator natijasi keyingisiga bog'liq — va window funksiyalar (29-dars) yordam bermasa
- Juda katta ishni bo'laklarga bo'lib, har bo'lakdan keyin
COMMITqilish
Boshqa barcha holatlarda — UPDATE ... JOIN, INSERT ... SELECT, CASE, window funksiyalar.
Nima uchun shunday?
Nima uchun SQL da "qatorma-qator" ishlash bunchalik sekin, dasturlash tillarida esa sikl — oddiy narsa?
SQL — deklarativ til: siz "nima" kerakligini aytasiz, baza "qanday" bajarishni o'zi hal qiladi — indekslarni, JOIN algoritmlarini, xotirani tanlaydi. To'plamli so'rovda optimizator butun vazifani ko'radi va eng samarali yo'lni topadi. Cursor bilan esa siz unga "qanday" ni o'zingiz aytasiz — bittadan, ketma-ket — va optimizator yordam bera olmaydi. Natija — har bir qator uchun alohida kichik so'rov, ya'ni N+1 muammosi (18-dars) bazaning ichida.
Xulosa
Eslab qoling:
- Cursor:
DECLARE ... CURSOR FOR→OPEN→FETCH ... INTO(siklda) →CLOSE DECLARE CONTINUE HANDLER FOR NOT FOUND SET tugadi = TRUE+LEAVE- E'lon tartibi: o'zgaruvchilar → cursor lar → handler lar
- MySQL cursor lari — faqat o'qish, faqat oldinga, faqat procedure ichida
- Handler sikl ichidagi boshqa
SELECT INTOlarga ham ta'sir qiladi — ehtiyot bo'ling - Deyarli har doim set-based SQL tezroq va soddaroq
- Cursor — dinamik SQL va to'plamli qilib bo'lmaydigan holatlar uchun
Keyingi dars
46-dars: Normalizatsiya — 1NF, 2NF, 3NF, BCNF