مقدمة عن التجميع
GROUP BY يُستخدم لتجميع الصفوف التي لها نفس القيم في أعمدة محددة،
ثم تطبيق دوال تجميعية (Aggregate Functions) على كل مجموعة.
الدوال التجميعية
COUNT() -- عدد الصفوف
SUM() -- مجموع القيم
AVG() -- المتوسط الحسابي
MAX() -- أكبر قيمة
MIN() -- أصغر قيمة
استخدام الدوال التجميعية بدون GROUP BY
-- إجمالي عدد الموظفين
SELECT COUNT(*) AS total_employees
FROM employees;
-- مجموع الرواتب
SELECT SUM(salary) AS total_salaries
FROM employees;
-- متوسط الرواتب
SELECT AVG(salary) AS average_salary
FROM employees;
-- أعلى وأقل راتب
SELECT MAX(salary) AS highest,
MIN(salary) AS lowest
FROM employees;
-- كل الإحصائيات معاً
SELECT COUNT(*) AS count,
SUM(salary) AS total,
AVG(salary) AS average,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary
FROM employees;
GROUP BY الأساسي
التجميع حسب عمود واحد
-- عدد الموظفين في كل قسم
SELECT department_id,
COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;
النتيجة:
+---------------+----------------+
| department_id | employee_count |
+---------------+----------------+
| 1 | 3 |
| 2 | 3 |
| 3 | 1 |
| 4 | 1 |
+---------------+----------------+
GROUP BY مع JOIN
-- عدد الموظفين مع اسم القسم
SELECT d.name AS department,
COUNT(e.id) AS employee_count
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name;
إحصائيات متعددة لكل مجموعة
-- إحصائيات الرواتب لكل قسم
SELECT department_id,
COUNT(*) AS employee_count,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary
FROM employees
GROUP BY department_id;
GROUP BY بأكثر من عمود
-- عدد الموظفين حسب القسم والعمر
SELECT department_id,
age,
COUNT(*) AS count
FROM employees
GROUP BY department_id, age;
-- المبيعات حسب المنتج والتاريخ
SELECT product_id,
order_date,
SUM(quantity) AS total_quantity
FROM orders
GROUP BY product_id, order_date;
HAVING - تصفية المجموعات
HAVING مثل WHERE لكن للمجموعات بعد التجميع.
-- الأقسام التي فيها أكثر من 2 موظفين
SELECT department_id,
COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 2;
الفرق بين WHERE و HAVING
WHERE: تُصفي الصفوف قبل التجميع
HAVING: تُصفي المجموعات بعد التجميع
WHERE مع HAVING معاً
-- الأقسام التي متوسط راتب موظفيها (فوق 25 سنة) أكبر من 5000
SELECT department_id,
AVG(salary) AS avg_salary
FROM employees
WHERE age > 25 -- تصفية قبل التجميع
GROUP BY department_id
HAVING AVG(salary) > 5000; -- تصفية بعد التجميع
أمثلة عملية متقدمة
أعلى 3 أقسام من حيث الرواتب
SELECT d.name AS department,
SUM(e.salary) AS total_salary
FROM employees e
JOIN departments d ON e.department_id = d.id
GROUP BY d.id, d.name
ORDER BY total_salary DESC
LIMIT 3;
المنتجات الأكثر مبيعاً
SELECT p.name AS product,
SUM(o.quantity) AS total_sold,
SUM(o.quantity * p.price) AS revenue
FROM orders o
JOIN products p ON o.product_id = p.id
GROUP BY p.id, p.name
ORDER BY total_sold DESC;
العملاء الأكثر طلباً
SELECT customer_name,
COUNT(*) AS order_count,
SUM(quantity) AS total_items
FROM orders
GROUP BY customer_name
HAVING COUNT(*) > 1
ORDER BY order_count DESC;
متوسط العمر في كل قسم
SELECT d.name AS department,
ROUND(AVG(e.age), 1) AS avg_age
FROM employees e
JOIN departments d ON e.department_id = d.id
GROUP BY d.id, d.name
ORDER BY avg_age;
COUNT بأشكال مختلفة
-- COUNT(*) - يعد كل الصفوف
SELECT COUNT(*) FROM employees;
-- COUNT(column) - يعد القيم غير NULL
SELECT COUNT(department_id) FROM employees;
-- لن يعد الصفوف التي department_id = NULL
-- COUNT(DISTINCT column) - يعد القيم الفريدة
SELECT COUNT(DISTINCT department_id)
FROM employees;
-- عدد الأقسام الفريدة
ترتيب تنفيذ الاستعلام
SELECT column, AGG(column) -- 5. اختيار الأعمدة
FROM table -- 1. تحديد الجدول
WHERE condition -- 2. تصفية الصفوف
GROUP BY column -- 3. التجميع
HAVING condition -- 4. تصفية المجموعات
ORDER BY column -- 6. الترتيب
LIMIT n; -- 7. تحديد العدد
أخطاء شائعة
-- خطأ: عمود غير مُجمّع وغير موجود في GROUP BY
SELECT name, department_id, COUNT(*)
FROM employees
GROUP BY department_id;
-- name ليس في GROUP BY ولا في دالة تجميعية!
-- الصحيح:
SELECT department_id, COUNT(*)
FROM employees
GROUP BY department_id;
-- خطأ: استخدام WHERE مع دالة تجميعية
SELECT department_id, COUNT(*)
FROM employees
WHERE COUNT(*) > 2 -- خطأ!
GROUP BY department_id;
-- الصحيح: استخدم HAVING
SELECT department_id, COUNT(*)
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 2;
تمارين للتطبيق
- احسب متوسط الأسعار لكل فئة منتجات
- اعرض الأقسام التي مجموع رواتبها أكبر من 10000
- احسب عدد الطلبات لكل عميل
- اعرض المنتج الأكثر مبيعاً
الخلاصة
GROUP BYلتجميع الصفوف- الدوال:
COUNT, SUM, AVG, MAX, MIN HAVINGلتصفية المجموعاتWHEREقبل التجميع،HAVINGبعده- كل عمود في SELECT يجب أن يكون في GROUP BY أو دالة تجميعية