تخطَّ إلى المحتوى

تقارير المبيعات في MySQL: GROUP BY و GROUP_CONCAT

الدرس 8 من 12 · MySQL

صاحب المتجر يريد أن يعرف: من أفضل العملاء؟ أي تصنيف يحقق أعلى متوسط سعر؟ كم طلباً وصلنا كل شهر؟ هذه تقارير، وهي ما يحوّل قاعدة البيانات من مخزن صامت إلى أداة لاتخاذ القرارات. في هذا الدرس الأخير ستبني تقارير حقيقية لمتجرنا، وستتعرف على أدوات تجميع خاصة بـ MySQL.

SELECT c.name, SUM(o.quantity * p.price) AS total_spent
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON o.product_id = p.id
GROUP BY c.id, c.name
ORDER BY total_spent DESC;

الناتج:

name total_spent
عمر 1009.00
سارة 379.49

خطوة بخطوة:

  1. JOIN يجمع لكل طلب: العميل والمنتج وسعره.
  2. o.quantity * p.price يحسب قيمة كل طلب.
  3. GROUP BY c.id, c.name يضع طلبات كل عميل في مجموعة.
  4. SUM يجمع قيم كل مجموعة.
  5. ORDER BY ... DESC يضع الأفضل في الأعلى.

عمر اشترى سماعتين (2 × 450.50) وكتاباً (108.00)، فالإجمالي 1009.00. هذا التقرير هو أساس برامج ولاء العملاء: من ينفق أكثر يستحق خصماً خاصاً.

SELECT category,
COUNT(*) AS products_count,
ROUND(AVG(price), 2) AS avg_price,
SUM(stock) AS total_stock
FROM products
GROUP BY category;

الناتج:

category products_count avg_price total_stock
كتب 2 83.25 45
إلكترونيات 2 325.25 27
أدوات مدرسية 2 157.88 50

في استعلام واحد: عدد المنتجات، ومتوسط السعر، وإجمالي المخزون لكل تصنيف. (ولاحظ ترتيب التصنيفات حسب تعريف ENUM، كما تعلمت في درس الترتيب.)

ميزة خاصة بـ MySQL ومفيدة جداً: بدلاً من عدّ المنتجات، اعرض أسماءها مجمعة:

SELECT category,
GROUP_CONCAT(name ORDER BY name SEPARATOR '، ') AS items
FROM products
GROUP BY category;

الناتج:

category items
كتب قصص الأطفال، كتاب البرمجة للمبتدئين
إلكترونيات آلة حاسبة علمية، سماعة لاسلكية
أدوات مدرسية حقيبة مدرسية، مجموعة أقلام

GROUP_CONCAT تجمع قيم المجموعة في نص واحد، ويمكنك تحديد ترتيبها بـ ORDER BY والفاصل بينها بـ SEPARATOR. مثالية لعرض «الوسوم» أو «المنتجات في الطلب» في سطر واحد.

كم طلباً كل شهر؟

SELECT YEAR(order_date) AS y, MONTH(order_date) AS m, COUNT(*) AS orders_count
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date)
ORDER BY y, m;

الناتج:

y m orders_count
2026 10 3
2026 11 2

بدمج دوال التاريخ مع GROUP BY، تبني الرسوم البيانية للمبيعات الشهرية التي تراها في لوحات التحكم. ويمكنك التجميع بالأسبوع أو اليوم بنفس الفكرة.

العملاء الذين طلبوا مرتين أو أكثر:

SELECT customer_id, COUNT(*) AS orders_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2;

الناتج:

customer_id orders_count
1 3
2 2

تذكّر الفرق: WHERE تصفّي الطلبات قبل التجميع (مثلاً: طلبات أكتوبر فقط)، و HAVING تصفّي المجموعات بعده (مثلاً: العملاء الذين تجاوزوا عدداً معيناً).

جرّب هذا الاستعلام:

SELECT category, name FROM products GROUP BY category;

الناتج:

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
and contains nonaggregated column 'shop.products.name' which is not
functionally dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by

لماذا الرفض؟ كل تصنيف فيه منتجان، فأي اسم يجب أن يظهر في صف التصنيف؟ السؤال بلا إجابة منطقية. الإصدارات القديمة من MySQL كانت تختار اسماً عشوائياً بصمت، فتنتج تقارير مضللة. أما الإصدارات الحديثة فتفعّل الوضع only_full_group_by افتراضياً وترفض الاستعلام.

الحل: كل عمود في SELECT إما في GROUP BY، أو داخل دالة تجميع مثل COUNT أو MAX أو GROUP_CONCAT:

SELECT category, GROUP_CONCAT(name) FROM products GROUP BY category;
  1. اعرض إجمالي الكمية المباعة من كل منتج، مرتبة من الأكثر مبيعاً.
  2. اعرض إجمالي إيرادات كل شهر (الكمية × السعر).
  3. اعرض لكل عميل أسماء المنتجات التي اشتراها في سطر واحد بـ GROUP_CONCAT.
  4. اعرض التصنيفات التي متوسط أسعارها أكبر من 100.
  5. تحدٍّ: اعرض أكثر منتج مبيعاً في كل شهر.
  • عمود ليس في GROUP BY ولا في دالة تجميع: الخطأ 1055. أضفه لـ GROUP BY أو ضعه داخل دالة.
  • شرط على دالة تجميع في WHERE: WHERE SUM(...) > 100 خطأ. استخدم HAVING.
  • GROUP_CONCAT لنصوص طويلة جداً: النتيجة تُقطع افتراضياً عند 1024 حرفاً. يمكن رفع الحد بالإعداد group_concat_max_len.

سؤال

أي دالة في MySQL تجمع أسماء منتجات كل تصنيف في نص واحد؟

سؤال

لماذا ترفض MySQL الحديثة SELECT category, name FROM products GROUP BY category؟

  • JOIN مع GROUP BY و SUM يبني تقارير مثل إجمالي مشتريات كل عميل.
  • COUNT و AVG و SUM معاً تعطي إحصاءات كاملة لكل مجموعة.
  • GROUP_CONCAT ميزة MySQL لدمج قيم المجموعة في نص، مع ترتيب وفاصل.
  • دوال التاريخ مع GROUP BY تبني تقارير شهرية، و HAVING تصفّي المجموعات.
  • only_full_group_by يمنع الأعمدة الغامضة.