Asosiy qismga o'tish

Aggregation, GROUP BY, HAVING

4-bob 13 kod bloki 3388 so'z

Hozirgacha har bir query allaqachon mavjud row larni qaytardi. Aggregation boshqacha ish qiladi: u ko'p row ni bittaga siqadi. "Nechta student bor?", "O'rtacha baho qancha?", "Qaysi course eng ko'p daromad keltiradi?" — analytics aslida shunday savollarni beradi, va aynan shu bobdan boshlab SQL qidiruv tilidan analytics tiliga aylanadi.

COUNT, SUM, AVG, MIN, MAX

Deyarli hamma narsani qoplaydigan beshta function:

Function Nima qaytaradi NULL ni e'tiborsiz qoldiradimi?
COUNT(*) Row lar soni Yo'q — har bir row ni sanaydi
COUNT(col) col dagi NULL bo'lmagan qiymatlar soni Ha
SUM(col) Yig'indi Ha
AVG(col) O'rtacha arifmetik Ha
MIN(col) / MAX(col) Eng kichik / eng katta Ha
sql8 qator
SELECT
    COUNT(*)      AS total_enrollments,
    COUNT(grade)  AS graded,
    ROUND(AVG(grade), 2) AS avg_grade,
    MIN(grade)    AS lowest,
    MAX(grade)    AS highest,
    SUM(grade)    AS total_points
FROM enrollments;
total_enrollmentsgradedavg_gradelowesthighesttotal_points
2 5001 82672.9838100133 255

1 qator

1 ms

total_enrollmentsgradedavg_gradelowesthighesttotal_points

201479.43471001112

Yigirmata row kirdi, bitta row chiqdi. Aggregate ni belgilaydigan asosiy xususiyat shu: u row lar to'plamini iste'mol qilib, bitta qiymat hosil qiladi.

COUNT(*) vs COUNT(col) vs COUNT(DISTINCT col)

Deyarli bir xil ko'rinadigan va uchta har xil savolga javob beradigan uchta function. Bu intervyularda doim so'raladi.

sql5 qator
SELECT
    COUNT(*)             AS rows_total,      -- 10
    COUNT(city)          AS cities_present,  -- 9   (Sevara ning NULL i tashlandi)
    COUNT(DISTINCT city) AS unique_cities    -- 6   (NULL umuman sanalmaydi)
FROM students;
rows_totalcities_presentunique_cities
3002928

1 qator

0 ms

IfodaNatijaQaysi savolga javob beradi

COUNT(*)10Nechta student bor? COUNT(city)9Nechta student ning shahri yozilgan? COUNT(DISTINCT city)6Biz nechta har xil shaharga xizmat qilamiz?

:::tip 💡 Tayyor data-quality tekshiruvi COUNT(*) - COUNT(col) — o'sha column dagi NULL lar soni. Bitta query bilan har bir column ning to'liqligini bir vaqtda olasiz — notanish table ga birinchi qarash uchun juda foydali:

sql4 qator
SELECT COUNT(*) AS rows,
       COUNT(*) - COUNT(city)   AS missing_city,
       COUNT(*) - COUNT(gender) AS missing_gender
FROM students;

:::

Aggregate lar NULL bilan qanday ishlaydi

COUNT(*) dan boshqa har bir aggregate NULL larni indamay tashlab ketadi. Odatda bu to'g'ri — lekin u maxrajni o'zgartiradi, va talabalar buni bilishi shart.

20 ta enrollment ustida AVG(grade)mavjud baholar : 14 ta qiymat, yig'indisi 1112 yo'q baholar : 6 ta NULL AVG(grade) = 1112 / 14 = 79.43 ← NULL lar butunlay tashlandi SUM(grade)/COUNT() = 1112 / 20 = 55.60* ← NULL lar nol deb hisoblandi

Bir xil data, ikkita javob, ikkalasi ham asoslanadi — lekin ular har xil narsani anglatadi. Avval biznes savolini so'rang: "bizda bor baholarning o'rtachasi" (79.43) mi, yoki "har bir enrollment ga to'g'ri keladigan o'rtacha ball, baholanmagani hech narsa deb hisoblanadi" (55.60) mi? Birinchisi deyarli doim kerak bo'lgan narsa; ikkinchisi deyarli doim xato.

