في هذا الدرس 6تصفّح المواضيع
من الأخطاء الشائعة محاولة كتابة دالة تجميعية داخل جملة WHERE مثل WHERE COUNT(*) > 5، وهو ما يرفضه محرك MySQL فوراً بالخطأ Error 1111: Invalid use of group function.
- توقيت التنفيذ المنطقي: تُنفَّذ جملة
WHEREعلى كل صف مفرد قبل الوصول إلى مرحلةGROUP BYوقبل تكوين المجموعات أصلاً. - عدم اكتمال الحساب التجميعي: أثناء فحص صف واحد من الجدول، لا يملك المحرك بعدُ قيمة المجموع الكلي
SUM()أو العدد النهائيCOUNT(*)للمجموعة!
ولذلك وفّرت لغة SQL جملة HAVING المخصصة لتصفية المجموعات بعد اكتمال تجميعها وحساب دوالها.
الفرق الدقيق بين تصفية الصفوف بـ WHERE وتصفية المجموعات بـ HAVING
-- Valid pre-grouping row filter in WHERE (no aggregates)
SELECT dept_id,
COUNT(*) AS active_staff,
AVG(salary) AS avg_salary
FROM employees
WHERE is_active = 1
GROUP BY dept_id;
تُكتب جملة HAVING مباشرةً بعد جملة GROUP BY (وقبل ORDER BY) لتصفية المجموعات الناتجة بناءً على قيم الدوال التجميعية.
- الفحص بعد اكتمال التجميع: بعد أن تقسم
GROUP BYالصفوف وتحسب الدوال التجميعية لكل مجموعة، تحتفظHAVINGفقط بالمجموعات التي يحقق شرطها القيمة الصائبة1. - دمج عدة شروط تجميعية: يمكنك ربط أكثر من دالة تجميعية داخل
HAVINGباستخدامANDوORوBETWEENومعاملات المقارنة القياسية.
تُستبعد أي مجموعة تفشل في تحقيق شرط HAVING من النتيجة النهائية للاستعلام.
-- Keep only categories with at least 5 products and high stock
SELECT category,
COUNT(*) AS product_count,
SUM(stock_qty) AS total_stock
FROM products
GROUP BY category
HAVING COUNT(*) >= 5
AND SUM(stock_qty) >= 100;
تتجلى القوة الحقيقية لاستعلامات التحليل عند الجمع بين WHERE و HAVING في استعلام واحد ليؤدي كل منهما دوره الدقيق:
- الخطوة الأولى
WHERE: تصفّي الصفوف الخام من الجدول قبل التجميع (مثل اختيار الطلبات المدفوعة فقط خلال عام2026). - الخطوة الثانية
GROUP BY: تجمّع الطلبات المدفوعة المتبقية حسب رقم العميلcustomer_idوتحسب مجموع مشتريات كل عميل. - الخطوة الثالثة
HAVING: تصفّي مجموعات العملاء للإبقاء فقط على كبار العملاء الذين بلغ مجموع مشترياتهم1000.00فأكثر.
لاحظ أن استبعاد الطلبات الملغاة مبكراً في WHERE يؤثر مباشرةً في دقة المجاميع التي تفحصها HAVING لاحقاً.
-- VIP customers in 2026: filter rows in WHERE, groups in HAVING
SELECT customer_id,
COUNT(*) AS paid_orders_2026,
SUM(total_usd) AS total_spent_2026
FROM orders
WHERE order_status = 'paid'
AND created_at >= '2026-01-01'
GROUP BY customer_id
HAVING SUM(total_usd) >= 1000.00
ORDER BY total_spent_2026 DESC;
يدعم محرك MySQL 8 ميزة مريحة تتيح الإشارة إلى الاسم المستعار المعرّف بـ AS للدالة التجميعية في SELECT مباشرةً داخل جملة HAVING.
- اختصار الكود في MySQL: يمكنك كتابة
SUM(total_usd) AS total_spentفي العرض ثم كتابةHAVING total_spent >= 500دون إعادة كتابة الدالة. - ملاحظة التوافق القياسي: في معيار ISO SQL الصارم (وبعض المحركات الأخرى)، تُنفَّذ
HAVINGمنطقياً قبل ربط أسماءSELECTالمستعارة؛ لذا فإن كتابة الدالة الصريحةHAVING SUM(total_usd) >= 500يضمن عمل الاستعلام على كافة قواعد البيانات.
داخل بيئة MySQL 8 يعمل كلا الأسلوبين بنفس الكفاءة وخطة التنفيذ تماماً.
-- Using SELECT aggregate aliases inside HAVING in MySQL 8
SELECT product_id,
COUNT(*) AS review_count,
AVG(rating) AS avg_rating
FROM product_reviews
GROUP BY product_id
HAVING review_count >= 10
AND avg_rating >= 4.5
ORDER BY avg_rating DESC;
رغم أن MySQL يسمح نحوياً بكتابة شرط عادي على عمود التجميع داخل HAVING (مثل GROUP BY dept_id HAVING dept_id = 10)، إلا أن ذلك يُعد ممارسة خاطئة تضر بالأداء:
- تكلفة التصفية المتأخرة: وضع شرط غير تجميعي في
HAVINGيعني منطقياً تجميع كل أقسام الجدول وحساب مجاميعها في الذاكرة أولاً ثم رميها جميعاً باستثناء القسم رقم10! - سرعة التصفية المبكرة بـ
WHERE: كتابةWHERE dept_id = 10تصفّي الصفوف مبكراً عبر فهرس B-Tree قبل بدء التجميع أصلاً، مما يوفر استهلاك المعالج والذاكرة المؤقتة.
القاعدة الهندسية الثابتة: ضع كل شرط لا يحتوي على دالة تجميعية في WHERE، وخصص HAVING للشروط التجميعية فقط.
-- Best Practice: filter dept_id early in WHERE, not in HAVING
SELECT dept_id,
job_role,
COUNT(*) AS role_count,
AVG(salary) AS avg_salary
FROM employees
WHERE dept_id IN (10, 20)
AND is_active = 1
GROUP BY dept_id, job_role
HAVING COUNT(*) >= 3;
يوضّح الجدول التالي الفروق الجوهرية الكاملة بين جملتي WHERE و HAVING في MySQL 8:
| وجه المقارنة | جملة WHERE | جملة HAVING |
|---|---|---|
| هدف التصفية | الصفوف الفردية الخام في الجدول | المجموعات الملخصة الناتجة عن التجميع |
| توقيت التنفيذ المنطقي | قبل مرحلة GROUP BY والتجميع | بعد مرحلة GROUP BY وحساب الدوال |
| الدوال التجميعية | ممنوعة (تُطلق الخطأ Error 1111) | مدعومة بالكامل (COUNT و SUM و AVG) |
| الأسماء المستعارة في SELECT | غير متاحة للاستخدام | متاحة للاستخدام في MySQL 8 |
| استثمار فهارس B-Tree | تستخدم الفهارس مباشرة لتقليص الصفوف المقروءة | تفحص نتائج المجموعات المحسوبة في الذاكرة |
ملخص الدرس: تعلّمت في هذا الدرس سبب رفض WHERE للدوال التجميعية، وكيفية تصفية المجموعات عبر HAVING، وطريقة الجمع بين WHERE و HAVING في استعلام واحد، وأهمية التصفية المبكرة بـ WHERE لتحسين الأداء.
الخطوة التالية والدرس القادم: نختتم وحدة التجميع في الدرس القادم بتعلّم توليد المجاميع الفرعية والكلية عبر WITH ROLLUP، وتمييز صفوف المجموع بدالة GROUPING()، وإتقان الترتيب المنطقي الكامل لتنفيذ استعلام SQL.
-- Pipeline: WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT
SELECT category,
COUNT(*) AS active_items,
SUM(stock_qty) AS category_stock,
AVG(unit_price) AS avg_category_price
FROM products
WHERE is_active = 1
GROUP BY category
HAVING COUNT(*) >= 3
AND SUM(stock_qty) >= 50
ORDER BY category_stock DESC
LIMIT 10;
ملخص الدرس
حصاد الدرسمراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي
ماذا تعلّمت في هذا الدرس؟
أتقنّا في هذا الدرس تصفية المجموعات بجملة HAVING والفرق الدقيق بينها وبين WHERE في MySQL 8 مع أمثلة عملية.
الخطوة التالية والدرس القادم
أكمل التدريبات والاختبار التفاعلي أدناه قبل الانتقال إلى الدرس التالي في مسار MySQL.
سجّل الدخول لحفظ تقدمك
تابع إنجاز المواضيع والمصطلحات والمهام، وكمّل من آخر خطوة على أي جهاز.
المصطلحات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 HAVING Clause جملة تصفية المجموعات HAVING
- جملة في SQL تُنفَّذ بعد GROUP BY لتصفية المجموعات الملخصة بناءً على نتائج الدوال التجميعية.
- للمراجعة
- 02 Pre-Grouping Filter (WHERE) التصفية قبل التجميع (WHERE)
- تصفية على مستوى الصفوف الفردية تُنفَّذ قبل GROUP BY لاستبعاد السجلات غير المطلوبة مبكراً باستخدام الفهارس.
- للمراجعة
- 03 Post-Grouping Filter (HAVING) التصفية بعد التجميع (HAVING)
- تصفية على مستوى المجموعات تُنفَّذ بعد حساب قيم COUNT و SUM و AVG لكل مجموعة.
- للمراجعة
- 04 MySQL Error 1111 خطأ الاستخدام غير الصالح لدالة التجميع (Error 1111)
- رسالة الخطأ التي يُطلقها MySQL عند كتابة دالة تجميعية مباشرة داخل جملة WHERE.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
الاختصارات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 KPI KPI
- Key Performance Indicator
- مؤشر الأداء الرئيسي الذي تُصفّى مجموعاته عبر جملة HAVING
- للمراجعة
- 02 VIP VIP
- Very Important Person (High-Value Segment)
- شريحة العملاء ذوي القيمة العالية المستخرجة عبر HAVING SUM(total) >= threshold
- للمراجعة
- 03 CPU CPU
- Central Processing Unit
- وحدة المعالجة المركزية التي نوفر استهلاكها بالتصفية المبكرة في WHERE
- للمراجعة
- 04 ISO ISO
- International Organization for Standardization
- المنظمة الدولية للمعايير المحددة لترتيب تنفيذ WHERE قبل GROUP BY ثم HAVING
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
مهامك قبل الاختبار
خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.
-
اكتب استعلام SELECT على جدول employees يعرض dept_id و COUNT(*) باسم emp_count مجمّعةً حسب dept_id، مع الإبقاء فقط على الأقسام التي يزيد عدد موظفيها على 5.
-
اكتب استعلام SELECT على جدول orders يعرض customer_id و SUM(total_usd) باسم total_spent للطلبات التي تحقق order_status = 'paid'، مجمّعةً حسب customer_id، بشرط ألا يقل المجموع عن 500.
تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.
اختبر فهمك.
ووصلها للإتقان.
كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.
أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.
الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.
لمحة عن التحدي8 أسئلة تنتظرك +
لماذا يفشل الاستعلام SELECT dept_id, COUNT(*) FROM employees WHERE COUNT(*) > 3 GROUP BY dept_id في MySQL؟
تُقيّم الدوال التجميعية بعد تقسيم المجموعات في GROUP BY، ولذلك يجب وضع شروطها في HAVING وليس في WHERE.
ما الترتيب المكتوب الصحيح لجمل استعلام SELECT في MySQL؟
تأتي WHERE قبل GROUP BY، وتأتي HAVING بعد GROUP BY، وتختم الجملة بـ ORDER BY ثم LIMIT.
لماذا يجب وضع شرط غير تجميعي مثل is_active = 1 في جملة WHERE بدلاً من HAVING؟
التصفية المبكرة في WHERE تستبعد الصفوف غير المطلوبة قبل إرسالها إلى مرحلة التجميع.
هل يمكن لجملة HAVING التصفية بدالة تجميعية مثل SUM(salary) > 50000 حتى لو لم تكن SUM(salary) مكتوبة في قائمة SELECT؟
تستطيع جملة HAVING فحص أي دالة تجميعية على المجموعة حتى لو لم ندرج تلك الدالة ضمن الأعمدة المعروضة في SELECT.
هل يسمح MySQL 8 باستخدام الاسم المستعار للدالة التجميعية المعرّف في SELECT (مثل COUNT(*) AS cnt) داخل جملة HAVING؟
يتيح MySQL استخدام الأسماء المستعارة المعرّفة في SELECT داخل كل من GROUP BY و HAVING و ORDER BY.
في الاستعلام SELECT dept, AVG(pay) FROM staff WHERE pay > 2000 GROUP BY dept HAVING AVG(pay) > 5000، أي موظفين يدخلون في حساب متوسط AVG(pay)؟
تستبعد جملة WHERE الموظفين الذين يقل راتبهم عن أو يساوي 2000 قبل بدء التجميع وحساب المتوسط.
هل يمكن ربط شرطين تجميعيين داخل HAVING باستخدام AND أو OR مثل HAVING COUNT(*) >= 3 AND MIN(price) >= 10؟
تقبل جملة HAVING دمج أي عدد من الشروط التجميعية عبر المعاملات المنطقية والأقواس.
ماذا يحدث عند استخدام HAVING في استعلام يحتوي على دوال تجميعية بدون جملة GROUP BY؟
عند غياب GROUP BY يشكّل الجدول مجموعة كلية واحدة، فتحتفظ HAVING بصف الملخص إذا تحقق الشرط أو تستبعده.