SQLite / المتوسط: بنية الاستعلام وتصميم المعاملة / الدرس 11
الدوال النافذية والترتيب وإطارات الحساب
احسب الرتب داخل المجموعات والمقاييس التراكمية مع حفظ صفوف التفاصيل، والتصريح بقواعد التعادل والإطار وترتيب العرض النهائي.
في هذه الصفحة
أهداف الدرس
- ميّز الحساب النافذي من التجميع.
- اختر صفاً واحداً من كل قسم بصورة حتمية.
- اختر إطار ROWS وفسر القيم المجاورة.
احتفظ بالتفاصيل أثناء حساب القيم المرتبطة
يختزل GROUP BY المدخلات إلى صفوف مجموعات، بينما تحسب الدالة النافذية عبر صفوف مرتبطة دون اختزالها بطبيعتها. يحدد PARTITION BY الأقسام المستقلة، ويحدد ORDER BY النافذي تسلسل الحساب، ويقيد الإطار الصفوف المساهمة في التجميع. ويبقى ORDER BY الخارجي للعرض. هذه عقود منفصلة ولو تشابهت التعبيرات.
يدعم إصدار الدورة صياغة النوافذ القياسية الموثقة. تظهر الدوال في تعبيرات النتائج ومواضع الترتيب المناسبة، ولا تتجاوز المعالجة المنطقية لـ WHERE. تحتاج تصفية الرتبة عادة طبقة استعلام أخرى. اختبر قسماً بصف واحد، وأقساماً بعدة صفوف وتعادلات قبل تطبيق النمط على تقرير كبير.
حدد سياسة التعادل عند اختيار الأحدث لكل مجموعة
تعطي ROW_NUMBER مواضع متمايزة داخل كل قسم. أما RANK وDENSE_RANK فتعالجان التعادل بسياستين مختلفتين، وتمنحان المتعادلين رتبة مشتركة بدلاً من اختيار فائز اعتباطي دائماً. لذلك يجب أن توضح قاعدة «أحدث طلب لكل عميل» كيفية حسم تساوي الأوقات. يرتب النص الأول التاريخ تنازلياً ثم المعرّفات الفريدة تنازلياً، ويصفي rn=1 عبر CTE.
يُختار id2 للعميل 1 من التاريخ المتعادل، وid3 للعميل 2، ويُرتب العرض النهائي بحسب العميل. لا يصح الفاصل الفريد إلا إذا أراد المنتج فائزاً واحداً. أما حفظ جميع الأحدث المتعادلين فيحتاج قاعدة ترتيب وعدداً متوقعاً مختلفين. الترتيب اختيار دلالي، لا تقنية عامة لإزالة التكرار.
CREATE TABLE purchase(id INTEGER PRIMARY KEY,customer INTEGER,created_on TEXT,amount INTEGER);
INSERT INTO purchase VALUES(1,1,'20260101',10),(2,1,'20260101',20),(3,2,'20260102',15);
WITH ranked AS (SELECT *,ROW_NUMBER() OVER
(PARTITION BY customer ORDER BY created_on DESC,id DESC) AS rn FROM purchase)
SELECT customer,id,amount FROM ranked WHERE rn=1 ORDER BY customer;
customer|id|amount
1|2|20
2|3|15
يفوز id2 بتعادل التاريخ للعميل 1، ويمثل id3 الصف الوحيد للعميل 2. ويتحكم الاستعلام الخارجي في ترتيب العرض.
أظهر حدود التراكم صراحة
يراكم SUM(amount) OVER(ORDER BY id ROWS UNBOUNDED PRECEDING) القيم من بداية القسم إلى الصف الحالي وفق هذا التسلسل. ينتج النص الثاني 10 ثم 30 ثم 35، ويستخدم LAG لإظهار مبلغ الصف السابق. تكون القيمة السابقة الأولى NULL لعدم وجود صف قبلها؛ أما اختيار قيمة بديلة للعرض فسياسة مستقلة لا يقررها الحساب نفسه.
قد تشمل الإطارات الافتراضية مجموعات الصفوف المتعادلة وفق دلالة RANGE عند تساوي قيم الترتيب. لذلك قد يقفز المجموع الجاري لجميع المتعادلين بدلاً من التقدم صفاً صفاً. اختر إطار ROWS صريحاً وتسلسلاً حتمياً عندما تقصد التقدم الفردي. ولا تفترض انطباق الإطار نفسه بالمعنى ذاته على كل دالة ترتيب أو إزاحة مدمجة؛ اقرأ سلوك كل دالة في الوثائق.
CREATE TABLE movement(id INTEGER PRIMARY KEY,amount INTEGER);
INSERT INTO movement VALUES(1,10),(2,20),(3,5);
SELECT id,amount,
SUM(amount) OVER(ORDER BY id ROWS UNBOUNDED PRECEDING) AS running,
LAG(amount) OVER(ORDER BY id) AS previous
FROM movement ORDER BY id;
id|amount|running|previous
1|10|10|NULL
2|20|30|10
3|5|35|20
يضاف مبلغ الصف الحالي إلى مبالغ الصفوف الأسبق. وتقرأ LAG الصف السابق، وهو غير موجود عند id1.
ميّز آخر الإطار من آخر القسم
تعتمد دوال مثل first_value وlast_value على الإطار؛ فقد تعني «القيمة الأخيرة» آخر صف بالإطار الحالي، لا آخر القسم. صرح بما إذا كانت الصفوف اللاحقة ستشارك. وللحصول على الأخيرة في القسم الكامل، تحتاج إطاراً مناسباً ينتهي بـ UNBOUNDED FOLLOWING، مع ترتيب محدد وسياسة تعادل واضحة.
تصف إزاحة مثل LAG موضع صف، لا فترة تقويمية بالضرورة. فإذا غابت تواريخ، لا يكون الصف السابق «أمس» تلقائياً. أنشئ تقويماً أو نموذج فواصل محدداً عندما يتعلق السؤال بالفجوات الزمنية. وتختلف القيمة المفقودة داخل صف موجود عن غياب الصف نفسه؛ وقد يخفي وسيط افتراضي هذا الفرق إن استُخدم دون عناية.
تحقق من هوية التفاصيل وكلفة الفرز
قارن مخرجات النوافذ بأقسام محلولة يدوياً تشمل تعادلاً وقيمة مفقودة وأحجام مجموعات متفاوتة. احتفظ بمعرّفات ثابتة حتى لا تختلط الرتبة المحسوبة بالهوية الدائمة للصف. ويختلف حساب الترتيب على مجموعة صُفيت مسبقاً عن ترتيب جميع الصفوف ثم تصفيتها؛ فاختر مرحلة التصفية بقصد لأنها تغير السؤال الذي تجيب عنه النتيجة.
قد يتطلب ترتيب النافذة فرزاً أو يستفيد من فهارس مركبة، لكن قصر التعبير لا يضمن قلة العمل. افحص خطة ممثلة وقارن صحة النتائج أولاً. وتوضح النوافذ المسماة التعريفات المتكررة دون أن تعد بأداء أفضل. يصرح التقرير بالقسم والترتيب والإطار ودلالة الغياب، وبحفظ مجتمع صفوف التفاصيل أو تغييره عمداً.
الأخطاء الشائعة وإصلاحها
يتغير الفائز بوصفه الأحدث عند تعادل الأوقات.
- لماذا يحدث؟
- ترتيب حساب الرتبة غير مكتمل.
- كيف تصلحه؟
- أضف قاعدة الحسم الفريدة المقصودة، أو احتفظ بالتعادل برتبة مناسبة.
- كيف تتجنب تكراره؟
- اختبر صفوفاً متساوية في قيمة الترتيب.
يقفز المجموع الجاري عبر القيم المتساوية.
- لماذا يحدث؟
- يشمل الإطار المختار أو الافتراضي الصفوف المتعادلة.
- كيف تصلحه؟
- استخدم إطار ROWS المقصود مع ترتيب حتمي.
- كيف تتجنب تكراره؟
- راجع حدود الإطار بصورة صريحة.
سُمّي الصف السابق «أمس» دون تحقق.
- لماذا يحدث؟
- اختلطت إزاحة الصف بالفاصل التقويمي.
- كيف تصلحه؟
- حدد معالجة التواريخ الغائبة وبنية التقويم.
- كيف تتجنب تكراره؟
- اختبر فجوات في تسلسل التواريخ.
اختبر فهمك
1. ما نتائج RANK وDENSE_RANK للدرجات المرتبة تنازلياً 90 و90 و80؟
اعرض الحل المشروح
تعطي RANK القيم 1 و1 و3، وتعطي DENSE_RANK القيم 1 و1 و2. أما ROW_NUMBER فتعطي مواضع متمايزة، وتحتاج قاعدة لحسم الاختيار بين صفي 90. اختر السياسة وفق رغبة الأعمال في حفظ التعادل أو اختيار صف واحد.
2. لماذا قد لا تعني LAST_VALUE مع الإطار المرتب الافتراضي القيمة النهائية للقسم؟
اعرض الحل المشروح
تقيد النتيجة بالإطار الحالي الذي قد ينتهي عند الصف الحالي أو مجموعة المتعادلين. إذا كان المطلوب القسم كله، فحدد إطاراً ينتهي بـ UNBOUNDED FOLLOWING، مع الإبقاء على عقد ترتيب مكتمل بدلاً من الاعتماد على الاسم وحده.
أهم ما تعلمته
- تحسب النوافذ قيماً مرتبطة مع الاحتفاظ بصفوف التفاصيل.
- تحتاج التعادلات وحدود الإطار والمعنى التقويمي قرارات منفصلة.
- يبقى العرض النهائي بحاجة إلى ORDER BY نهائي.