AzLearn

دوال النوافذ

Window Functions

مفهوم ~25 دقيقة

دوال النوافذ — Window Functions

GROUP BY يطوي عدة صفوف إلى صف واحد لكل مجموعة. أحياناً تريد حساب تجميع — مجموع، ترتيب، فرق عن الصف السابق — دون فقدان الصفوف الأصلية. هنا تأتي دوال النوافذ عبر الكلمة OVER.

دوال النوافذ متاحة في SQLite منذ الإصدار 3.25 (سبتمبر 2018)، PostgreSQL، MySQL 8+، وكل قواعد البيانات الحديثة. الأقدم من ذلك يحتاج subqueries مرتبطة وهي أبطأ بكثير.

الفكرة الأساسية: OVER لا يطوي الصفوف

قارن:

query.sql

الأول 2 صفوف. الثاني 5 صفوف، كل صف يعرف مجموع عميله بجانبه.

ROW_NUMBER() — ترقيم تسلسلي

أعطِ كل صف رقماً حسب ترتيب معيّن:

query.sql

النتيجة: العميل 1 يحصل على 1، 2، 3 لطلباته الثلاثة. العميل 2 يحصل على 1، 2.

استخدامه الشائع: “آخر طلب لكل عميل” بكتابة WHERE order_seq = 1 بعد ترتيب تنازلي.

RANK و DENSE_RANK — التعامل مع التعادل

ROW_NUMBER يعطي رقماً فريداً حتى عند التساوي. RANK يكرر الرقم ويقفز، DENSE_RANK يكرر دون قفز:

query.sql
playerscorernrkdr
Aziz90111
Sara90211
Omar80332
Lina70443

اختر بناءً على معنى الترتيب: في المسابقات تستخدم RANK (ميدالية فضية مكررة، لا برونزية). في تقارير “أعلى N” تستخدم ROW_NUMBER لتفادي التساوي.

LAG و LEAD — الصف السابق والتالي

LAG يسحب قيمة من صف سابق ضمن النافذة، LEAD من لاحق. مفيد لحساب الفروقات والاتجاهات:

query.sql

أول طلب لكل عميل يأخذ NULL في prev_order_at لأنه لا يوجد قبله شيء. هذا متوقع وغالباً مفيد للفلترة.

SUM() OVER (...) — مجاميع تراكمية

أضف ORDER BY داخل OVER لتحوّل المجموع من إجمالي إلى تراكمي حتى الصف الحالي:

query.sql
dayamountrunning_total
2026-04-011200012000
2026-04-02800020000
2026-04-031500035000
2026-04-04500040000

نفس الفكرة مع AVG، COUNT، أو MAX. أضف PARTITION BY لإعادة العد في كل مجموعة (مثلاً تراكمي شهري لكل قناع بيع).

مثال مركّب: ترتيب أكبر طلب لكل عميل

query.sql

النتيجة: أكبر طلب لكل عميل في صف واحد — نمط شائع جداً للوحات التقارير.

متى لا تستخدم النافذة

  • إذا كنت تريد طي الصفوف فعلاً، GROUP BY أوضح وأسرع.
  • إذا كان الترتيب لا معنى له (مثلاً ROW_NUMBER() بدون ORDER BY يعطي ترتيباً غير محدد) — سيعمل لكن النتيجة متذبذبة بين التشغيلات.

التحدي

من الجدول التالي، اعرض آخر طلب (الأحدث) لكل عميل في صف واحد. استخدم ROW_NUMBER داخل CTE.

تحدي — Challenge