SQL Server / الأساسي / الدرس 5
التجميع والمجاميع الموثوقة
ابنِ ملخصات عملاء ذات معانٍ صريحة للمبالغ والأعداد والقيم المفقودة، باستخدام GROUP BY وHAVING والتجميع المشروط والحساب الصحيح.
في هذه الصفحة
أهداف الدرس
- تمييز شروط الصفوف من شروط المجموعات.
- شرح سلوك COUNT وNULL.
- التحقق من وحدة الملخص والدقة العددية.
اختر مفاتيح التجميع وفق السؤال
تغير GROUP BY وحدة الصف من صفوف الإدخال المفردة إلى صف لكل تركيب متميز من مفاتيح التجميع. يحتاج تقرير «حسب العميل والشهر» الخاصيتين معاً. قد يدمج التجميع باسم العميل وحده حسابات مختلفة تحمل الاسم نفسه. اجمع بالمعرف المستقر ثم أضف الصفات الوصفية بعناية، أو ضمنها عندما تكون اعتماديتها مفهومة.
يجب أن يصف كل تعبير مختار المجموعة بصورة مسموحة أو أن يكون تجميعاً. لا يختار SQL Server اسماً غير مجمّع اعتباطياً. تحمي هذه الصرامة سؤالاً مهماً: أي اسم تعرض مجموعة تضم عدة عملاء؟ ابدأ بكتابة المفاتيح المتوقعة واحسب مجموعة واحدة يدوياً قبل كتابة الاستعلام الكامل.
ميّز الصفوف من القيم المعروفة
تحسب COUNT(*) الصفوف، بينما تحسب COUNT(expression) القيم غير NULL للتعبير. تتجاهل SUM وAVG مدخلات NULL ولا تعتبرها صفراً. في المثال ثلاثة طلبات ومبلغان معروفان فقط، مجموعهما 60.00 ومتوسطهما 30.00. يجيب عدد المبالغ المعروفة عن سؤال مختلف عن عدد الطلبات.
عند خلو الإدخال ودون GROUP BY تعيد COUNT صفراً وتعيد SUM القيمة NULL. ومع GROUP BY قد لا يوجد صف مجموعة أصلاً. قرر هل يجب عرض المجموعات الغائبة وهل الصفر مناسب للعرض. قد تنسق COALESCE المجموع الفارغ في النهاية، لكن استبدال NULL بالصفر قبل AVG يغير المقام وبالتالي معنى المقياس.
SELECT COUNT(*) AS orders,COUNT(Amount) AS known_amounts,
SUM(Amount) AS total,AVG(Amount) AS average_known
FROM (VALUES (CAST(20 AS decimal(10,2))),
(CAST(NULL AS decimal(10,2))),(40))v(Amount);
orders known_amounts total average_known
3 2 60.00 30.000000
المتوسط 60 مقسوماً على قيمتين معروفتين؛ وقد تختلف المنازل المعروضة بين الأدوات دون تغير القيمة.
صفِّ الصفوف قبل التجميع والمجموعات بعده
تقرر WHERE الصفوف الداخلة في التجميع، وتقرر HAVING المجموعات المكتملة التي تبقى. لتقرير العملاء الذين تتجاوز طلباتهم المفتوحة 50، احصر الإدخال أولاً في الطلبات المفتوحة ثم جمّع حسب CustomerId وطبق HAVING SUM(Amount) > 50. أما تصفية كل مبلغ أكبر من 50 فتلغي طلبات صغيرة قد تشكل معاً رصيداً كبيراً.
يبقي المثال العميل 1 لأن طلبين مفتوحين مجموعهما 70، ولا يشارك الطلب المغلق بقيمة 200. يبلغ مجموع العميل 2 مقدار 25 فيفشل HAVING. هذا فرق في المعنى حتى لو أعاد المحسّن ترتيب شروط آمنة داخلياً. اكتب الاستعلام بحيث يستطيع المراجع تتبع عضوية الصفوف واختيار المجموعات مستقلاً.
SELECT CustomerId,SUM(Amount) AS open_total
FROM (VALUES (1,N'Open',30),(1,N'Open',40),
(1,N'Closed',200),(2,N'Open',25))o(CustomerId,Status,Amount)
WHERE Status=N'Open'
GROUP BY CustomerId
HAVING SUM(Amount)>50
ORDER BY CustomerId;
CustomerId open_total
1 70
يجمع الصفان 30 و40 قبل اختبار HAVING، ويستبعد WHERE المبلغ المغلق.
ابنِ مقاييس متعددة من الإدخال نفسه
يضع التجميع المشروط CASE داخل دالة التجميع. تجمع SUM(CASE WHEN Status = N'Open' THEN Amount ELSE 0 END) المبالغ المؤهلة مع الاحتفاظ بصفوف المقاييس الأخرى. وللعد تستخدم SUM(CASE WHEN condition THEN 1 ELSE 0 END). وثّق هل تعني المبالغ المجهولة غياب معلومة أم تعامل كصفر في ذلك المقياس المحدد.
للأنواع العددية أثر: تقطع القسمة الصحيحة الجزء الكسري، وتعيد SUM(int) النوع int، مما قد يسبب تجاوز المدى. حوّل بقصد إلى decimal للنسب وإلى bigint للمجاميع الصحيحة الكبيرة عند الحاجة. احمِ المقام الصفري بـ NULLIF وحدد عرض NULL الناتجة. لا تجعل نسبة غير معرّفة تبدو خطأ كأنها صفر مقاس.
طابق المجاميع عند كل حد
تحقق من الملخص بمطابقة مجموعه مع صفوف الأساس ذات الشروط نفسها. أضف حالات بلا طلبات ومبلغ NULL ومبالغ متكررة وعدة بنود للطلب. لا تصلح SUM(DISTINCT Amount) تضاعف الربط؛ إذ تختزل طلبين صحيحين قيمة كل منهما 50 إلى قيمة واحدة. أصلح وحدة الصف قبل التجميع.
يجب أن تحدد مواصفات التقرير العملة ونطاق الحالات واصطلاح التاريخ ومعاملة الطلبات الملغاة أو المستردة. قد تجيب SUM الصحيحة رياضياً عن سؤال عمل خاطئ. ضع تعريف كل مقياس قرب تعبيره، وقارن مجموعات محسوبة يدوياً بالناتج قبل تحسين الأداء. الدقة وعضوية الصفوف شرطان للصحة وليستا تفصيلاً في العرض.
الأخطاء الشائعة وإصلاحها
Column is invalid in the select list.
- لماذا يحدث؟
- التعبير غير مجمّع ولا وارد في GROUP BY.
- كيف تصلحه؟
- جمّع بالمفتاح المقصود أو طبّق تجميعاً ذا معنى.
- كيف تتجنب تكراره؟
- حدد وحدة النتيجة قبل اختيار الأعمدة.
Divide by zero error encountered.
- لماذا يحدث؟
- قد يكون مقام النسبة صفراً.
- كيف تصلحه؟
- استخدم NULLIF(denominator,0) وحدد عرض النتيجة المفقودة.
- كيف تتجنب تكراره؟
- اختبر المجموعات الفارغة والصفرية.
Averages are unexpectedly low.
- لماذا يحدث؟
- حُولت NULL إلى صفر قبل المتوسط.
- كيف تصلحه؟
- احسب متوسط المعروف أو وثّق قاعدة إحلال الصفر عمداً.
- كيف تتجنب تكراره؟
- حدد المقام صراحة.
اختبر فهمك
1. لثلاثة صفوف مبالغ 0 و20 وNULL. احسب COUNT(*) وCOUNT(Amount) وAVG(Amount).
اعرض الحل المشروح
النتائج 3 و2 و10. الصفر قيمة معروفة تشارك في المتوسط؛ أما NULL فلا. استبدال NULL بالصفر أولاً ينتج مقياساً مختلفاً مقامه 3.
2. لطلبين بقيمة 50 لكل منهما بندان. هل تصلح SUM(DISTINCT header.Amount) ربط البنود؟
اعرض الحل المشروح
لا؛ تعيد 50 مع أن المجموع الصحيح 100. يكرر الربط قيم الرؤوس؛ احسب على مستوى الطلب أو لخّص الرؤوس مستقلة. اختلاف القيم لا يعني اختلاف الطلبات.
أهم ما تعلمته
- يحدد التجميع وحدة صف جديدة.
- تجيب COUNT(*) وCOUNT(column) عن سؤالين مختلفين.
- تحقق من العضوية والدقة قبل تحسين الأداء.