تخطَّ إلى المحتوى

التجميع في SQL: COUNT و SUM و GROUP BY

الدرس 8 من 12 · SQL

المدير لا يريد قراءة آلاف الصفوف، بل يريد إجابات: كم عدد الطلاب؟ ما متوسط الدرجات؟ أي مدينة فيها أكبر عدد طلاب؟ أي كورس هو الأكثر طلباً؟ هذه الأسئلة يجيب عنها التجميع (Aggregation): تلخيص صفوف كثيرة في أرقام قليلة مفيدة. وهذا الدرس الأخير في الكورس يحوّلك من شخص يقرأ البيانات إلى شخص يحللها.

سنعمل على جدول الطلاب الأصلي: سارة (القاهرة، 92)، وعمر (الإسكندرية، 78)، ومنى (طنطا، 85)، ويوسف (القاهرة، 64)، وليلى (أسيوط، بلا درجة)، وأحمد (الإسكندرية، 71).

SELECT COUNT(*) AS total FROM students;

الناتج: 6

SELECT COUNT(score) AS with_score FROM students;

الناتج: 5

لاحظ الفرق المهم:

  • COUNT(*): يعدّ كل الصفوف.
  • COUNT(score): يعدّ الصفوف التي فيها قيمة في score فقط، ويتجاهل NULL. ليلى لم تُعدّ.
SELECT SUM(score) AS total,
AVG(score) AS average,
MIN(score) AS lowest,
MAX(score) AS highest
FROM 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_count
FROM students
GROUP BY city
ORDER 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_score
FROM students
GROUP BY city
ORDER BY avg_score DESC;

الناتج:

city avg_score
طنطا 85.0
القاهرة 78.0
الإسكندرية 74.5
أسيوط NULL

أسيوط متوسطها NULL، لأن طالبتها الوحيدة بلا درجة.

المدن التي فيها طالبان أو أكثر فقط:

SELECT city, COUNT(*) AS students_count
FROM students
GROUP BY city
HAVING COUNT(*) >= 2
ORDER 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.

ما أكثر الكورسات طلباً؟ نجمع ما تعلمناه في الدرس السابق مع هذا الدرس، على جداول الكورسات والاشتراكات:

SELECT c.title, COUNT(e.student_id) AS enrolled
FROM courses c
LEFT JOIN enrollments e ON e.course_id = c.id
GROUP BY c.title
ORDER BY enrolled DESC;

الناتج:

title enrolled
Python 3
SQL 2
HTML 1

استخدمنا LEFT JOIN حتى يظهر الكورس حتى لو لم يشترك فيه أحد (بعدد صفر)، و COUNT(e.student_id) حتى لا يُعدّ صف الـ NULL الناتج عن عدم وجود اشتراكات.

هذا النوع من الاستعلامات هو ما يبني لوحات التحكم (Dashboards) والتقارير في الشركات الحقيقية.

  1. احسب عدد الطلاب في كل عمر.
  2. اعرض أعلى درجة في كل مدينة.
  3. اعرض المدن التي متوسط درجاتها أكبر من 75.
  4. احسب عدد الكورسات التي اشترك فيها كل طالب، واعرض الطلاب المشتركين في كورسين أو أكثر فقط.
  5. تحدٍّ: اعرض اسم الطالب صاحب أعلى درجة (تلميح: ORDER BY مع LIMIT، أو استعلام داخل استعلام).
  • عمود ليس في GROUP BY ولا داخل دالة تجميع: نتيجة مرفوضة أو مضللة.
  • استخدام WHERE مع دوال التجميع: WHERE COUNT(*) > 2 خطأ. للمجموعات استخدم HAVING.
  • نسيان أن التجميع يتجاهل NULL: قد يختلف COUNT(*) عن COUNT(column)، ويُحسب المتوسط على القيم الموجودة فقط.

سؤال

ما الفرق بين COUNT(*) و COUNT(score)؟

سؤال

أين تضع شرطاً مثل «المدن التي فيها أكثر من 10 طلاب»؟

  • COUNT و SUM و AVG و MIN و MAX تلخص صفوفاً كثيرة في قيمة واحدة.
  • دوال التجميع تتجاهل NULL، و COUNT(*) وحده يعد كل الصفوف.
  • GROUP BY يقسم الصفوف لمجموعات، والتجميع يُطبَّق على كل مجموعة.
  • WHERE تصفّي الصفوف قبل التجميع، و HAVING تصفّي المجموعات بعده.