Menu
flag Ar iconالعربيةdown icon

GROUP BY و HAVING في SQLite: تصفية النتائج المُجمَّعة

كيف تقوم GROUP BY بتجميع الصفوف داخل مجموعات في SQLite، وكيف تُستخدم HAVING لتصفية هذه المجموعات بعد التجميع، مع توضيح عملي للفرق بين WHERE و HAVING.

تحتوي هذه الصفحة على محررات قابلة للتشغيل — حرّر، شغّل، وشاهد النتيجة فوراً.

GROUP BY في SQLite: تجميع الصفوف في مجموعات

دوال التجميع مثل COUNT وSUM وAVG تختصر عدداً كبيراً من الصفوف إلى رقم واحد. أما GROUP BY فيتيح لك فعل ذلك لكل فئة على حدة — رقم لكل عميل، أو لكل شهر، أو لكل حالة. وهكذا تتحوّل كل قيمة فريدة (أو كل توليفة من القيم) إلى صف واحد في النتيجة.

ثلاثة عملاء، ثلاثة صفوف في النتيجة. الصفوف الستة الأصلية اختفت — انطوت داخل مجموعات حسب العميل، وتم حساب COUNT(*) و SUM(amount) لكل مجموعة على حدة.

التصور الذهني للأمر: عبارة GROUP BY customer تقول لـ SQLite: "اعتبر كل الصفوف التي تحمل نفس اسم العميل مجموعة واحدة". بعدها تشتغل دوال التجميع على كل مجموعة بشكل مستقل.

ما الذي يُسمح بوضعه في قائمة SELECT؟

هذه النقطة تربك الكثيرين. عند استخدام GROUP BY، كل عمود تضعه في قائمة SELECT يجب أن يكون إما مذكورًا في عبارة GROUP BY، أو ملفوفًا داخل دالة تجميع. خلاف ذلك تصبح القيمة غامضة — أي صف من المجموعة يُفترض أن نأخذ القيمة منه؟

لو كتبت SELECT region, rep, SUM(amount) مع GROUP BY region، فإن SQLite سينفّذ الاستعلام دون أي اعتراض (وهو متساهل في هذه النقطة عكس قواعد بيانات أخرى ترفضها)، لكن قيمة rep ستُختار بشكل عشوائي من داخل المجموعة. ستحصل على اسم مندوب واحد لكل منطقة دون أي ضمان لهويته. لا تعتمد على هذا السلوك أبداً، واحرص على تضمين كل عمود غير مُجمَّع في GROUP BY طالما أنك تعرضه في النتيجة.

HAVING لتصفية المجموعات بعد التجميع

تعمل WHERE على تصفية الصفوف قبل التجميع، بينما تعمل HAVING على تصفية المجموعات بعد التجميع. هذا هو الفرق بين WHERE و HAVING باختصار، ولهذا السبب لا يمكنك وضع COUNT(*) > 1 داخل جملة WHERE، لأن قيمة العدّ لم تُحسب بعد عند تنفيذ WHERE.

كليو عندها طلب واحد فقط، فمجموعتها تتم تصفيتها وتختفي من النتائج، ويبقى لدينا أدا وبوريس. لاحظ أن الشرط هنا يُطبَّق على القيمة المُجمَّعة لكل مجموعة، لا على الصفوف كلٌّ على حدة.

من الأشياء الجميلة في SQLite أنك تقدر تستخدم أسماء الأعمدة المستعارة (aliases) من قائمة SELECT مباشرةً داخل HAVING:

وغالبًا ما يكون هذا أوضح من تكرار SUM(amount) داخل عبارة HAVING.

الفرق بين WHERE و HAVING: استخدمهما معًا

العبارتان ليستا بديلتين عن بعضهما. WHERE تحدد أيّ الصفوف ستدخل في عملية التجميع، بينما HAVING تحدد أيّ المجموعات ستظهر في النتيجة النهائية. ومعظم الاستعلامات الواقعية تستخدم الاثنتين معًا.

اقرأه من الأعلى إلى الأسفل بترتيب التنفيذ:

  1. WHERE status = 'paid' — استبعد الصفوف المُستردّة كليًا.
  2. GROUP BY customer — وزّع ما تبقّى على مجموعات حسب العميل.
  3. SUM(amount) يُحسب لكل مجموعة على حدة.
  4. HAVING SUM(amount) > 75 — أبقِ فقط المجموعات التي تتجاوز هذا الحد.

ينجو بوريس (80 + 20 = 100) وكليو (200). أما آدا فطلبها المدفوع الوحيد كان 50، وهو أقل من الحد المطلوب.

HAVING بشروط متعددة والتجميع بأكثر من عمود

يقبل HAVING نفس المعاملات المنطقية التي يقبلها WHEREAND وOR وNOT — كما يمكنك التجميع باستخدام أكثر من عمود للحصول على مجموعات فرعية:

كل زوج (region, quarter) يُعتبر مجموعة مستقلة. شرط HAVING هنا يفرض شرطين معاً: أن يتجاوز الإجمالي 100 و أن يكون عدد الصفقات اثنين على الأقل. لذا فإن ('North', 'Q1') و ('South', 'Q2') فقط هما اللذان يستوفيان الشرطين.

نمط عملي: البحث عن القيم المكررة

يُعدّ الاستعلام GROUP BY ... HAVING COUNT(*) > 1 الطريقة المعتادة للعثور على القيم المكررة داخل عمود معيّن:

يظهر لدينا تكراران. من هنا، عادةً ما تقرّر إمّا دمج الحسابات، أو إضافة قيد UNIQUE، أو تنظيف البيانات — لكن استعلام الاكتشاف يبقى بالشكل نفسه في كل مرة.

استخدام HAVING بدون GROUP BY

هذه حالة غير شائعة، لكنها مسموح بها. عند غياب GROUP BY، تُعامَل النتيجة بأكملها كمجموعة واحدة، ويقوم HAVING بتصفيتها ككتلة واحدة — فإمّا أن تحصل على كل القيم المُجمَّعة، أو لا شيء على الإطلاق:

يظهر صف نتيجة واحد لأن المجموع يساوي 160. لو غيّرت الشرط إلى > 200، فلن يُرجع الاستعلام أي صفوف على الإطلاق. عمليًا، ستستخدم HAVING دائمًا تقريبًا مع GROUP BY، لكن من المفيد أن تعرف أن اللغة لا تشترط ذلك.

خلاصة سريعة

  • GROUP BY يجمّع الصفوف في مجموعات حسب المفتاح، ثم تعمل دوال التجميع داخل كل مجموعة.
  • أي عمود غير مُجمَّع داخل SELECT يجب أن يظهر أيضًا في GROUP BY.
  • WHERE يصفّي الصفوف قبل التجميع، أما HAVING فيصفّي المجموعات بعده.
  • دوال التجميع مثل COUNT(*) وSUM(...) مكانها في HAVING وليس WHERE أبدًا.
  • يقبل HAVING شروطًا مركّبة، ويمكنه الإشارة إلى الأسماء المستعارة في SELECT.

التالي: المفاتيح الأجنبية

تجميع البيانات داخل جدول واحد أمر مفيد، لكن معظم قواعد البيانات الحقيقية توزّع البيانات على عدة جداول: الطلبات في جدول، العملاء في جدول آخر، والمنتجات في جدول ثالث. المفاتيح الأجنبية هي الطريقة التي تربط بها هذه الجداول معًا للحفاظ على اتساق العلاقات بينها. وهذا ما سنتناوله في الفصل القادم.

الأسئلة الشائعة

ما الفرق بين WHERE و HAVING في SQLite؟

WHERE تقوم بتصفية الصفوف الفردية قبل عملية التجميع، أما HAVING فتُصفّي المجموعات نفسها بعد تنفيذ التجميع. فمثلًا WHERE amount > 100 تُبقي الصفوف التي تتجاوز قيمتها 100 فقط، في حين أن HAVING SUM(amount) > 100 تُبقي المجموعات التي يتعدى مجموعها 100. ولا يُسمح باستخدام دوال التجميع مثل COUNT أو SUM داخل WHERE — وهنا يأتي دور HAVING.

هل يمكن استخدام HAVING بدون GROUP BY في SQLite؟

نعم، يمكن ذلك. عند غياب GROUP BY يتعامل SQLite مع مجموعة النتائج كاملةً باعتبارها مجموعة واحدة، وتقوم HAVING بتصفيتها كوحدة واحدة، فيكون الناتج إما صفًا واحدًا أو لا شيء. لكن هذا الأسلوب نادر عمليًا، إذ غالبًا ما تُرافق HAVING جملة GROUP BY.

كيف أُصفّي المجموعات بناءً على COUNT في SQLite؟

ضع دالة التجميع داخل HAVING وليس WHERE. على سبيل المثال: SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 تُرجع العملاء الذين لديهم أكثر من طلب واحد. كما يسمح لك SQLite بالإشارة إلى الاسم المستعار (alias) لعمود من قائمة SELECT داخل HAVING مباشرةً.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن