استعلامات التجميع

استعلامات التجميع

🌍 تشبيه من العالم האמיתי

تخيل أنك معلم لديك كومة من كشوف الدرجات. تريد معرفة:

هذا ما يفعله GROUP BY: أولاً تقسيم البيانات إلى مجموعات، ثم إجراء الإحصائيات على كل مجموعة. الدوال التجميعية التي تعلمتها في الدرس السابق تحسب قيمة واحدة للجدول بالكامل، بينما GROUP BY يحسب القيم حسب الفئات.


🎯 المفاهيم الأساسية

الصيغة الأساسية لـ GROUP BY

SQL
SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE condition
GROUP BY column_name;

منطق التنفيذ: أولاً تجميع حسب عمود GROUP BY، ثم تطبيق الدالة التجميعية على كل مجموعة.

SQL
-- كم عدد الموظفين في كل قسم؟
SELECT department_id, COUNT(*) AS headcount
FROM employees
GROUP BY department_id;

الناتج:

TEXT
department_id  headcount
-------------  ---------
1              3
2              2
3              2
NULL           1
💡 قاعدة: يجب أن تظهر الأعمدة في SELECT إما في GROUP BY أو أن تكون ملفوفة بدالة تجميعية. وإلا، ستكون الدلالات غامضة وسيُبلّغ قاعدة البيانات عن خطأ.

التجميع بعد التجميع

النمط الأساسي لـ GROUP BY هو: تجميع → تجميع إحصائي.

SQL
-- متوسط الراتب لكل قسم
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

مجموعات تجميعية شائعة:

SQL
SELECT department_id,
       COUNT(*) AS headcount,
       SUM(salary) AS total_salary,
       AVG(salary) AS avg_salary,
       MAX(salary) AS highest_salary,
       MIN(salary) AS lowest_salary
FROM employees
GROUP BY department_id;

التجميع بأعمدة متعددة

يمكنك التجميع حسب عدة أعمدة — تُعتبر الصفوف في نفس المجموعة فقط عندما تتطابق قيم جميع الأعمدة المحددة:

SQL
-- عدد الموظفين حسب القسم والحالة
SELECT department_id, status, COUNT(*) AS headcount
FROM employees
GROUP BY department_id, status;

الناتج:

TEXT
department_id  status    headcount
-------------  --------  ---------
1              active    3
2              active    1
2              inactive  1
3              active    2
💡 الفهم: التجميع بأعمدة متعددة يشبه المجاميع الفرعية متعددة المستويات في Excel. أولاً تجميع حسب القسم، ثم حسب الحالة — كل تركيبة قسم + حالة تشكل مجموعة.

GROUP BY + ORDER BY

يمكنك الترتيب بعد التجميع:

SQL
-- متوسط الراتب حسب القسم، مرتب من الأعلى إلى الأدنى
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
ORDER BY avg_salary DESC;

WHERE مقابل HAVING — تصفية الصفوف مقابل تصفية المجموعات

هذا أحد أهم المفاهيم في هذا الدرس:

الميزة WHERE HAVING
يُصفّي صفوف (قبل التجميع) مجموعات (بعد التجميع)
توقيت التنفيذ قبل GROUP BY بعد GROUP BY
يمكن استخدام الدوال التجميعية ❌ لا ✅ نعم
الاستخدام يمكن استخدامه بمفرده يجب استخدامه مع GROUP BY
SQL
-- WHERE: تصفية الصفوف أولاً، ثم التجميع
-- إيجاد الأقسام التي لديها أكثر من 3 موظفين
SELECT department_id, COUNT(*) AS headcount
FROM employees
WHERE salary > 5000       -- أولاً استبعاد الصفوف التي رواتبها أقل من 5000
GROUP BY department_id;

-- HAVING: التجميع أولاً، ثم تصفية المجموعات
-- إيجاد الأقسام التي لديها 3 موظفين أو أكثر
SELECT department_id, COUNT(*) AS headcount
FROM employees
GROUP BY department_id
HAVING COUNT(*) >= 3;     -- تصفية المجموعات التي عدد أعضائها أقل من 3
SQL
-- الجمع بين WHERE + HAVING
-- إيجاد الأقسام التي لديها موظفون تم تعيينهم في 2024 ومتوسط راتب > 10000
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2024-01-01'   -- تصفية الصفوف أولاً
GROUP BY department_id
HAVING AVG(salary) > 10000;      -- ثم تصفية المجموعات

ترتيب التنفيذ

يختلف ترتيب الكتابة وترتيب التنفيذ في استعلامات SQL:

TEXT
ترتيب الكتابة:  SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
ترتيب التنفيذ: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

فهم ترتيب التنفيذ أمر بالغ الأهمية لكتابة SQL صحيح:

  1. FROM — تحديد مصدر البيانات
  2. WHERE — تصفية صف بصف
  3. GROUP BY — تجميع
  4. HAVING — تصفية المجموعات
  5. SELECT — اختيار الأعمدة، حساب التعبيرات
  6. ORDER BY — ترتيب
💡 مفتاحي: هذا هو السبب في عدم إمكانية استخدام الدوال التجميعية في WHERE — لأنه عند تنفيذ WHERE، لم يتم تنفيذ GROUP BY بعد، لذا لا توجد نتائج تجميعية.


📝 الصيغة الأساسية

SQL
-- التجميع الأساسي
SELECT column_name, aggregate_function(column_name)
FROM table_name
GROUP BY column_name;

-- التجميع بأعمدة متعددة
SELECT column1, column2, aggregate_function(column_name)
FROM table_name
GROUP BY column1, column2;

-- WHERE + GROUP BY + HAVING
SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE row_level_condition
GROUP BY column_name
HAVING aggregate_level_condition
ORDER BY sort_column;
💡 نصيحة:

  • لا يلزم أن تتطابق أعمدة GROUP BY مع ترتيب الأعمدة في SELECT، ولكن يجب أن تشمل جميع الأعمدة غير التجميعية
  • يمكن لـ HAVING استخدام أسماء المستعارة للدوال التجميعية (مدعومة في MySQL و PostgreSQL)، لكن SQL القياسي يتطلب كتابة التعبير الكامل
  • تتجمع قيم NULL معًا في GROUP BY

📌 أمثلة

مثال: إحصائيات الموظفين حسب القسم

SQL
SELECT d.department_name,
       COUNT(e.employee_id) AS headcount,
       AVG(e.salary) AS avg_salary,
       SUM(e.salary) AS total_salary
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name
ORDER BY total_salary DESC;
▶ جرّب الكود

الناتج:

TEXT
department_name  headcount  avg_salary  total_salary
---------------  ---------  ----------  ------------
Technology       3          17666.67    53000.00
Finance          2          13500.00    27000.00
Marketing        2          11500.00    23000.00
Administration   1          9000.00     9000.00
💡 التفسير: يضمن LEFT JOIN عرض الأقسام التي لا يوجد بها موظفون (بعدد 0). يقوم GROUP BY department_name بالتجميع حسب اسم القسم.


مثال: WHERE + GROUP BY + HAVING معًا

SQL
-- من بين الموظفين الذين تم تعيينهم في 2024، إيجاد الأقسام بمتوسط راتب > 12000
SELECT department_id,
       COUNT(*) AS headcount,
       AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2024-01-01'
GROUP BY department_id
HAVING AVG(salary) > 12000
ORDER BY avg_salary DESC;
▶ جرّب الكود

الناتج:

TEXT
department_id  headcount  avg_salary
-------------  ---------  ----------
1              2          18500.00
3              1          14000.00
💡 عملية التنفيذ:

  1. WHERE hire_date >= '2024-01-01' — أولاً تصفية الموظفين الذين تم تعيينهم في 2024
  2. GROUP BY department_id — تجميع حسب القسم
  3. HAVING AVG(salary) > 12000 — الاحتفاظ فقط بالمجموعات التي متوسط رواتبها > 12000
  4. ORDER BY avg_salary DESC — ترتيب حسب متوسط الراتب تنازليًا

مثال: تجميع بأعمدة متعددة + شروط معقدة

SQL
-- إحصائيات الموظفين حسب القسم والحالة، عرض المجموعات التي عدد أعضائها 2 أو أكثر فقط
SELECT d.department_name,
       e.status,
       COUNT(*) AS headcount,
       MIN(e.salary) AS lowest_salary,
       MAX(e.salary) AS highest_salary
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 0
GROUP BY d.department_name, e.status
HAVING COUNT(*) >= 2
ORDER BY d.department_name, headcount DESC;
▶ جرّب الكود

الناتج:

TEXT
department_name  status  headcount  lowest_salary  highest_salary
---------------  ------  ---------  -------------  --------------
Finance          active  2          12000.00       14000.00
Marketing        active  2          10000.00       12000.00
Technology       active  3          15000.00       20000.00
💡 التفسير: أولاً استخدم WHERE لاستبعاد الصفوف التي رواتبها صفر، ثم تجميع حسب القسم + الحالة، يقوم HAVING بتصفية المجموعات التي عدد أعضائها أقل من 2، وأخيرًا الترتيب.


🎬 سيناريوهات تطبيقية

السيناريو 1: تقرير المبيعات — إحصائيات الطلبات الشهرية

إنشاء تقرير مبيعات شهري، عرض الأشهر التي لديها 3 طلبات أو أكثر فقط.

SQL
SELECT 
    EXTRACT(YEAR FROM order_date) AS year,
    EXTRACT(MONTH FROM order_date) AS month,
    COUNT(*) AS order_count,
    SUM(total_amount) AS total_amount,
    AVG(total_amount) AS avg_order_amount
FROM orders
WHERE status IN ('completed', 'shipped')
GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date)
HAVING COUNT(*) >= 3
ORDER BY year, month;
💡 النهج: يقوم WHERE بتصفية الطلبات المكتملة والمشحونة، تجميع حسب السنة والشهر للإحصائيات، يقوم HAVING بتصفية الأشهر التي عدد طلباتها أقل من 3.

السيناريو 2: تحليل الموارد البشرية — إيجاد الأsections ذات الرواتب المرتفعة

إيجاد الأقسام التي متوسط رواتبها أعلى من متوسط الرواتب في الشركة.

SQL
SELECT d.department_name,
       COUNT(e.employee_id) AS headcount,
       AVG(e.salary) AS dept_avg_salary
FROM departments d
JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name
HAVING AVG(e.salary) > (SELECT AVG(salary) FROM employees)
ORDER BY dept_avg_salary DESC;
💡 النهج: يمكن استخدام الاستعلامات الفرعية في HAVING. أولاً حساب متوسط راتب كل قسم، ثم مقارنته بالمتوسط العام للشركة. هذا تطبيق مشترك للدوال التجميعية والاستعلامات الفرعية.


❓ أسئلة شائعة

س: هل يجب أن تظهر أعمدة SELECT في GROUP BY? ج: نعم، يجب أن تظهر جميع الأعمدة غير التجميعية في GROUP BY. هذا متطلب معياري في SQL. على سبيل المثال، SELECT a, b, COUNT(*) FROM t GROUP BY a سيُسبب خطأ لأن b ليست في GROUP BY ولا ملفوفة بدالة تجميعية.

س: هل يمكن لـ HAVING استخدام أسماء المستعارة من SELECT? ج: يدعم MySQL و PostgreSQL استخدام أسماء المستعارة في HAVING، لكن SQL Server لا يدعم ذلك. للتوافق، يُوصى بكتابة التعبير الكامل: HAVING AVG(salary) > 10000 بدلاً من HAVING avg_salary > 10000.

س: كيف تُعالج قيم NULL في GROUP BY? ج: تتجمع قيم NULL معًا. إذا كان department_id يحتوي على قيم NULL، فسيتم وضع جميع الصفوف التي تحتوي على NULL في نفس المجموعة. يكون هذا مفيدًا عادةً، لكن انتبه للتمييز بين "لا يوجد قسم" و"معرّف القسم هو 0".

س: هل يمكن استخدام WHERE و HAVING معًا? ج: نعم، وهو شائع جدًا. يقوم WHERE بتصفية الصفوف قبل التجميع، وHAVING يصفّي المجموعات بعد التجميع. ترتيب التنفيذ: WHEREGROUP BYHAVING.


📖 ملخص


📝 تمارين

التمرين 1 (⭐): استعلم عن عدد الموظفين ومتوسط الراتب لكل قسم، مرتب حسب متوسط الراتب من الأعلى إلى الأدنى.

التمرين 2 (⭐⭐): استعلم عن عدد الموظفين الذين تم تعيينهم بعد 2024 في كل قسم، عرض الأقسام التي لديها 2 موظفين أو أكثر فقط.

التمرين 3 (⭐⭐⭐): استعلم عن أعلى موظف راتبًا في كل قسم. اعرض اسم القسم واسم الموظف والراتب. تلميح: يمكن تحقيق ذلك باستخدام استعلام فرعي أو دالة النافذة ROW_NUMBER() (تُغطى لاحقًا).


الدرس التالي

👉 15-advanced-functions - الدوال المتقدمة: تعلّم الدوال المتقدمة في SQL، وإتقان معالجة النصوص، والحسابات الرقمية، وعمليات التواريخ، وغيرها من المهارات العملية!

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%