التجميع في SQL: COUNT و SUM و GROUP BY
المدير لا يريد قراءة آلاف الصفوف، بل يريد إجابات: كم عدد الطلاب؟ ما متوسط الدرجات؟ أي مدينة فيها أكبر عدد طلاب؟ أي كورس هو الأكثر طلباً؟ هذه الأسئلة يجيب عنها التجميع (Aggregation): تلخيص صفوف كثيرة في أرقام قليلة مفيدة. وهذا الدرس الأخير في الكورس يحوّلك من شخص يقرأ البيانات إلى شخص يحللها.
سنعمل على جدول الطلاب الأصلي: سارة (القاهرة، 92)، وعمر (الإسكندرية، 78)، ومنى (طنطا، 85)، ويوسف (القاهرة، 64)، وليلى (أسيوط، بلا درجة)، وأحمد (الإسكندرية، 71).
العدّ: COUNT
Section titled “العدّ: COUNT”SELECT COUNT(*) AS total FROM students;الناتج: 6
SELECT COUNT(score) AS with_score FROM students;الناتج: 5
لاحظ الفرق المهم:
COUNT(*): يعدّ كل الصفوف.COUNT(score): يعدّ الصفوف التي فيها قيمة فيscoreفقط، ويتجاهلNULL. ليلى لم تُعدّ.
دوال التجميع الأساسية
Section titled “دوال التجميع الأساسية”SELECT SUM(score) AS total, AVG(score) AS average, MIN(score) AS lowest, MAX(score) AS highestFROM students;الناتج:
| total | average | lowest | highest |
|---|---|---|---|
| 390 | 78.0 | 64 | 92 |
| الدالة | وظيفتها |
|---|---|
COUNT |
العدد |
SUM |
المجموع |
AVG |
المتوسط |
MIN و MAX |
أقل وأكبر قيمة |
انتبه للمتوسط: 390 ÷ 5 = 78. القسمة على 5 وليس 6! لأن دوال التجميع كلها تتجاهل NULL، فلم تُحسب ليلى أصلاً. وهذا منطقي: درجتها مجهولة، وليست صفراً. ولو عاملناها صفراً لظلمناها وأنزلنا المتوسط.
ولتقريب النتيجة: ROUND(AVG(score), 1) يقرّب لرقم عشري واحد.
GROUP BY: التجميع حسب مجموعات
Section titled “GROUP BY: التجميع حسب مجموعات”كم طالباً في كل مدينة؟
SELECT city, COUNT(*) AS students_countFROM studentsGROUP BY cityORDER BY students_count DESC, city;الناتج:
| city | students_count |
|---|---|
| الإسكندرية | 2 |
| القاهرة | 2 |
| أسيوط | 1 |
| طنطا | 1 |
GROUP BY city يقسّم الصفوف إلى مجموعات: كل مدينة مجموعة. ثم تُطبَّق دالة التجميع على كل مجموعة منفصلة.
تخيّل معلماً يفرز أوراق الامتحان: يضع أوراق كل فصل في كومة مستقلة (GROUP BY)، ثم يعدّ أوراق كل كومة أو يحسب متوسطها (COUNT و AVG).
متوسط الدرجات لكل مدينة:
SELECT city, ROUND(AVG(score), 1) AS avg_scoreFROM studentsGROUP BY cityORDER BY avg_score DESC;الناتج:
| city | avg_score |
|---|---|
| طنطا | 85.0 |
| القاهرة | 78.0 |
| الإسكندرية | 74.5 |
| أسيوط | NULL |
أسيوط متوسطها NULL، لأن طالبتها الوحيدة بلا درجة.
HAVING: تصفية المجموعات
Section titled “HAVING: تصفية المجموعات”المدن التي فيها طالبان أو أكثر فقط:
SELECT city, COUNT(*) AS students_countFROM studentsGROUP BY cityHAVING COUNT(*) >= 2ORDER BY city;الناتج:
| city | students_count |
|---|---|
| الإسكندرية | 2 |
| القاهرة | 2 |
ما الفرق بين WHERE و HAVING؟
WHEREتصفّي الصفوف قبل التجميع.HAVINGتصفّي المجموعات بعد التجميع، ولذلك تستطيع استخدامCOUNTوAVGفيها.
مثلاً: WHERE age >= 14 تستبعد الطلاب الصغار قبل العد، و HAVING COUNT(*) >= 2 تستبعد المدن الصغيرة بعد العد.
ترتيب الاستعلام الكامل الآن: SELECT ← FROM ← WHERE ← GROUP BY ← HAVING ← ORDER BY ← LIMIT.
التجميع مع JOIN
Section titled “التجميع مع JOIN”ما أكثر الكورسات طلباً؟ نجمع ما تعلمناه في الدرس السابق مع هذا الدرس، على جداول الكورسات والاشتراكات:
SELECT c.title, COUNT(e.student_id) AS enrolledFROM courses cLEFT JOIN enrollments e ON e.course_id = c.idGROUP BY c.titleORDER BY enrolled DESC;الناتج:
| title | enrolled |
|---|---|
| Python | 3 |
| SQL | 2 |
| HTML | 1 |
استخدمنا LEFT JOIN حتى يظهر الكورس حتى لو لم يشترك فيه أحد (بعدد صفر)، و COUNT(e.student_id) حتى لا يُعدّ صف الـ NULL الناتج عن عدم وجود اشتراكات.
هذا النوع من الاستعلامات هو ما يبني لوحات التحكم (Dashboards) والتقارير في الشركات الحقيقية.
جرّب بنفسك
Section titled “جرّب بنفسك”- احسب عدد الطلاب في كل عمر.
- اعرض أعلى درجة في كل مدينة.
- اعرض المدن التي متوسط درجاتها أكبر من 75.
- احسب عدد الكورسات التي اشترك فيها كل طالب، واعرض الطلاب المشتركين في كورسين أو أكثر فقط.
- تحدٍّ: اعرض اسم الطالب صاحب أعلى درجة (تلميح:
ORDER BYمعLIMIT، أو استعلام داخل استعلام).
أخطاء شائعة
Section titled “أخطاء شائعة”- عمود ليس في GROUP BY ولا داخل دالة تجميع: نتيجة مرفوضة أو مضللة.
- استخدام WHERE مع دوال التجميع:
WHERE COUNT(*) > 2خطأ. للمجموعات استخدمHAVING. - نسيان أن التجميع يتجاهل NULL: قد يختلف
COUNT(*)عنCOUNT(column)، ويُحسب المتوسط على القيم الموجودة فقط.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما الفرق بين COUNT(*) و COUNT(score)؟
COUNT(*) يعد الصفوف كلها، أما COUNT(عمود) فيعد القيم الموجودة فقط في هذا العمود.
سؤال
أين تضع شرطاً مثل «المدن التي فيها أكثر من 10 طلاب»؟
الشرط على نتيجة دالة تجميع مثل COUNT يكون في HAVING، لأنها تصفّي المجموعات بعد التجميع.
الخلاصة
Section titled “الخلاصة”COUNTوSUMوAVGوMINوMAXتلخص صفوفاً كثيرة في قيمة واحدة.- دوال التجميع تتجاهل
NULL، وCOUNT(*)وحده يعد كل الصفوف. GROUP BYيقسم الصفوف لمجموعات، والتجميع يُطبَّق على كل مجموعة.WHEREتصفّي الصفوف قبل التجميع، وHAVINGتصفّي المجموعات بعده.