⚠️ Hech narsaning SUM i 0 emas, NULL

SELECT SUM(grade) FROM enrollments WHERE status = 'nonexistent'; 0 emas, NULL qaytaradi — qo'shadigan hech narsa bo'lmagan. Dashboard ga raqam kerak bo'lsa, o'rang: COALESCE(SUM(grade), 0). COUNT bu yerda istisno: u bo'sh to'plam uchun to'g'ri 0 qaytaradi.

GROUP BY

GROUP BY "butun table uchun bitta son" g'oyasini olib, uni har bir guruhga qo'llaydi. "O'rtacha baho" o'rniga "har bir course uchun o'rtacha baho" olasiz.

1. row lar 2. guruhlangan 3. guruhga bitta row course 1 · 92 course 2 · 88 course 1 · 75 course 2 · 95 course 1 · 58 course 2 · 70 course 1 · 47 course 2 · 100 course 2 · 83 course 1 9275 5847 course 2 8895 70100 83 course 1 COUNT 4 · AVG 68.00 course 2 COUNT 5 · AVG 87.20
sql9 qator
SELECT
    course_id,
    COUNT(*)             AS enrollments,
    COUNT(grade)         AS graded,
    ROUND(AVG(grade), 2) AS avg_grade,
    MAX(grade)           AS best
FROM enrollments
GROUP BY course_id
ORDER BY course_id;
course_idenrollmentsgradedavg_gradebest
119313279.15100
218614674.2796
317412863.5986
416711974.18100
516712569.3794
617411875.1794
716512279.89100
817013175.15100
916111876.197
1017914773.52100
1120014068.3492
1318712773.8697
1418913574.65100
1518813865.5991

14 qator

1 ms

course_idenrollmentsgradedavg_gradebest

15468.092 25587.2100 33276.589 43280.081 54191.091

:::gotcha ⚠️ 6-course yo'q — 5-course esa chalg'ituvchi Bitta natija jadvalida ikkita saboq:

  • "Deep Learning" (6-course) umuman ko'rinmaydi. GROUP BY faqat mavjud row larni guruhlay oladi, 6-course da esa enrollment yo'q. Shu tarzda qurilgan "course lar hisoboti" hech kim yozilmagan har bir course ni indamay tashlab ketadi — holbuki rahbariyat aynan shu haqda eshitmoqchi bo'lgan. Yechim — courses dan LEFT JOIN (5-bob).
  • 5-course to'rtta enrollment dan bitta baho asosida 91.0 ko'rsatyapti. Kichik namunadagi o'rtachalar ishonchli ko'rinadi, lekin unday emas. Hisobotda AVG yoniga doim COUNT qo'ying, shunda o'quvchi son qanchalik qimmatga ega ekanini ko'radi. :::

Matnli column bo'yicha guruhlash ham xuddi shunday ishlaydi:

sql4 qator
SELECT status, COUNT(*) AS n
FROM enrollments
GROUP BY status
ORDER BY n DESC;
statusn
completed1 634
active472
dropped202
failed192

4 qator

1 ms

:::tip 💡 Bu — value_counts() pandas dan kelgan talabalar buni darrov taniydi: GROUP BY col + COUNT(*) aynan df['col'].value_counts(). Bu bog'lanishni erta ko'rsatish butun bobni tushunarli qiladi. :::

NULL esa o'z guruhini hosil qiladi — SQL barcha NULL larni teng deb hisoblaydigan yagona joy:

sql5 qator
SELECT COALESCE(city, '(unknown)') AS city, COUNT(*) AS students
FROM students
GROUP BY city
ORDER BY students DESC, city;
-- Tashkent 3, Samarkand 2, keyin Andijan/Bukhara/Fergana/Namangan/(unknown) 1 tadan
citystudents
Tashkent82
Samarkand40
Bukhara38
Fergana36
Namangan28
Andijan24
Karshi22
Nukus22
(unknown)8

9 qator

0 ms

GROUP BY ning oltin qoidasi :::info 📌 Qoida SELECT dagi har bir column yo GROUP BY da bo'lishi, yo aggregate function ichida bo'lishi shart. Boshqasiga ruxsat yo'q. :::

Sababi yuqoridagi diagrammaga qarasangiz oddiy. Guruhlashdan keyin 1-course to'rtta kirish row idan qurilgan bitta chiqish row i. student_id so'rasangiz, savolning javobi yo'q — u yerda to'rtta har xil student bor edi. Qaysi birini chop etsin?

python7 qator
-- XATO: student_id na guruhlangan, na aggregate qilingan
SELECT course_id, student_id, AVG(grade)
FROM enrollments
GROUP BY course_id;

-- PostgreSQL: XATO — "student_id" column i GROUP BY da bo'lishi kerak
-- SQLite/MySQL: xato YO'Q — ular tasodifiy qiymat o'ylab topadi. Bu ancha xavfli.
course_idstudent_idAVG(grade)
15979.152
224174.267
38563.586
421174.176
528169.368
62175.169
713079.893
83375.145
921576.102
1010273.524
1121668.343
1322173.858
1418874.652
159565.594

14 qator

1 ms

:::gotcha ⚠️ SQLite va MySQL bunga yo'l qo'yadi PostgreSQL va SQL Server query ni rad etadi. SQLite va eski MySQL esa hech qanday ogohlantirishsiz qandaydir student_id ni qaytaradi — engine oxirgi tegib o'tgan row nikini. Talabalar keyin real ko'rinadigan, lekin ma'nosiz qiymatlar bilan to'la hisobotni topshiradi. Engine nimaga yo'l qo'yishidan qat'i nazar, qoidani mutlaq deb o'rgating. :::

Aslida kerak bo'lgan narsani olishning uchta to'g'ri yo'li:

python9 qator
-- (a) uni ham guruhlash — har bir (course, student) uchun bitta row
SELECT course_id, student_id, AVG(grade) FROM enrollments
GROUP BY course_id, student_id;

-- (b) uni aggregate qilish — masalan nechta har xil student
SELECT course_id, COUNT(DISTINCT student_id) AS students, AVG(grade) FROM enrollments
GROUP BY course_id;

-- (c) aslida "har bir course dagi eng yaxshi student" kerak edi → window function, 7-bob

1-natija

course_idstudent_idAVG(grade)
1377
1684
1866
11075
111NaN
11293
113NaN
11477
11969
12077
12176
12277
12374.333
124100
12696

… 1866 qatordan birinchi 15 tasi

1866 qator

2-natija

course_idstudentsAVG(grade)
113479.152
213474.267
312463.586
412174.176
513069.368
613375.169
712979.893
812775.145
912976.102
1013773.524
1115268.343
1313673.858
1414874.652
1513265.594

14 qator

3 ms

Bir nechta column bo'yicha guruhlash Bir nechta column sanab o'tish har bir birikma uchun bitta guruh hosil qiladi. Chiqish row lari soni nazariy juftliklar soniga emas, haqiqatan uchraydigan birikmalar soniga teng.

python8 qator
-- "Har bir shaharda qaysi jinsdan nechta student bor?"
SELECT
    COALESCE(city, '(unknown)') AS city,
    gender,
    COUNT(*) AS students
FROM students
GROUP BY city, gender
ORDER BY city, gender;
citygenderstudents
(unknown)F2
(unknown)M6
AndijanF10
AndijanM14
BukharaF18
BukharaM20
FerganaF16
FerganaM20
KarshiF11
KarshiM11
NamanganF15
NamanganM13
NukusF15
NukusM7
SamarkandF20

… 18 qatordan birinchi 15 tasi

18 qator

0 ms

💡 Uni haqiqiy cross-tab ga aylantirish

gender ni row emas, column qilib olish — ya'ni pivot table — uchun SUM ni CASE WHEN bilan qo'shing (8-bob):

sql run SELECT COALESCE(city,'(unknown)') AS city, SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male, SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female, COUNT(*) AS total FROM students GROUP BY city;

Bu SUM(CASE WHEN ...) pattern i real analytics ishida eng ko'p ishlatiladigan konstruksiyalardan biri. Mashq qildirishga arziydi.

HAVING va WHERE

1-bobdagi qoida, endi sababi ko'rinib turibdi: WHERE guruhlashdan oldin row larni, HAVING guruhlashdan keyin guruhlarni filtrlaydi.

Har biri qayerda turadiFROM → row larni oladi WHERE → row larni tashlaydi (aggregate yo'q — hali guruh mavjud emas) GROUP BY → guruhlarni quradi HAVING → butun guruhlarni tashlaydi (aggregate bor — guruhlar mavjud) SELECT → chiqishni quradi ORDER BY → saralaydi
python10 qator
-- "Faqat tugatilgan enrollment lar orasida qaysi course larning o'rtachasi 80 dan yuqori?"
SELECT
    course_id,
    COUNT(*)             AS completed_count,
    ROUND(AVG(grade), 2) AS avg_grade
FROM enrollments
WHERE status = 'completed'      -- ROW larni filtrlaydi: active/failed/dropped tashlanadi
GROUP BY course_id
HAVING AVG(grade) > 80          -- GURUH larni filtrlaydi: past o'rtachali course lar tashlanadi
ORDER BY avg_grade DESC;
course_idcompleted_countavg_grade
712080.25

1 qator

1 ms

course_idcompleted_countavg_grade

5191.0 2587.2 1283.5

4-course yo'q: uning tugatilgan baholari 81 va 79, o'rtachasi aynan 80.0, > 80 esa qat'iy. 3-course ning o'rtachasi 76.5. Shuningdek e'tibor bering: 1-course ning o'rtachasi 68.0 dan 83.5 ga ko'tarildi — chunki WHERE status = 'completed' avval ikkita yiqilgan bahoni olib tashladi. WHERE ni o'zgartirish guruhlar ichidagi narsani o'zgartiradi.

⚠️ Row shartini HAVING ga qo'ymang

Bu ikkalasi bir xil javob beradi:

WHERE status = 'completed' GROUP BY course_id -- tez GROUP BY course_id HAVING status = 'completed' -- sekin / noto'g'ri Ikkinchisi har bir row dan har bir guruhni quradi va shundan keyingina ishni tashlab yuboradi — qat'iy database da esa u shunchaki xato, chunki status guruhlanmagan. Iloji boricha erta filtrlang. Agar shartda aggregate ishtirok etmasa, u WHERE ga tegishli.

💡 Foydali bitta qator: dublikatlarni topish

sql run SELECT student_id, course_id, COUNT(*) AS times FROM enrollments GROUP BY student_id, course_id HAVING COUNT(*) > 1;

Qaytgan har bir row — bitta course ga ikki marta yozilgan student. Bu GROUP BY ... HAVING COUNT(*) > 1 pattern i dublikat topishning standart query si va juda ko'p uchraydigan intervyu topshirig'i. Bizning toza data da u hech narsa qaytarmaydi.

ROUND va butun bo'lish tuzog'i

2-bobdagi tuzoq shu yerda qaytadi, va aggregation da u haqiqiy zarar keltiradi.

python11 qator
-- "Enrollment larning necha foizi tugatilgan?"

-- XATO: butun arifmetikada 12 / 20 = 0
SELECT COUNT(*) / (SELECT COUNT(*) FROM enrollments) * 100 AS pct
FROM enrollments WHERE status = 'completed';
-- 0 qaytaradi

-- TO'G'RI: bo'lishdan oldin * 1.0 bilan kasrga majburlash
SELECT ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM enrollments), 1) AS pct
FROM enrollments WHERE status = 'completed';
-- 60.0 qaytaradi

1-natija

pct
0

1 qator

2-natija

pct
65.4

1 qator

1 ms

"Shartga mos row larning foizi" uchun ixcham idiom, yodlashga arziydi:

Guruhning shartga mos foiziROUND(100.0 * SUM(CASE WHEN shart THEN 1 ELSE 0 END) / COUNT(*), 1)
python9 qator
-- har bir course bo'yicha o'tish foizi, bitta query da
SELECT
    course_id,
    COUNT(*) AS graded,
    ROUND(100.0 * SUM(CASE WHEN grade >= 60 THEN 1 ELSE 0 END) / COUNT(*), 1) AS pass_rate_pct
FROM enrollments
WHERE grade IS NOT NULL
GROUP BY course_id
ORDER BY pass_rate_pct DESC;
course_idgradedpass_rate_pct
113298.5
712298.4
1312796.9
611896.6
911894.9
1014794.6
411993.3
1413592.6
214691.8
813190.1
512588
1114082.9
1513873.2
312863.3

14 qator

1 ms

course_idgradedpass_rate_pct

25100.0 32100.0 42100.0 51100.0 1450.0

WHERE grade IS NOT NULL ga e'tibor bering: usiz baholanmagan enrollment lar maxrajda qolib, har bir o'tish foizini pastga tortadi. Maxrajga nima kirishini hal qilish — aslida asosiy analitik ish; SQL esa oson qismi.

Mashqlar to'plami

python41 qator
-- 1. Har bir shaharda nechta student? (noma'lum aniq ko'rsatilsin)
SELECT COALESCE(city, '(unknown)') AS city, COUNT(*) AS n
FROM students GROUP BY city ORDER BY n DESC;

-- 2. Kategoriya bo'yicha course larning o'rtacha narxi
SELECT category, COUNT(*) AS courses, ROUND(AVG(price)) AS avg_price
FROM courses GROUP BY category ORDER BY avg_price DESC;

-- 3. Har bir student ning o'rtacha bahosi, eng yaxshilari birinchi
SELECT student_id,
       COUNT(grade)         AS graded,
       ROUND(AVG(grade), 1) AS avg_grade
FROM enrollments
GROUP BY student_id
HAVING COUNT(grade) > 0
ORDER BY avg_grade DESC;

-- 4. 3 tadan ko'p enrollment i bor course lar
SELECT course_id, COUNT(*) AS n
FROM enrollments GROUP BY course_id HAVING COUNT(*) > 3;

-- 5. Oylar bo'yicha enrollment soni
SELECT strftime('%Y-%m', enrolled_at) AS month, COUNT(*) AS n
FROM enrollments GROUP BY month ORDER BY month;

-- 6. Har bir course bo'yicha tugatish foizi
SELECT course_id,
       COUNT(*) AS total,
       SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed,
       ROUND(100.0 * SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct
FROM enrollments GROUP BY course_id ORDER BY pct DESC;

-- 7. Data-quality tekshiruvi: column lardagi NULL lar
SELECT COUNT(*) AS rows_total,
       COUNT(*) - COUNT(city)       AS missing_city,
       COUNT(*) - COUNT(birth_date) AS missing_birth_date
FROM students;

-- 8. Dublikat enrollment bormi? (hech narsa qaytmasligi kerak)
SELECT student_id, course_id, COUNT(*) AS times
FROM enrollments GROUP BY student_id, course_id HAVING COUNT(*) > 1;

1-natija

cityn
Tashkent82
Samarkand40
Bukhara38
Fergana36
Namangan28
Andijan24
Nukus22
Karshi22
(unknown)8

9 qator

2-natija

categorycoursesavg_price
Business31 366 667
Math31 216 667
Data41 175 000
Programming31 166 667
Design2900 000

5 qator

3-natija

student_idgradedavg_grade
197387.3
1386.3
116284
107284
6882.8
270282.5
176482.3
103582.2
276481.8
248981.6
247381
130681
199480.8
167680.8
298380.3

… 300 qatordan birinchi 15 tasi

300 qator

4-natija

course_idn
1193
2186
3174
4167
5167
6174
7165
8170
9161
10179
11200
13187
14189
15188

14 qator

5-natija

monthn
2025-021 077
2025-03131
2025-091 188
2025-10104

4 qator

6-natija

course_idtotalcompletedpct
1017913977.7
716512072.7
218613472
916111269.6
817011869.4
119313067.4
416711166.5
1418912566.1
516711065.9
1318712365.8
617411465.5
1120011658
1518810153.7
31748146.6

14 qator

7-natija

rows_totalmissing_citymissing_birth_date
30080

1 qator

8-natija

student_idcourse_idtimes
132
1112
2112
362
3112
472
4152
522
552
642
6142
792
812
10142
1172

… 514 qatordan birinchi 15 tasi

514 qator

5 ms

Intervyu savollari

S: COUNT(*), COUNT(column) va COUNT(1) farqi?

COUNT(*) row larni sanaydi, hatto butunlay NULL row larni ham. COUNT(column) faqat o'sha column NULL bo'lmagan row larni sanaydi. COUNT(1) zamonaviy engine larda COUNT(*) bilan aynan bir xil — uning tezroq degan eski qarash afsona.

S: AVG NULL larni hisobga oladimi?

Yo'q. U NULL bo'lmagan qiymatlar soniga bo'ladi. NULL lar nol deb hisoblanishini xohlasangiz, buni aniq yozishingiz kerak: AVG(COALESCE(grade, 0)) — lekin biznes haqiqatan shuni nazarda tutayaptimi, so'rang.

S: Nol row ustida SUM nima qaytaradi?

0 emas, NULL. Raqam kerak bo'lsa, COALESCE(SUM(x), 0) ga o'rang. COUNT esa to'g'ri 0 qaytaradi.

S: WHERE va HAVING?

WHERE GROUP BY dan oldin row larni filtrlaydi va aggregate ishlata olmaydi. HAVING GROUP BY dan keyin guruhlarni filtrlaydi va ishlata oladi. Aggregate talab qilmaydigan har qanday shart WHERE ga tegishli, chunki ertaroq filtrlash kamroq ish degani.

S: GROUP BY siz HAVING ishlatsa bo'ladimi?

Ha. U holda butun table bitta guruh deb qaraladi: SELECT AVG(grade) FROM enrollments HAVING AVG(grade) > 70; bitta row yoki hech narsa qaytaradi.

S: Nega SELECT course_id, student_id, AVG(grade) ... GROUP BY course_id noto'g'ri?

Chunki guruhda ko'p har xil student_id bor, chop etish uchun esa faqat bittasi mumkin. PostgreSQL xato beradi; SQLite va MySQL tasodifiy qiymat qaytaradi, bu esa battarroq — natija to'g'ridek ko'rinadi, lekin ma'nosiz.

S: Dublikat row larni qanday topasiz?

Unique bo'lishi kerak bo'lgan column lar bo'yicha GROUP BY, keyin HAVING COUNT(*) > 1.

S: Nega "mahsulot bo'yicha savdo" hisobotida ba'zi mahsulotlar yo'q bo'lishi mumkin?

Chunki GROUP BY faqat mavjud row larni ko'radi. Savdosi nol bo'lgan mahsulotning guruhlanadigan row i yo'q. Uni 0 bilan hisobotda qoldirish uchun products table idan LEFT JOIN kerak.

Xulosa

  • Aggregate lar ko'p row ni bittaga siqadi: COUNT, SUM, AVG, MIN, MAX.
  • COUNT(*) = row lar · COUNT(col) = NULL bo'lmagan qiymatlar · COUNT(DISTINCT col) = har xil NULL bo'lmagan qiymatlar. Ularning farqi — bepul NULL tekshiruvi.
  • COUNT(*) dan boshqa har bir aggregate NULL larni tashlaydi, bu esa AVG ning maxrajini o'zgartiradi. Nol row ustida SUM 0 emas, NULL.
  • GROUP BY har bir guruh uchun bitta chiqish row i yasaydi. U row i yo'q guruhlarni ko'rsata olmaydi — buning uchun LEFT JOIN kerak.
  • Oltin qoida: tanlangan har bir column yo GROUP BY da, yo aggregate ichida. SQLite/MySQL buzishga yo'l qo'yadi; buzmang.
  • WHERE guruhlashdan oldin, HAVING keyin. Iloji boricha erta filtrlang.
  • GROUP BY ... HAVING COUNT(*) > 1 — standart dublikat topuvchi.
  • * 1.0 yoki * 100.0 bilan kasrga majburlang; SUM(CASE WHEN ... THEN 1 ELSE 0 END) — foiz idiomi.
  • AVG yoniga doim COUNT ni chiqaring, shunda hech kim bitta qiymatdan yasalgan o'rtachaga ishonmaydi.

Keyingi: 5-bob — JOIN lar. Hozirgacha har bir hisobot bitta table ichida qamalgan edi; JOIN lar esa bitta natijada "student ismi va course nomi" deyish imkonini beradi.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar