{"id":"l-sql-3-3","courseId":"sql-for-analysts","lang":"ar","kind":"lesson","summary":"حوّل جدولًا شهريًا أو يوميًا إلى اتجاه بالمجاميع التراكمية، وإعادة الضبط مع بداية كل سنة، ومقارنات LAG وLEAD، والمتوسطات المتحركة على إطارات ROWS صريحة، بما في ذلك ملء الأيام المفقودة أولًا.","html":"<p>تحتاج المراجعة الشهرية لأعمال رانيا إلى ثلاثة أشياء في شريحة واحدة: الإيراد حسب الشهر، وما تراكم من إجمالي السنة حتى الآن، وهل نما كل شهر مقارنةً بسابقه. يمكنك حساب رقم ما منذ بداية السنة في جدول بيانات لاحقًا. لكن دوال النافذة نفسها التي رتّبت المنتجات في الدرس الماضي تستطيع أيضًا أن <em>تراكم</em> و<em>تقارن</em>، فيخرج الجدول كله من SQL مباشرةً، وبطريقة قابلة لإعادة الإنتاج.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">المجاميع التراكمية ومنذ بداية السنة</h2>\n<p>دالة التجميع كدالة نافذة مع <code dir=\"ltr\">ORDER BY</code> داخل <code dir=\"ltr\">OVER</code> تصبح حسابًا تراكميًا: كل صف يرى الصفوف حتى نفسه. أضف <code dir=\"ltr\">PARTITION BY</code> فيبدأ الحساب من جديد لكل مجموعة. هذه طلبات Cartwheel الشهرية مع عدد منذ بداية السنة يبدأ من جديد في كل يناير:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> strftime(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'%Y-%m'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    PARTITION</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> substr(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_ytd</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 14</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>مفتاح الـ partition هو السنة، مأخوذة من الأحرف الأربعة الأولى في تسمية الشهر. يغلق ديسمبر 2024 السنة على 552 طلبًا، ويبدأ يناير 2025 من جديد بـ 84.</p>\n<p>نافذتان على الصف نفسه تتكاملان جيدًا. قسمة المجموع التراكمي على مجموع الـ partition كله، <code dir=\"ltr\">100.0 * SUM(orders) OVER (PARTITION BY substr(month, 1, 4) ORDER BY month) / SUM(orders) OVER (PARTITION BY substr(month, 1, 4))</code>، تعطي النسبة التراكمية من السنة التي بلغها كل شهر. في نشاط موسمي مثل Cartwheel، هذا هو الرقم الذي تتابعه المالية فعلًا: بنهاية أكتوبر 2025 كان قد أُنجز أقل بقليل من ثلثي السنة.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسيان ORDER BY داخل OVER</span></p>\n<p><code dir=\"ltr\">SUM(orders) OVER ()</code> و<code dir=\"ltr\">SUM(orders) OVER (PARTITION BY year)</code> لا ينتجان مجاميع تراكمية؛ كل صف يحصل على المجموع الكامل لنافذته. الـ ORDER BY داخل القوسين هو ما يجعل النافذة تكبر صفًا بعد صف. إذا عرض كل صف في «مجموعك التراكمي» الرقم نفسه، فهذه هي القطعة الناقصة.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">المقارنة بالصف السابق</h2>\n<p>يعيد <code dir=\"ltr\">LAG(x)</code> قيمة <code dir=\"ltr\">x</code> من الصف السابق بترتيب النافذة، ويعيد <code dir=\"ltr\">LEAD(x)</code> قيمتها من الصف التالي. بعدها يصبح التغيّر بين فترتين عملية حسابية بسيطة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> strftime(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'%Y-%m'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> prev_month,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> change,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        /</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> NULLIF</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> change_pct,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  LAG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> same_month_last_year</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthly</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> DESC</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>قفز نوفمبر 2025 بـ 193 طلبًا، ثم هبط ديسمبر بـ 140: Black Friday، ثم الركود الذي يليه. الأرقام الشهرية المتتالية مشوّشة في نشاط موسمي، ولهذا يستخدم العمود الأخير <code dir=\"ltr\">LAG(orders, 12)</code>، أي القيمة قبل اثني عشر صفًا، لمنظور سنة مقابل سنة: 325 طلبًا في نوفمبر هذا العام مقابل 120 في نوفمبر الماضي.</p>\n<p>لا يكون <code dir=\"ltr\">LAG(orders, 12)</code> «الشهر نفسه في السنة الماضية» إلا إذا كان لكل شهر صف. الشهر الذي لا طلبات فيه لن يكون له صف، وستنزاح كل مقارنة بعده شهرًا واحدًا بصمت. وهذا الافتراض على وشك أن يصبح مهمًا.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">المتوسطات المتحركة والإطار</h2>\n<p>المتوسط المتحرك يخفّف التشويش اليومي. تحتاج النافذة إلى <strong>إطار</strong> (frame) صريح، أي نطاق الصفوف حول الصف الحالي الذي تراه دالة التجميع: <code dir=\"ltr\">ROWS BETWEEN 6 PRECEDING AND CURRENT ROW</code> هو الصف الحالي مع الستة التي قبله.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 170\" role=\"img\" aria-labelledby=\"l-sql-3-3-t1\"><defs><marker id=\"arrow-l-sql-3-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sql-3-3-t1\">عشرة صفوف يومية مرتّبة. للصف الحالي، اليوم 9، يغطي الإطار ROWS BETWEEN 6 PRECEDING AND CURRENT ROW الأيام من 3 إلى 9، أي سبعة صفوف. وحين يتقدّم الصف الحالي يومًا واحدًا، يتحرك الإطار معه.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"50\" y=\"85\" text-anchor=\"middle\">d1</text>\n  <rect class=\"d-box\" x=\"88\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"118\" y=\"85\" text-anchor=\"middle\">d2</text>\n  <rect class=\"d-box-accent\" x=\"156\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"186\" y=\"85\" text-anchor=\"middle\">d3</text>\n  <rect class=\"d-box-accent\" x=\"224\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"254\" y=\"85\" text-anchor=\"middle\">d4</text>\n  <rect class=\"d-box-accent\" x=\"292\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"322\" y=\"85\" text-anchor=\"middle\">d5</text>\n  <rect class=\"d-box-accent\" x=\"360\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"390\" y=\"85\" text-anchor=\"middle\">d6</text>\n  <rect class=\"d-box-accent\" x=\"428\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"458\" y=\"85\" text-anchor=\"middle\">d7</text>\n  <rect class=\"d-box-accent\" x=\"496\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"526\" y=\"85\" text-anchor=\"middle\">d8</text>\n  <rect class=\"d-box-primary\" x=\"564\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-label-strong\" x=\"594\" y=\"85\" text-anchor=\"middle\">d9</text>\n  <rect class=\"d-box\" x=\"632\" y=\"60\" width=\"60\" height=\"40\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"662\" y=\"85\" text-anchor=\"middle\">d10</text>\n  <path class=\"d-line\" d=\"M156 120 L624 120\"/>\n  <path class=\"d-line\" d=\"M156 112 L156 128\"/>\n  <path class=\"d-line\" d=\"M624 112 L624 128\"/>\n  <text class=\"d-label\" x=\"390\" y=\"150\" text-anchor=\"middle\">الإطار: من 6 PRECEDING إلى CURRENT ROW (7 صفوف)</text>\n  <text class=\"d-label-muted\" x=\"594\" y=\"40\" text-anchor=\"middle\">الصف الحالي</text>\n</svg><figcaption>إطار ROWS ينزلق على الصفوف المرتّبة</figcaption></figure>\n<p>يعدّ الإطار <strong>الصفوف</strong> لا الأيام. إذا لم يكن في يوم ما طلبات، فلا صف له، ويمتد الإطار بصمت إلى زمن أبعد. يُظهر الشهر الأول في Cartwheel مدى سوء هذا التضليل: في يناير 2024 وصلت 8 طلبات فقط، في 8 أيام مختلفة. المتوسط على 7 صفوف من الصفوف الموجودة يقول «طلب واحد يوميًا» طوال الشهر. والحقيقة نحو ربع ذلك.</p>\n<p>الإصلاح هو تقويم: ولّد صفًا لكل يوم بـ CTE تكراري، واربط به الأعداد اليومية بـ LEFT JOIN، وحوّل الأيام المفقودة إلى أصفار:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WITH</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> RECURSIVE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  UNION ALL</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'+1 day'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-31'</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">daily </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(order_date) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-02-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">filled </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">COALESCE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">orders</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> days</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  LEFT JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> daily </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> x </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">smoothed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, orders,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">AVG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">           ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ROWS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> PRECEDING</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> CURRENT </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ROW</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">         ), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_7d_avg</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> filled</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> smoothed</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2024-01-25'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>الآن يعرض 31 يناير 0.43 طلب يوميًا خلال الأسبوع الأخير، وهو الرقم الصادق. لاحظ موضع شرط التاريخ: في الـ SELECT الأخير، <em>بعد</em> حساب المتوسط المتحرك في <code dir=\"ltr\">smoothed</code>. تعمل دوال النافذة بعد WHERE، فالتصفية في الـ query نفسه ستترك الأيام الأولى المعروضة بلا تاريخ سابق يُحسب متوسطه. وحيلة التقويم نفسها تحمي <code dir=\"ltr\">LAG(orders, 12)</code> من الأشهر المفقودة.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>ROWS، لا الإطار الافتراضي</span></p>\n<p>مع ORDER BY وبلا إطار، تستخدم دالة التجميع كدالة نافذة <code dir=\"ltr\">RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code>، الذي يعامل الصفوف المتساوية في قيمة الترتيب كخطوة واحدة: تحصل كلها على المجموع التراكمي نفسه. حين لا يكون مفتاح الترتيب فريدًا، اكتب <code dir=\"ltr\">ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code> لتحصل على مجموع تراكمي صارم صفًا بصف.</p>\n</aside>\n<p>أصبحت قادرًا على الترتيب والمراكمة والمقارنة. يضع الدرس التالي هذه الأدوات في خدمة التحليلين اللذين يطلبهما كل فريق منتج: القمع (funnel) ومعدل الاحتفاظ (retention) لكل cohort.</p>\n","toc":[{"id":"sec-1","text":"المجاميع التراكمية ومنذ بداية السنة","level":2},{"id":"sec-2","text":"المقارنة بالصف السابق","level":2},{"id":"sec-3","text":"المتوسطات المتحركة والإطار","level":2}],"takeaways":["SUM(x) OVER (ORDER BY period) مجموع تراكمي؛ وإضافة PARTITION BY year تجعله يبدأ من جديد كل سنة، أي المجموع منذ بداية السنة.","يقرأ LAG(x) قيمة الصف السابق ويقرأ LEAD(x) قيمة الصف التالي، فيكون التغيّر بين فترتين متتاليتين x - LAG(x).","يحتاج المتوسط المتحرك (moving average) إلى إطار صريح، مثل ROWS BETWEEN 6 PRECEDING AND CURRENT ROW، وإلى صف لكل فترة، بما فيها الفترات الفارغة.","تعمل دوال النافذة بعد WHERE، لذا صفِّ النطاق المعروض في query خارجي، وإلا لن ترى النافذة إلا الصفوف المصفّاة."],"further":[{"title":"SQLite window functions, frame specifications","url":"https://www.sqlite.org/windowfunctions.html#frame_specifications"},{"title":"PostgreSQL window function calls","url":"https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS"}],"quiz":[{"q":"<p>يُضاف <code dir=\"ltr\">SUM(revenue) OVER ()</code> إلى جدول إيرادات شهري. ماذا يعرض كل صف؟</p>","options":[{"text":"المجموع التراكمي حتى ذلك الشهر.","why":"المجموع التراكمي يحتاج إلى ORDER BY داخل OVER. بدونه تكون النافذة هي النتيجة كلها."},{"text":"إيراد الشهر السابق.","why":"هذا هو LAG(revenue) OVER (ORDER BY month)."},{"text":"إيراد ذلك الشهر، دون تغيير.","why":"SUM كدالة نافذة يجمع كل صف في النافذة، وهي هنا كل الصفوف، لا الصف الحالي وحده."},{"text":"المجموع الكلي لكل الأشهر.","why":"صحيح. الـ OVER () الفارغ ينشئ نافذة واحدة تضم كل الصفوف، فيحصل كل صف على المجموع نفسه."}],"answer":3},{"q":"<p>الطلبات الشهرية 84 و63 و70 من يناير إلى مارس. ماذا يعيد <code dir=\"ltr\">orders - LAG(orders) OVER (ORDER BY month)</code> ليناير وفبراير ومارس؟</p>","options":[{"text":"0، ‎-21، 7","why":"لا يوجد صف سابق ليناير، فيعيد LAG القيمة NULL، وكذلك عملية الطرح، لا 0."},{"text":"‎-21، 7، NULL","why":"هذه هي النسخة المبنية على LEAD، التي تقارن كل شهر بالشهر الذي يليه."},{"text":"NULL، ‎-21، 7","why":"صحيح. لا صف سابق ليناير؛ وفبراير هو 63 - 84؛ ومارس هو 70 - 63. مرّر قيمة افتراضية، <code dir=\"ltr\">LAG(orders, 1, 0)</code>، فقط إذا كان 0 هو خط الأساس الصحيح فعلًا."}],"answer":2},{"q":"<p>الطلبات اليومية مفقودة في الأيام التي لا مبيعات فيها. ما الخطأ في <code dir=\"ltr\">AVG(orders) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)</code> على هذا الجدول؟</p>","options":[{"text":"الإطار يغطي آخر 7 صفوف، وقد تمتد على أكثر بكثير من 7 أيام، فتُتجاهل الأيام الهادئة ويكون المتوسط مرتفعًا أكثر من اللازم.","why":"صحيح. ROWS يعدّ الصفوف لا التواريخ. املأ التقويم بصف لكل يوم (0 طلبات) قبل حساب المتوسط."},{"text":"لا يمكن استخدام AVG كدالة نافذة.","why":"كل دالة تجميع، بما فيها AVG، يمكن استخدامها مع OVER."},{"text":"إطارات ROWS غير مدعومة في SQLite.","why":"يدعم SQLite إطارات ROWS منذ وصول دوال النافذة في الإصدار 3.25 (عام 2018)."}],"answer":0},{"q":"<p>يُفترض أن يعرض هذا الـ query متوسطًا متحركًا لسبعة أيام للأسبوع الأخير من ديسمبر فقط. لماذا تأتي القيم الأولى منخفضة أكثر من اللازم؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">AVG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">OVER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ROWS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 6</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> PRECEDING</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> CURRENT </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ROW</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> filled_days</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> day</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-12-25'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n","options":[{"text":"يجب أن يكون الإطار RANGE لا ROWS.","why":"نوع الإطار ليس المشكلة. الأيام السابقة التي يحتاجها الإطار حذفها WHERE قبل أن تعمل النافذة، ولا يستطيع أي إطار أن يرى صفوفًا غير موجودة."},{"text":"AVG يتجاهل الأيام التي فيها 0 طلبات.","why":"AVG يتجاهل NULL لا الأصفار. الأصفار تدخل في المتوسط كأي رقم آخر."},{"text":"يجب أن يكون ORDER BY تنازليًا.","why":"الترتيب التنازلي سيجعل الإطار ينظر إلى الأمام في الزمن، وهذا ليس متوسطًا للأيام السابقة."},{"text":"WHERE يعمل قبل النافذة، فلا يجد الإطار في 25 ديسمبر أيامًا سابقة يحسب متوسطها.","why":"صحيح. احسب المتوسط المتحرك على النطاق الكامل في CTE، ثم صفِّ الأيام التي تريد عرضها في الـ query الخارجي."}],"answer":3}],"exercise":{"kind":"sql","starter":"-- 2025 monthly revenue: running total and change vs previous month\nWITH monthly AS (\n  SELECT\n    strftime('%Y-%m', o.order_date) AS month,\n    SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS revenue\n  FROM orders AS o\n  JOIN order_items AS oi ON oi.order_id = o.order_id\n  WHERE o.status <> 'cancelled'\n    AND o.order_date >= '2025-01-01'\n    AND o.order_date < '2026-01-01'\n  GROUP BY month\n)\nSELECT\n  month,\n  ROUND(revenue, 2) AS revenue,\n  ROUND(SUM(revenue) OVER (), 2) AS running_revenue,\n  ROUND(revenue - LEAD(revenue) OVER (ORDER BY month), 2) AS mom_change\nFROM monthly\nORDER BY month;","solution":"WITH monthly AS (\n  SELECT\n    strftime('%Y-%m', o.order_date) AS month,\n    SUM(oi.quantity * oi.unit_price * (1 - oi.discount)) AS revenue\n  FROM orders AS o\n  JOIN order_items AS oi ON oi.order_id = o.order_id\n  WHERE o.status <> 'cancelled'\n    AND o.order_date >= '2025-01-01'\n    AND o.order_date < '2026-01-01'\n  GROUP BY month\n)\nSELECT\n  month,\n  ROUND(revenue, 2) AS revenue,\n  ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_revenue,\n  ROUND(revenue - LAG(revenue) OVER (ORDER BY month), 2) AS mom_change\nFROM monthly\nORDER BY month;","orderMatters":true,"title":"اتجاه إيرادات رانيا","prompt":"<p>ابنِ جدول الشريحة لرانيا: صف واحد لكل شهر في 2025 (الطلبات غير الملغاة) فيه <code dir=\"ltr\">month</code> (‘YYYY-MM’)، و<code dir=\"ltr\">revenue</code>، و<code dir=\"ltr\">running_revenue</code> (الإيراد المتراكم من يناير حتى ذلك الشهر شاملًا إياه)، و<code dir=\"ltr\">mom_change</code> (إيراد هذا الشهر ناقص إيراد الشهر السابق؛ وNULL ليناير). قرّب أعمدة المال إلى منزلتين عشريتين ورتّب حسب <code dir=\"ltr\">month</code>. الترتيب مهم.</p>\n<p>الـ CTE المسمّى <code dir=\"ltr\">monthly</code> صحيح. أما عمودا النافذة في الـ SELECT الأخير فكلاهما خاطئ.</p>\n","hints":["كل صف في <code dir=\"ltr\">running_revenue</code> يعرض الرقم نفسه. ما الذي ينقص داخل <code dir=\"ltr\">OVER ()</code>؟","استخدم <code dir=\"ltr\">SUM(revenue) OVER (ORDER BY month)</code> للمجموع التراكمي.","LEAD ينظر إلى الصف التالي. للمقارنة بالشهر السابق، استخدم <code dir=\"ltr\">revenue - LAG(revenue) OVER (ORDER BY month)</code>."],"explanation":"<p>الـ <code dir=\"ltr\">OVER ()</code> الفارغ ينشئ نافذة واحدة تضم الأشهر الاثني عشر كلها، فطبع الكود المبدئي المجموع السنوي في كل صف. إضافة <code dir=\"ltr\">ORDER BY month</code> تحوّلها إلى نافذة تكبر تدريجيًا، أي مجموع تراكمي. وللتغيّر الشهري تحتاج إلى الصف السابق، أي LAG؛ أما LEAD فقارن كل شهر بالشهر التالي وترك ديسمبر NULL بدل يناير. الإيراد التراكمي لديسمبر يساوي إجمالي السنة، وهذا فحص مطابقة مجاني مقابل جدول الفئات من درس الـ JOIN.</p>\n","checkLabels":{}},"words":641,"readMinutes":4}