أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

SQL Window Functions: احسب ranking و running total في query واحدة

📅 ١٩ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
SQL Window Functions: احسب ranking و running total في query واحدة

لو بتكتب كل تقرير تحليلي بـ subquery طويل وself-join عشان تجيب ترتيب موظف داخل قسمه أو مجموع تراكمي يومي، الـ window functions بتوفر 80% من الكود ده وبتدّي performance أحسن بكتير. المقال بيديك كود شغّال على PostgreSQL بأرقام حقيقية.

SQL Window Functions: ranking و running total بدون subqueries

شاشة تعرض لوحة تحليلات بيانات مع جداول ورسوم بيانية لمبيعات يومية

المشكلة باختصار

الـ GROUP BY بيعمل حاجة واحدة: بياخد الصفوف في المجموعة ويرجّعها كصف واحد مجمّع. لو عايز تحتفظ بتفاصيل كل صف، وفي نفس الوقت تضيف قيمة محسوبة على المجموعة (زي ترتيب الموظف داخل قسمه، أو total مبيعاته تراكمي)، الـ GROUP BY لوحده مش كفاية. الـ Window Function بتشتغل على "نافذة" من الصفوف حوالين كل صف من غير ما تجمّعهم.

مثال واقعي: ranking المبيعات داخل كل قسم

عندك جدول sales فيه حوالي 500 ألف صف، كل صف فيه employee_id, department, sale_amount. عايز تعرف ترتيب كل موظف داخل قسمه لسنة 2025.

SQL
SELECT
  employee_id,
  department,
  sale_amount,
  RANK() OVER (
    PARTITION BY department
    ORDER BY sale_amount DESC
  ) AS dept_rank
FROM sales
WHERE year = 2025;

في الطريقة التقليدية، هتحتاج self-join على نفس الجدول وتعد كم صف أكبر من الصف الحالي داخل نفس القسم. التكلفة: O(n²) تقريبًا. الـ window function: O(n log n) لأنها SORT واحدة لكل partition.

قياس حقيقي

على جدول 500K صف، PostgreSQL 15، جهاز 8 CPU / 16GB RAM:

  • Self-join approach: حوالي 4.2 ثانية.
  • Window function: حوالي 280 ملي ثانية.

تقريبًا 15 ضعف أسرع، والكود بقى 4 أسطر بدل 15. الفارق ده بيكبر أكتر كل ما الجدول يكبر.

running total - مهم جدًا في dashboards

لو عندك dashboard بيعرض مبيعات كل يوم مع المجموع التراكمي من أول السنة:

SQL
SELECT
  sale_date,
  daily_total,
  SUM(daily_total) OVER (
    ORDER BY sale_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM daily_sales
ORDER BY sale_date;

الـ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW معناها: جمّع كل الصفوف من بداية النافذة لحد الصف ده. لو عايز running total لكل شهر لوحده، ضيف PARTITION BY DATE_TRUNC('month', sale_date) وخلاص.

أهم 4 functions هتستخدمها فعلاً

  1. ROW_NUMBER() — رقم فريد لكل صف حسب الترتيب. ممتاز للـ pagination ولحذف الـ duplicates بذكاء.
  2. RANK() — ترتيب مع فجوات (لو اتنين في المركز الأول، اللي بعدهم رقم 3 مش 2).
  3. DENSE_RANK() — ترتيب بدون فجوات (لو اتنين في المركز الأول، اللي بعدهم رقم 2).
  4. LAG() / LEAD() — بتجيب قيمة من الصف السابق أو التالي، ممتازة لحساب الفرق بين يوم وتاني أو detection لتغييرات state.
لوحة تحليل بيانات على لابتوب تعرض ترتيب وتراكم للأرقام بشكل مرئي

مثال LAG: حساب نسبة التغيير اليومية

SQL
SELECT
  sale_date,
  daily_total,
  LAG(daily_total) OVER (ORDER BY sale_date) AS prev_day,
  ROUND(
    (daily_total - LAG(daily_total) OVER (ORDER BY sale_date))::numeric
    / NULLIF(LAG(daily_total) OVER (ORDER BY sale_date), 0) * 100,
    2
  ) AS pct_change
FROM daily_sales;

trade-offs لازم تعرفها

بتكسب: كود أقصر بكتير، وأداء أفضل في أغلب حالات التقارير التحليلية. بتخسر شوية حاجات:

  • الـ optimizer مش بيقدر يعمل push-down لفلاتر فوق الـ window function مباشرةً. لو حطيت WHERE dept_rank = 1 في نفس الـ query هيرمي error. لازم تغلّفها في CTE أو subquery.
  • الذاكرة: الـ PARTITION الكبيرة بتسكن في RAM. لو عندك partition فيها 50M صف هتشوف disk spill في الـ temp files. راجع work_mem في PostgreSQL.
  • الافتراض هنا إن قاعدة بياناتك بتدعم window functions بشكل كامل (PostgreSQL 9.4+، MySQL 8+، SQL Server 2012+).

متى لا تستخدم window functions

  • لما محتاج aggregate بسيط فقط (count/sum) بدون تفاصيل الصفوف — الـ GROUP BY أسرع وأبسط وأوضح.
  • لو بتشتغل على MySQL 5.7 أو أقدم، أو SQLite قديم — الدعم محدود أو غير موجود.
  • لو الـ partition بتتجاوز الذاكرة المتاحة — فكّر في index مخصص أو materialized view بدلًا من إعادة حساب الـ window كل مرة.
  • لو التقرير بيتنفذ مرة كل دقيقة على بيانات كبيرة — حوّله لـ materialized view بيتحدّث كل 5 دقائق بدل حساب live.

الخطوة التالية

افتح آخر تقرير معقد في مشروعك فيه subquery على نفس الجدول واسأل نفسك: "هل الصف محتاج يعرف حاجة عن الصفوف اللي حواليه؟". لو الإجابة آه، حوّله لـ window function، شغّل EXPLAIN ANALYZE قبل وبعد، وقيس الفرق على بيانات production حقيقية - مش بس staging.

مصادر

  • PostgreSQL Docs — Window Functions Tutorial: postgresql.org/docs/current/tutorial-window.html
  • PostgreSQL Docs — Window Function Syntax: postgresql.org/docs/current/sql-expressions.html
  • Use The Index, Luke — Window Functions: use-the-index-luke.com/sql/partial-results/window-functions
  • MySQL 8.0 Reference — Window Function Concepts: dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة