التجميع: GROUP BY و HAVING
من آلاف الصفوف إلى أرقام تساعد على القرار. ⏱ نحو 30 دقيقة.
- تلخّص البيانات بـ
COUNTوSUMوAVGوMINوMAX. - تجمع الصفوف بـ
GROUP BYوتحسب لكل مجموعة. - تفرّق بين
WHEREوHAVING. - تكتب استعلامًا كاملًا بترتيب أجزائه الصحيح.
١دوال التجميع: من صفوف كثيرة إلى رقم واحد
المدير لا يريد قائمة 12 منتجًا؛ يريد «كم منتجًا لدينا؟ وما متوسط الأسعار؟». دوال التجميع Aggregate Functions تلخّص صفوفًا كثيرة في قيمة واحدة:
| الدالة | تحسب | |
|---|---|---|
COUNT(*) | عدد الصفوف | |
COUNT(col) | عدد القيم غير الفارغة في العمود | تتجاهل NULL |
SUM(col) | المجموع | |
AVG(col) | المتوسط | تتجاهل NULL |
MIN(col) و MAX(col) | الأصغر والأكبر | تعمل مع النصوص والتواريخ أيضًا |
SELECT COUNT(*) AS products, ROUND(AVG(price), 2) AS avg_price, MIN(price) AS cheapest, MAX(price) AS dearest, SUM(price * stock) AS stock_valueFROM products;
| products | avg_price | cheapest | dearest | stock_value |
|---|---|---|---|---|
| 12 | 60.67 | 12 | 150 | 13845 |
SELECT COUNT(*) AS customers, COUNT(phone) AS with_phone, COUNT(city) AS with_cityFROM customers;
| customers | with_phone | with_city |
|---|---|---|
| 10 | 9 | 9 |
COUNT(*) عدّ كل العملاء (10)، و COUNT(phone) تجاهل عبدالله الذي لا جوال له.
٢التجميع حسب مجموعة: GROUP BY
والسؤال الأقوى: «كم منتجًا في كل تصنيف؟». GROUP BY تقسم الصفوف إلى مجموعات حسب قيمة العمود، ثم تحسب دالة التجميع لكل مجموعة:
SELECT category, COUNT(*) AS products, ROUND(AVG(price), 1) AS avg_priceFROM productsGROUP BY category;
| category | products | avg_price |
|---|---|---|
| أدوات | 3 | 78.3 |
| بهارات | 2 | 41.5 |
| تمور | 2 | 67.5 |
| عسل | 2 | 105 |
| مشروبات | 3 | 21.7 |
ومن جدول أصناف الطلبات: كم قطعة بيعت من كل منتج؟ وكم كسبنا منه؟
SELECT product_id, SUM(qty) AS units, SUM(qty * price) AS revenueFROM order_itemsGROUP BY product_idORDER BY revenue DESC;
| product_id | units | revenue |
|---|---|---|
| 3 | 4 | 360 |
| 5 | 2 | 300 |
| 1 | 7 | 245 |
| 6 | 3 | 180 |
| 7 | 2 | 150 |
| 12 | 1 | 120 |
| 2 | 5 | 90 |
| 9 | 3 | 84 |
| 8 | 2 | 80 |
| 11 | 6 | 72 |
| 10 | 1 | 55 |
٣تصفية المجموعات: HAVING
WHERE تصفّي الصفوف قبل التجميع، و HAVING تصفّي المجموعات بعد التجميع، فتقبل دوال التجميع:
-- التصنيفات التي فيها 3 منتجات أو أكثرSELECT category, COUNT(*) AS productsFROM productsGROUP BY categoryHAVING COUNT(*) >= 3;
| category | products |
|---|---|
| أدوات | 3 |
| مشروبات | 3 |
-- العملاء الذين طلبوا أكثر من مرة، من الطلبات غير الملغاة فقطSELECT customer_id, COUNT(*) AS orders_countFROM ordersWHERE status <> 'ملغى'GROUP BY customer_idHAVING COUNT(*) > 1;
| customer_id | orders_count |
|---|---|
| 1 | 3 |
| 2 | 2 |
| 3 | 2 |
WHERE حذفت الطلبات الملغاة أولًا، ثم جُمعت، ثم HAVING أبقت من له أكثر من طلب.
SELECT category, COUNT(*)FROM productsWHERE COUNT(*) >= 3GROUP BY category;
SELECT category, COUNT(*)FROM productsGROUP BY categoryHAVING COUNT(*) >= 3;
| category | COUNT(*) |
|---|---|
| أدوات | 3 |
| مشروبات | 3 |
٤الترتيب الكامل للاستعلام
| الكتابة | التنفيذ | الوظيفة |
|---|---|---|
SELECT | 5 | الأعمدة والحسابات |
FROM | 1 | الجدول |
WHERE | 2 | تصفية الصفوف |
GROUP BY | 3 | تكوين المجموعات |
HAVING | 4 | تصفية المجموعات |
ORDER BY | 6 | الترتيب |
LIMIT | 7 | العدد |
توقّع وتحقّق
عدد الصفوف كلها:
NULL.تصفية المجموعات بعد التجميع:
GROUP BY.أيهما يُنفَّذ أولًا؟
COUNT(phone) حين يكون جوال واحد NULL من 10:
NULL.الخلاصة
- دوال التجميع تلخّص صفوفًا كثيرة في قيمة، وتتجاهل
NULL(إلاCOUNT(*)). GROUP BYعمود: نتيجة لكل قيمة مختلفة.- كل عمود في
SELECTإما فيGROUP BYوإما داخل دالة تجميع. WHEREللصفوف قبل التجميع، وHAVINGللمجموعات بعده.SELECT…FROM…WHERE…GROUP BY…HAVING…ORDER BY…LIMIT
تحدٍّ برمجي: تقرير التصنيفات
متوسطتقرير للإدارة: لكل تصنيف عدد منتجاته ومتوسط أسعارها، للتصنيفات التي فيها أكثر من منتجين فقط، مرتبًا من الأعلى متوسطًا.
- الأعمدة:
category، ثم عدد المنتجات، ثم متوسط السعر مقربًا لمنزلة عشرية واحدة. - التصنيفات التي فيها أكثر من منتجين فقط.
- الترتيب حسب المتوسط تنازليًّا.
👀 اعرض النتيجة المطلوبة
| category | products | avg_price |
|---|---|---|
| أدوات | 3 | 78.3 |
| مشروبات | 3 | 21.7 |
- الكود يعمل بلا أخطاء
- التصنيفات الصحيحة بأعدادها ومتوسطاتها بالترتيب الصحيح
- تستخدم GROUP BY
- تستخدم HAVING
💡 تلميح 1
ROUND(AVG(price), 1)💡 تلميح 2
HAVING COUNT(*) > 2 ثم ORDER BYحاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
SELECT category, COUNT(*) AS products, ROUND(AVG(price), 1) AS avg_priceFROM productsGROUP BY categoryHAVING COUNT(*) > 2ORDER BY avg_price DESC;