دوال النوافذ
Window Functions
دوال النوافذ — Window Functions
GROUP BY يطوي عدة صفوف إلى صف واحد لكل مجموعة. أحياناً تريد حساب تجميع — مجموع، ترتيب، فرق عن الصف السابق — دون فقدان الصفوف الأصلية. هنا تأتي دوال النوافذ عبر الكلمة OVER.
دوال النوافذ متاحة في SQLite منذ الإصدار 3.25 (سبتمبر 2018)، PostgreSQL، MySQL 8+، وكل قواعد البيانات الحديثة. الأقدم من ذلك يحتاج subqueries مرتبطة وهي أبطأ بكثير.
الفكرة الأساسية: OVER لا يطوي الصفوف
قارن:
الأول 2 صفوف. الثاني 5 صفوف، كل صف يعرف مجموع عميله بجانبه.
ROW_NUMBER() — ترقيم تسلسلي
أعطِ كل صف رقماً حسب ترتيب معيّن:
النتيجة: العميل 1 يحصل على 1، 2، 3 لطلباته الثلاثة. العميل 2 يحصل على 1، 2.
استخدامه الشائع: “آخر طلب لكل عميل” بكتابة WHERE order_seq = 1 بعد ترتيب تنازلي.
RANK و DENSE_RANK — التعامل مع التعادل
ROW_NUMBER يعطي رقماً فريداً حتى عند التساوي. RANK يكرر الرقم ويقفز، DENSE_RANK يكرر دون قفز:
| player | score | rn | rk | dr |
|---|---|---|---|---|
| Aziz | 90 | 1 | 1 | 1 |
| Sara | 90 | 2 | 1 | 1 |
| Omar | 80 | 3 | 3 | 2 |
| Lina | 70 | 4 | 4 | 3 |
اختر بناءً على معنى الترتيب: في المسابقات تستخدم RANK (ميدالية فضية مكررة، لا برونزية). في تقارير “أعلى N” تستخدم ROW_NUMBER لتفادي التساوي.
LAG و LEAD — الصف السابق والتالي
LAG يسحب قيمة من صف سابق ضمن النافذة، LEAD من لاحق. مفيد لحساب الفروقات والاتجاهات:
أول طلب لكل عميل يأخذ NULL في prev_order_at لأنه لا يوجد قبله شيء. هذا متوقع وغالباً مفيد للفلترة.
SUM() OVER (...) — مجاميع تراكمية
أضف ORDER BY داخل OVER لتحوّل المجموع من إجمالي إلى تراكمي حتى الصف الحالي:
| day | amount | running_total |
|---|---|---|
| 2026-04-01 | 12000 | 12000 |
| 2026-04-02 | 8000 | 20000 |
| 2026-04-03 | 15000 | 35000 |
| 2026-04-04 | 5000 | 40000 |
نفس الفكرة مع AVG، COUNT، أو MAX. أضف PARTITION BY لإعادة العد في كل مجموعة (مثلاً تراكمي شهري لكل قناع بيع).
مثال مركّب: ترتيب أكبر طلب لكل عميل
النتيجة: أكبر طلب لكل عميل في صف واحد — نمط شائع جداً للوحات التقارير.
متى لا تستخدم النافذة
- إذا كنت تريد طي الصفوف فعلاً،
GROUP BYأوضح وأسرع. - إذا كان الترتيب لا معنى له (مثلاً
ROW_NUMBER()بدونORDER BYيعطي ترتيباً غير محدد) — سيعمل لكن النتيجة متذبذبة بين التشغيلات.
التحدي
من الجدول التالي، اعرض آخر طلب (الأحدث) لكل عميل في صف واحد. استخدم ROW_NUMBER داخل CTE.