SQL: GROUP BY va agregatlar
Bitta raqamdan jadvalgacha
8-bobda COUNT(*) bitta raqam berdi: bekor qilinganlar 1 000 ga yaqin. Analitik savoli esa deyarli har doim "... bo'yicha": to'lov usuli bo'yicha, oy bo'yicha, hudud bo'yicha. Bu GROUP BY. U jadvalni guruhlarga bo'ladi va har guruh uchun bitta qator qaytaradi:
SELECT status, COUNT(*) AS soni FROM orders GROUP BY status
| status | soni |
|---|---|
| cancelled | 765 |
| delivered | 18 661 |
| returned | 574 |
3 qator, 68 ms
Excel pivot ning Rows va Values qismi aynan shu. Farqi: bu yerda 20 million qator ham bo'lishi mumkin.
Agregat funksiyalar
| Funksiya | Nima | NULL bilan |
|---|---|---|
COUNT(*) |
qatorlar soni | NULL ham sanaladi |
COUNT(ustun) |
bo'sh bo'lmaganlar | NULL sanalmaydi |
SUM(ustun) |
yig'indi | NULL e'tiborsiz |
AVG(ustun) |
o'rtacha | NULL e'tiborsiz (0 deb olmaydi, bu yaxshi) |
MIN, MAX |
eng kichik, eng katta | NULL e'tiborsiz |
SELECT payment_method, COUNT(*) AS buyurtmalar, SUM(total_amount) / 1000000.0 AS summa_mln, ROUND(AVG(total_amount)) AS ortacha_chek, MAX(total_amount) AS eng_katta FROM orders WHERE status = 'delivered' GROUP BY payment_method ORDER BY summa_mln DESC
| payment_method | buyurtmalar | summa_mln | ortacha_chek | eng_katta |
|---|---|---|---|---|
| card | 5 589 | 9 801.152 | 1 753 650 | 34 391 000 |
| click | 5 332 | 9 684.275 | 1 816 256 | 43 841 000 |
| payme | 5 021 | 9 057.434 | 1 803 910 | 28 600 000 |
| cash | 2 719 | 4 934.043 | 1 814 654 | 35 910 000 |
4 qator, 34 ms
Bir so'rovda to'rt savolga javob. WHERE guruhlashdan oldin filtrlaydi (faqat yetkazilganlar), ORDER BY natija jadvalini tartiblaydi. ROUND o'rtachadagi kasrni olib tashlaydi: hisobotda 1 816 693.7 emas, 1 816 694.
Qoida: SELECT dagi hamma ustun yo GROUP BY da, yo agregatda
SELECT payment_method, order_id, COUNT(*) -- XATO FROM orders GROUP BY payment_method
order_id guruhda yuzlab qiymat oladi, qaysi birini ko'rsatsin? PostgreSQL xato beradi. SQLite jimgina tasodifiy bittasini beradi: bu yomonroq, chunki xato ko'rinmaydi. Qoida: SELECT dagi har ustun yo GROUP BY da, yo COUNT/SUM/AVG ichida.
Bir nechta ustun bo'yicha
SELECT payment_method, status, COUNT(*) AS soni FROM orders GROUP BY payment_method, status ORDER BY payment_method, status
| payment_method | status | soni |
|---|---|---|
| card | cancelled | 184 |
| card | delivered | 5 589 |
| card | returned | 177 |
| cash | cancelled | 273 |
| cash | delivered | 2 719 |
| cash | returned | 83 |
| click | cancelled | 155 |
| click | delivered | 5 332 |
| click | returned | 163 |
| payme | cancelled | 153 |
| payme | delivered | 5 021 |
| payme | returned | 151 |
12 qator, 27 ms
Har juftlik uchun bitta qator: 4 to'lov usuli × 3 holat = 12 qator. Bu 3-bobdagi long shakl. Pivot dagi Rows + Columns ko'rinishi (wide) uchun 13-bobdagi CASE yoki pandas pivot (19-bob).
Hikoya 5: qaysi to'lovda bekor qilish ko'p
Sonlar o'zi javob bermaydi: naqd to'lovda 300 ta bekor, kartada 200 ta, lekin naqd buyurtmalar kamroq. Ulush kerak:
SELECT payment_method, COUNT(*) AS jami, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS bekor, ROUND(100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS bekor_foiz FROM orders GROUP BY payment_method ORDER BY bekor_foiz DESC
| payment_method | jami | bekor | bekor_foiz |
|---|---|---|---|
| cash | 3 075 | 273 | 8.9 |
| card | 5 950 | 184 | 3.1 |
| payme | 5 325 | 153 | 2.9 |
| click | 5 650 | 155 | 2.7 |
4 qator, 25 ms
Naqd to'lovda bekor qilish ulushi boshqalardan taxminan uch barobar yuqori. Bu 1-bobdagi 5-sir. CASE WHEN ... THEN 1 ELSE 0 END ni SUM ichida ishlatish "shartli sanash" degani, Excel dagi COUNTIF ning SQL varianti. 13-bobda CASE batafsil. 100.0 * dagi nuqta: butun songa bo'lish tuzog'i (7-bob).
Bu jadval qaror uchun yetarli emas, lekin savol uchun yetarli: "naqd buyurtmalarni tasdiqlash uchun qo'ng'iroq qilaylikmi?" Analitik ishi shu: raqamdan savolga.
HAVING: guruhlarni filtrlash
WHERE qatorlarni guruhlashdan oldin filtrlaydi. Guruhlarni natijasiga qarab filtrlash uchun HAVING:
SELECT customer_id, COUNT(*) AS buyurtmalar, SUM(total_amount) / 1000000.0 AS summa_mln FROM orders WHERE status = 'delivered' GROUP BY customer_id HAVING COUNT(*) >= 15 ORDER BY buyurtmalar DESC LIMIT 10
| customer_id | buyurtmalar | summa_mln |
|---|---|---|
| 867 | 61 | 112.529 |
| 598 | 58 | 78.761 |
| 145 | 49 | 104.248 |
| 264 | 48 | 83.081 |
| 1 552 | 47 | 125.702 |
| 514 | 45 | 90.673 |
| 1 379 | 45 | 67.754 |
| 2 657 | 43 | 81.325 |
| 1 010 | 42 | 77.249 |
| 2 746 | 41 | 43.755 |
10 qator, 104 ms
"15 va undan ko'p buyurtma bergan mijozlar." Bu shartni WHERE ga yozib bo'lmaydi: WHERE COUNT(*) >= 15 xato, chunki WHERE paytida guruh hali yo'q. Ikkalasi bir so'rovda: WHERE qatorlar uchun (yetkazilgan), HAVING guruhlar uchun (15+).
Oy bo'yicha: birinchi vaqt qatori
SELECT substr(order_date, 1, 7) AS oy, COUNT(*) AS buyurtmalar, SUM(total_amount) / 1000000.0 AS summa_mln FROM orders WHERE status = 'delivered' GROUP BY oy ORDER BY oy
| oy | buyurtmalar | summa_mln |
|---|---|---|
| 2025-01 | 788 | 1 324.116 |
| 2025-02 | 725 | 1 385.47 |
| 2025-03 | 1 138 | 2 051.271 |
| 2025-04 | 1 119 | 2 102.522 |
| 2025-05 | 1 137 | 2 072.246 |
| 2025-06 | 1 057 | 1 617.648 |
| 2025-07 | 1 096 | 2 119.16 |
| 2025-08 | 1 408 | 2 637.128 |
| 2025-09 | 1 685 | 2 938.99 |
| 2025-10 | 2 029 | 3 538.945 |
| 2025-11 | 2 640 | 4 841.466 |
| 2025-12 | 3 839 | 6 847.942 |
12 qator, 179 ms
substr(order_date, 1, 7) sananing birinchi 7 belgisi: '2025-06'. ISO format shuning uchun qulay. Bu 1-bobdagi jadval va 4-bobdagi pivot. Uch vosita, bitta javob: iyun 1 618 mln, dekabr 6 848 mln. Agar uchta vositada uch xil raqam chiqsa, bittasida filtr boshqacha: shu yerda status = 'delivered' ekanini eslang.
GROUP BY oy da alias ishlatildi: SQLite va PostgreSQL ikkalasi ham GROUP BY da alias ni qabul qiladi (WHERE dan farqli). Xavfsiz yo'l: ifodani takrorlash, GROUP BY substr(order_date, 1, 7).
Hikoya 3: o'rtacha chek hudud bo'yicha
orders da hudud yo'q, customers da bor. Ularni bog'lash 10-bob (JOIN). Hozircha hudud o'rniga kanal, u ham customers da... demak u ham 10-bobda. GROUP BY ning haqiqiy kuchi JOIN bilan ochiladi: keyingi bob.
Shu bobda mumkin bo'lgani: kategoriya bo'yicha mahsulotlar:
SELECT category, COUNT(*) AS mahsulotlar, ROUND(AVG(price)) AS ortacha_narx, MIN(price) AS eng_arzon, MAX(price) AS eng_qimmat, SUM(stock) AS omborda FROM products GROUP BY category ORDER BY ortacha_narx DESC
| category | mahsulotlar | ortacha_narx | eng_arzon | eng_qimmat | omborda |
|---|---|---|---|---|---|
| Maishiy texnika | 10 | 1 906 000 | 180 000 | 5 200 000 | 2 448 |
| Elektronika | 15 | 1 772 667 | 35 000 | 7 800 000 | 3 130 |
| Sport | 9 | 447 222 | 35 000 | 2 400 000 | 1 938 |
| Kiyim | 12 | 262 500 | 60 000 | 650 000 | 2 291 |
| Uy-ro'zg'or | 10 | 250 800 | 28 000 | 950 000 | 1 421 |
| Go'zallik | 8 | 148 750 | 18 000 | 380 000 | 2 027 |
| Oziq-ovqat | 10 | 80 600 | 6 000 | 160 000 | 1 378 |
| Kitoblar | 8 | 68 750 | 38 000 | 120 000 | 2 029 |
8 qator, 11 ms
COUNT: yulduzcha, ustun va DISTINCT
Uchta COUNT bor va ular uch xil raqam beradi:
SELECT COUNT(*) AS qatorlar, COUNT(delivery_days) AS yetkazish_toldirilgan, COUNT(DISTINCT customer_id) AS nechta_mijoz, ROUND(AVG(delivery_days), 2) AS ortacha_kun FROM orders
| qatorlar | yetkazish_toldirilgan | nechta_mijoz | ortacha_kun |
|---|---|---|---|
| 20 000 | 19 235 | 2 819 | 3.99 |
1 qator, 86 ms
COUNT(*) — qatorlar soni. COUNT(ustun) — shu ustunda qiymati bor qatorlar: 20 000 buyurtmadan 19 235 tasida yetkazish kuni bor, qolgan 765 tasida NULL (bekor qilinganlarda yetkazish bo'lmaydi). COUNT(DISTINCT ...) — takrorlanmaydigan qiymatlar: 20 000 buyurtmani 2 819 mijoz bergan.
Shu yerda jim xato yashiringan: AVG(delivery_days) = 3.99 kun — bu 19 235 buyurtmaning o'rtachasi, 20 000 niki emas. Agregatlar NULL ni tashlab ketadi. Agar "har bir buyurtma bo'yicha o'rtacha" kerak bo'lsa, NULL ni ataylab to'ldiring: AVG(COALESCE(delivery_days, 0)) — lekin avval o'ylang, bekor qilingan buyurtmaning yetkazish kuni nol bo'lishi to'g'rimi yoki uni hisobdan chiqarish kerakmi (13-bob).
Shuning uchun "mijozlar soni" degan savolga javob berishdan oldin so'rang: nechta qator yoki nechta odam?
Nisbatlarning o'rtachasini olmang
Har to'lov usuli bo'yicha bekor qilish foizi:
SELECT payment_method, COUNT(*) AS buyurtma, ROUND(100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 2) AS bekor_foiz FROM orders GROUP BY payment_method ORDER BY bekor_foiz DESC
| payment_method | buyurtma | bekor_foiz |
|---|---|---|
| cash | 3 075 | 8.88 |
| card | 5 950 | 3.09 |
| payme | 5 325 | 2.87 |
| click | 5 650 | 2.74 |
4 qator, 192 ms
Endi savol: do'konning umumiy bekor qilish foizi qancha? To'rt foizning o'rtachasini olish oson, lekin noto'g'ri:
SELECT ROUND(AVG(foiz), 2) AS foizlar_ortachasi, ROUND(100.0 * SUM(bekor) / SUM(n), 2) AS togri_foiz FROM ( SELECT 100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*) AS foiz, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS bekor, COUNT(*) AS n FROM orders GROUP BY payment_method )
| foizlar_ortachasi | togri_foiz |
|---|---|
| 4.4 | 3.83 |
1 qator, 210 ms
4.40% va 3.83% — farq sezilarli. Sabab: cash ning bekor foizi eng yuqori (8.88%), lekin u eng kichik guruh (3 075 buyurtma). Oddiy o'rtacha kichik guruhga katta guruh bilan teng vazn beradi.
Qoida: nisbat har doim jami hisob / jami maxraj. Guruhlarning foizini o'rtachalash kerak bo'lsa, og'irlikli o'rtacha oling. Bu xato hisobotlarda juda ko'p uchraydi — 25-bobda yana ko'ramiz.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| GROUP BY | Har guruh uchun bitta qator. Excel pivot ning bazadagi ko'rinishi |
| COUNT turlari | COUNT(*) qator, COUNT(ustun) NULL siz qator, COUNT(DISTINCT) takrorsiz qiymat |
| Nisbatlar | Foizlarning o'rtachasi ≠ umumiy foiz; jami / jami hisoblang |
| Agregatlar | COUNT, SUM, AVG, MIN, MAX. AVG NULL ni 0 deb olmaydi, tashlab ketadi |
| Qoida | SELECT dagi har ustun yo GROUP BY da, yo agregat ichida. SQLite bu xatoni ko'rsatmaydi |
| Ko'p ustun | GROUP BY a, b → har juftlik uchun qator (long shakl) |
| Shartli sanash | SUM(CASE WHEN ... THEN 1 ELSE 0 END): SQL dagi COUNTIF; ulush uchun 100.0 * |
| HAVING | Guruhlarni natijasiga qarab filtrlash; WHERE qatorlar uchun, HAVING guruhlar uchun |
| Oy | substr(order_date, 1, 7), ISO sana shuning uchun qulay |
Talaba ishi
- Har
statusuchun o'rtachatotal_amount. Bekor qilinganlar o'rtachasi yetkazilganlardan farq qiladimi? Nega bo'lishi mumkin? - Har kategoriya uchun
order_itemsda sotilgan miqdor (SUM(quantity)) — bu so'rovni yozib ko'ring va nima yetishmayotganini ayting (kategoriya qaysi jadvalda?). 10-bobga tayyorgarlik. - Hafta kunlari bo'yicha buyurtmalar soni:
strftime('%w', order_date)0 (yakshanba) dan 6 gacha beradi. Qaysi kun eng band? - 20 dan ko'p buyurtma bergan mijozlar nechta? (HAVING, keyin tashqi COUNT yoki 11-bobdagi CTE bilan.)
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.