16 β Window funksiyalar¶
β¬ οΈ Oldingi: 15 β CTE β WITH bilan ishlash Β· π README Β· Keyingi: 17 β UPDATE va DELETE β chuqur β‘οΈ
Bu bobda: window (oyna) funksiyalarini o'rganamiz: OVER va PARTITION BY bilan qatorlarni yo'qotmasdan guruh hisob-kitobini qilish, ROW_NUMBER/RANK/DENSE_RANK bilan raqamlash, "har guruhda eng kattasi" klassik masalasi, LAG/LEAD bilan oldingi-keyingi qatorga qarash, yig'ilib boruvchi summa (running total) va LAST_VALUE'dagi mashhur tuzoq.
11-bobda GROUP BY bilan tanishganimizda bir narsaga ko'nikkan edik: guruhlash β bu qatorlarni "yig'ishtirib", har guruhdan bitta qator qoldirish. Lekin amaliyotda tez-tez boshqacha savol tug'iladi: "menga HAMMA qatorlar kerak, lekin har qatorning YONIDA guruh statistikasi ham tursin". Mana shu yerda window funksiyalar (oyna funksiyalari) sahnaga chiqadi β zamonaviy SQL'ning eng kuchli qurollaridan biri.
π Window funksiyalar MySQL'da 8.0 versiyadan beri bor. Eski 5.7 da bu sintaksis xato beradi β yana bir sabab yangi versiyada ishlashga.
G'oya: GROUP BY qatorlarni yutadi, WINDOW yutmaydi¶
Taksi bazasini eslang: 10 ta safar, 5 ta haydovchi. GROUP BY haydovchi_id qilsak, 10 qator 5 qatorga "yiqilib" tushadi β har haydovchidan bittadan, safarlarning o'zi (sana, narx, mijoz) esa natijadan yo'qoladi. Lekin "har safar YONIDA shu haydovchining jami daromadi ham ko'rinsin" desak-chi? Window funksiya aynan shuni qiladi:
SELECT
haydovchi_id, narx, sana,
SUM(narx) OVER (PARTITION BY haydovchi_id) AS haydovchi_jami
FROM taksi.safarlar;
OVER (PARTITION BY haydovchi_id) so'zma-so'z: "haydovchi bo'yicha guruhlab hisobla, lekin qatorlarni saqlab qol". Qatorlar soni o'zgarmaydi β shunchaki yangi ustun qo'shiladi (natijaning bir qismi):
| haydovchi_id | narx | haydovchi_jami |
|---|---|---|
| 1 | 35000 | 100000 |
| 1 | 20000 | 100000 |
| 1 | 45000 | 100000 |
| 2 | 42000 | 96000 |
| 2 | 28000 | 96000 |
Taqqoslab qo'yaylik:
| GROUP BY | Window (OVER) | |
|---|---|---|
| Qatorlar soni | kamayadi (har guruhdan 1 ta) | o'zgarmaydi |
| Asl ustunlar | faqat guruhlanganlari qoladi | hammasi joyida turadi |
| Natija | guruh hisoboti | har qator + guruh statistikasi |
"Window" (oyna) nomi ham shundan: funksiya har bir qator uchun jadvalga go'yo oynadan qaraydi β o'sha qatorga tegishli qatorlar to'plamini (oynasini) ko'radi va hisobni shu to'plam ustida bajaradi. OVER qavsi ichidagi ikki kalit so'zni eslab qoling:
PARTITION BYβ jadval qanday oynalarga bo'linishini aytadi (yozmasangiz β butun jadval bitta oyna);ORDER BYβ oyna ichidagi tartibni belgilaydi (raqamlash, LAG/LEAD va running total uchun kerak bo'ladi).
π Bo'sh OVER () ham mutlaqo qonuniy β u holda oyna butun jadval bo'ladi. "Har mahsulot yonida UMUMIY o'rtacha narx tursin" kabi masalalarda juda qulay:
SELECT nomi, narx,
AVG(narx) OVER () AS umumiy_ortacha,
narx - AVG(narx) OVER () AS farq
FROM dokon.mahsulotlar;
Raqamlash: ROW_NUMBER, RANK, DENSE_RANK¶
Window funksiyalarning ikkinchi katta oilasi β raqamlovchilar. Eng oddiysi ROW_NUMBER() β qatorlarni ko'rsatilgan tartibda 1 dan boshlab raqamlab chiqadi:
SELECT nomi, narx,
ROW_NUMBER() OVER (ORDER BY narx DESC) AS orin
FROM dokon.mahsulotlar;
-- Eng qimmat = 1, keyingisi = 2...
E'tibor bering: bu yerdagi ORDER BY oynaning ichida β u raqamlash tartibini belgilaydi, natija qatorlarining ekrandagi tartibini emas (buning uchun so'rov oxiridagi odatdagi ORDER BY xizmat qiladi).
Endi qiziq savol: ikkita qiymat TENG bo'lsa-chi? Aynan shu yerda uchala funksiya farq qiladi. Aytaylik, narxlar 100 000, 90 000, 90 000, 80 000:
| narx | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 100000 | 1 | 1 | 1 |
| 90000 | 2 | 2 | 2 |
| 90000 | 3 | 2 | 2 |
| 80000 | 4 | 4 | 3 |
ROW_NUMBERβ 1,2,3,4: har doim ketma-ket. Tenglarga ham har xil raqam beradi (qaysi biri 2, qaysi biri 3 bo'lishi β tasodif, chunki tartib noaniq);RANKβ 1,2,2,4: tenglarga teng o'rin, keyin SAKRAYDI. Xuddi sportdagidek: ikki kishi 2-o'rinni bo'lishsa, keyingisi 4-o'rin oladi;DENSE_RANKβ 1,2,2,3: tenglarga teng o'rin, lekin sakramaydi ("zich" rank).
π‘ Yana bir raqamlovchi β NTILE(n): qatorlarni n ta teng guruhga bo'lib, har qatorga guruh raqamini (1 dan n gacha) yozadi. Masalan, NTILE(4) OVER (ORDER BY narx) mahsulotlarni narx bo'yicha 4 ta "chorak"ka ajratadi β masalalarda sinab ko'rasiz.
Klassik masala: har guruhda eng kattasi¶
"Har kategoriyada eng qimmat mahsulot" β intervyularda ham, real ishda ham eng ko'p uchraydigan savollardan. Bu window'ning yulduzli oni:
WITH raqamlangan AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY kategoriya_id ORDER BY narx DESC) AS rn
FROM dokon.mahsulotlar
)
SELECT * FROM raqamlangan WHERE rn = 1;
Mantiq ikki qadam: har kategoriya ICHIDA mahsulotlarni narx bo'yicha (qimmatdan arzonga) raqamlaymiz, keyin har guruhning 1-raqamlisini olamiz. Bu rn = 1 patternini yodlab oling β juda ko'p ishlatasiz.
π "Nega CTE kerak, to'g'ridan-to'g'ri WHERE rn = 1 deb yozsak bo'lmaydimi?" β Yo'q. Window funksiyalar WHERE bosqichidan KEYIN hisoblanadi, shuning uchun WHERE ichida ularga murojaat qilib bo'lmaydi (MySQL "Unknown column 'rn'" deb xato beradi). Avval CTE (yoki subquery) ichida hisoblab olamiz, keyin tashqarida filtrlaymiz.
π‘ Eng qimmat mahsulot ikkita bo'lsa-yu, IKKALASI ham kerak bo'lsa β ROW_NUMBER o'rniga RANK ishlating: tenglarning hammasi rn = 1 bo'lib chiqadi.
LAG va LEAD β oldingi/keyingi qatorga qarash¶
Ba'zan har qator o'z "qo'shnisi" bilan solishtirilishi kerak: bugungi tushum kechagidan qancha farq qiladi? LAG β oldingi qatordagi, LEAD β keyingi qatordagi qiymatni olib keladi:
-- Har kun tushumi + kechagi bilan farqi:
WITH kunlik AS (
SELECT DATE(sana) AS kun, SUM(narx) AS tushum
FROM taksi.safarlar
GROUP BY kun
)
SELECT kun, tushum,
LAG(tushum) OVER (ORDER BY kun) AS kechagi,
tushum - LAG(tushum) OVER (ORDER BY kun) AS farq
FROM kunlik;
| kun | tushum | kechagi | farq |
|---|---|---|---|
| 2026-06-01 | 77000 | NULL | NULL |
| 2026-06-02 | 20000 | 77000 | -57000 |
| 2026-06-03 | 25000 | 20000 | 5000 |
| 2026-06-04 | 50000 | 25000 | 25000 |
π Birinchi qatorning "kechasi" yo'q β shuning uchun LAG u yerda NULL qaytaradi (oxirgi qatorda LEAD ham xuddi shunday). To'liq shakli LAG(ustun, qadam, standart): LAG(tushum, 1, 0) β "1 qadam orqaga qara, topolmasang 0 de". LAG(tushum, 7) desangiz β bir hafta oldingi kun bilan solishtirasiz.
π‘ Bitta oynani ikki marta yozish zerikarli bo'lsa, unga nom bering β WINDOW bandi (MySQL 8 da bor). Yuqoridagi misolning SELECT qismi shunday qisqaradi (boshidagi WITH kunlik AS (...) qismi o'z joyida qoladi β usiz kunlik jadvali topilmaydi): SELECT kun, tushum, LAG(tushum) OVER w AS kechagi, tushum - LAG(tushum) OVER w AS farq FROM kunlik WINDOW w AS (ORDER BY kun);
Yig'ilib boruvchi summa (running total)¶
Agregat oynasiga ORDER BY qo'shsangiz, hisob "yig'ilib boruvchi" rejimga o'tadi β har qatorda boshidan shu qatorgacha bo'lgan jami chiqadi:
SELECT id, sana, narx,
SUM(narx) OVER (ORDER BY sana, id) AS yigilgan
FROM taksi.safarlar;
-- Har qatorda: shu paytgacha jami qancha tushum bo'lgan
| sana | narx | yigilgan |
|---|---|---|
| 2026-06-01 08:30 | 35000 | 35000 |
| 2026-06-01 09:15 | 42000 | 77000 |
| 2026-06-02 14:00 | 20000 | 97000 |
| 2026-06-03 18:45 | 25000 | 122000 |
Bu β bank ilovangizdagi "balans tarixi"ning aynan o'zi: har amaliyot yonida shu paytgacha yig'ilgan summa. PARTITION BY bilan birlashtirsangiz, har guruh o'z ichida noldan boshlab yig'iladi: SUM(narx) OVER (PARTITION BY haydovchi_id ORDER BY sana) β har haydovchi daromadi qanday o'sib borgani.
π‘ ORDER BYda sanadan keyin id ham turibdi β ikki safar aynan bir vaqtda bo'lsa ham tartib bir ma'noli bo'lishi uchun. Tartib noaniq qolsa, teng qiymatli qatorlarda yig'ilgan summa "g'alati" ko'rinishi mumkin.
FIRST_VALUE, LAST_VALUE va bitta tuzoq¶
Oynadagi birinchi va oxirgi qiymatni olish uchun FIRST_VALUE va LAST_VALUE funksiyalari bor. Lekin LAST_VALUEda deyarli har bir boshlovchi yiqiladigan tuzoq yashiringan:
SELECT mijoz_id, sana,
FIRST_VALUE(sana) OVER (PARTITION BY mijoz_id ORDER BY sana) AS birinchi,
-- Kutilmagan natija: bu "oxirgi" emas, JORIY qatorning o'zini qaytaradi!
LAST_VALUE(sana) OVER (PARTITION BY mijoz_id ORDER BY sana) AS oxirgi_emas
FROM dokon.buyurtmalar;
Sababi: OVER ichida ORDER BY bo'lsa, oyna sukut bo'yicha "boshidan JORIY qatorgacha" deb qisqartiriladi (bu chegara frame deyiladi). FIRST_VALUEga bu farq qilmaydi β boshi baribir boshida. LAST_VALUE uchun esa "oynaning oxiri" β joriy qatorning o'zi bo'lib qoladi. (Aniqroq aytsak: ORDER BY ustunida joriy qatorga TENG qiymatli qatorlar bo'lsa, frame ularni ham qamrab oladi va LAST_VALUE o'sha tengdoshlarning oxirgisini qaytaradi; bizning misolda sanalar takrorlanmagani uchun bu β joriy qatorning o'zi.) Davosi β frame'ni oxirigacha ochib qo'yish:
LAST_VALUE(sana) OVER (
PARTITION BY mijoz_id ORDER BY sana
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS oxirgi
π‘ Amalda ko'pchilik soddaroq yo'lni tanlaydi: MIN(sana) OVER (PARTITION BY mijoz_id) va MAX(sana) OVER (PARTITION BY mijoz_id) β frame'siz, tuzoqsiz, o'sha natija. 20-masalada ikkala usulni ham sinaysiz.
16-bob masalalari¶
- (dokon) Mahsulotlarni narx bo'yicha raqamlang (ROW_NUMBER)
- (dokon) RANK va DENSE_RANK bilan ham qiling β bir xil narxli mahsulot qo'shib, farqni ko'ring
- (dokon) Har kategoriya ichida mahsulotlarni narx bo'yicha raqamlang (PARTITION BY)
- (dokon) Har kategoriyaning eng qimmat mahsuloti (rn = 1 patterni)
- (dokon) Har kategoriyaning eng ARZON mahsuloti
- (dokon) Har mahsulot yonida o'z kategoriyasining o'rtacha narxi (AVG OVER PARTITION)
- (dokon) Har mahsulot o'z kategoriyasi o'rtachasidan necha foiz qimmat/arzon
- (kutubxona) Har janr ichida kitoblarni yil bo'yicha raqamlang
- (kutubxona) Har janrning eng eski kitobi (rn = 1)
- (kutubxona) Har kitob yonida o'sha muallifning jami kitoblari soni (COUNT OVER)
- (kutubxona) Kitoblarni sahifa bo'yicha 3 guruhga bo'ling: NTILE(3) OVER (ORDER BY sahifa) β natijaga qarab qaysi kitob qaysi guruhga tushganini tushuntiring
- (klinika) Har shifokor ichida qabullarni sana bo'yicha raqamlang β har shifokorning BIRINCHI qabuli qachon?
- (klinika) Har bemorning OXIRGI tashrifi (PARTITION BY bemor_id ORDER BY sana DESC, rn = 1)
- (klinika) Har qabul yonida: shu bemorning shu paytgacha jami to'lovi (PARTITION bilan running total)
- (taksi) Har haydovchining eng qimmat safari
- (taksi) Kunlik tushum + kechagi kun + farq (LAG misoli asosida)
- (taksi) Kunlik tushumning yig'ilib boruvchi summasi (running total)
- (taksi) Har safar yonida o'sha haydovchining o'rtacha safar narxi va shu safar undan qancha farq qilishi
- (taksi) LEAD bilan: har safar yonida o'sha haydovchining KEYINGI safari sanasi β safarlar orasidagi tanaffusni hisoblang (TIMESTAMPDIFF(HOUR, ...))
- (dokon) Murakkab: har mijozning birinchi va oxirgi buyurtmasi sanasi bitta qatorda (FIRST_VALUE va LAST_VALUE yoki MIN/MAX OVER β ikkala usulni sinang, LAST_VALUE'da frame tuzog'ini unutmang!)