{"id":"l-sql-3-2","courseId":"sql-for-analysts","lang":"ar","kind":"lesson","summary":"رتّب الصفوف داخل المجموعات باستخدام ROW_NUMBER وRANK وDENSE_RANK مع OVER (PARTITION BY … ORDER BY …)، وأجب عن أسئلة «أعلى N في كل مجموعة» و«الحدث الأول» دون أن تفقد الصفوف التفصيلية.","html":"<p>تريد هدى أن تعرض المنتجين الأكثر مبيعًا من كل فئة في النشرة البريدية لفصل الربيع. يمكنك الحصول على الإيراد لكل منتج بـ GROUP BY. لكن «أعلى اثنين <em>داخل كل فئة</em>» يحتاج إلى شيء لا يستطيعه GROUP BY: ترتيب الصفوف مقابل جيرانها مع إبقاء كل صف. هذه هي مهمة <strong>دوال النافذة</strong> (window functions)، وحين تمتلكها، تصبح فئة كاملة من الأسئلة قصيرة.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">إبقاء الصفوف مع النظر عبرها</h2>\n<p>يضغط <code dir=\"ltr\">GROUP BY</code> كل مجموعة في صف واحد. أما دالة النافذة فتحسب قيمة عبر مجموعة من الصفوف، هي <em>النافذة</em>، وتلحقها بكل صف، دون أن تضغط شيئًا.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 190\" role=\"img\" aria-labelledby=\"l-sql-3-2-t1\"><defs><marker id=\"arrow-l-sql-3-2-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-2-t1\">ثلاثة منتجات من فئة Yoga ومنتج واحد من فئة Kitchen مع إيراداتها في 2025. يعيد GROUP BY category صفّين، صفًا لكل فئة. ويعيد RANK على partition حسب الفئة الصفوف الأربعة كلها، مع ترتيب كل صف داخل فئته.</title>\n  <text class=\"d-label-strong\" x=\"120\" y=\"24\" text-anchor=\"middle\">إيراد المنتجات</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"36\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"56\" text-anchor=\"middle\">Yoga   Mat      4665</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"70\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"90\" text-anchor=\"middle\">Yoga   Cushion  3923</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"104\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"124\" text-anchor=\"middle\">Yoga   Block    1656</text>\n  <rect class=\"d-box-success\" x=\"20\" y=\"146\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"120\" y=\"166\" text-anchor=\"middle\">Kitchen Board   2039</text>\n  <text class=\"d-label-strong\" x=\"360\" y=\"24\" text-anchor=\"middle\">GROUP BY</text>\n  <rect class=\"d-box-accent\" x=\"270\" y=\"70\" width=\"180\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"360\" y=\"90\" text-anchor=\"middle\">Yoga    10244</text>\n  <rect class=\"d-box-success\" x=\"270\" y=\"146\" width=\"180\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"360\" y=\"166\" text-anchor=\"middle\">Kitchen  2039</text>\n  <text class=\"d-label-strong\" x=\"600\" y=\"24\" text-anchor=\"middle\">RANK() OVER</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"36\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"56\" text-anchor=\"middle\">Yoga   Mat      1</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"70\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"90\" text-anchor=\"middle\">Yoga   Cushion  2</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"104\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"124\" text-anchor=\"middle\">Yoga   Block    3</text>\n  <rect class=\"d-box-success\" x=\"490\" y=\"146\" width=\"220\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"600\" y=\"166\" text-anchor=\"middle\">Kitchen Board   1</text>\n</svg><figcaption>GROUP BY يضغط المجموعة؛ ودالة النافذة تضيف قيمة إلى كل صف</figcaption></figure>\n<p>الصياغة دالة يتبعها <code dir=\"ltr\">OVER (...)</code>. داخل القوسين، يقسم <code dir=\"ltr\">PARTITION BY</code> الصفوف إلى مجموعات، ويُعاد بدء الحساب في كل مجموعة؛ ويحدّد <code dir=\"ltr\">ORDER BY</code> الترتيب الذي تسير به الدالة. إذا حذفت PARTITION BY، أصبحت النتيجة كلها نافذة واحدة.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ROW_NUMBER وRANK وDENSE_RANK</h2>\n<p>لا تختلف دوال الترتيب الثلاث إلا في طريقة تعاملها مع التعادل. هؤلاء هم أكثر المشترين تكرارًا في الإمارات في 2025:</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\"> uae_orders </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\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> o</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">country</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'United Arab Emirates'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    AND</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">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:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> >=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-01-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_date</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2026-01-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  GROUP BY</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</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:#1F2328;--shiki-dark:#E6EDF3\">  customer_id,</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\">  ROW_NUMBER</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, customer_id) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> row_num,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  RANK</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> rnk,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  DENSE_RANK</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:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> dense_rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> uae_orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 8</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>قدّم العميلان 163 و534 كلاهما 11 طلبًا. ومع ذلك يعطيهما <code dir=\"ltr\">ROW_NUMBER</code> الرقمين 4 و5، أي موضعين فريدين، مستخدمًا <code dir=\"ltr\">customer_id</code> لكسر التعادل. ويعطي <code dir=\"ltr\">RANK</code> كليهما 4 ثم يقفز إلى 6، كجدول ترتيب الدوري الرياضي. ويعطي <code dir=\"ltr\">DENSE_RANK</code> كليهما 4 ويكمل بـ 5.</p>\n<p>اختر حسب السؤال. «صف واحد بالضبط لكل مجموعة» (آخر طلب، أول زيارة) يعني ROW_NUMBER. و«أعلى 3، مع كل من يتعادل في المركز الثالث» يعني RANK. و«أعلى ثلاث <em>قيم</em>» يعني DENSE_RANK.</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>ROW_NUMBER غير الحتمي</span></p>\n<p><code dir=\"ltr\">ROW_NUMBER() OVER (ORDER BY orders DESC)</code> بلا عمود لكسر التعادل يرقّم الصفوف المتعادلة بأي ترتيب يصادفها به المحرّك، وقد يتغيّر ذلك من تشغيل إلى آخر. التقرير الذي يقول «العميل 534 في المركز الرابع» يوم الاثنين و«الخامس» يوم الثلاثاء يفقد الثقة بسرعة. أنهِ كل ترتيب لـ ROW_NUMBER بعمود فريد.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">أعلى N في كل مجموعة</h2>\n<p>تعمل دوال النافذة بعد WHERE، في خطوة SELECT، فلا يمكنك التصفية على الترتيب في WHERE الخاص بالـ query نفسه. النمط المعتاد هو CTE يحسب الترتيب، وquery خارجي يصفّيه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-index=\"1\"><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\">WITH</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked </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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    category,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    product,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    revenue,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    RANK</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\">PARTITION</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> revenue </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> product_revenue</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:#1F2328;--shiki-dark:#E6EDF3\"> category, product, revenue, rnk</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rnk </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>يمثّل <code dir=\"ltr\">product_revenue</code> هنا الـ CTE الخاص بالإيراد لكل منتج الذي ستكتبه أولًا، مستخدمًا الـ JOIN من درس الـ JOIN. وبناؤه هو التمرين.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الأحداث الأولى: صف واحد لكل عميل</h2>\n<p>تجيب الأداة نفسها عن سؤال تنتظره هدى منذ أشهر: أي قناة تجلب عملاء <em>جددًا</em>؟ إنها قناة الطلب الأول لكل عميل، أي صف واحد لكل partition:</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:#1F2328;--shiki-dark:#E6EDF3\"> numbered </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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    customer_id,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    channel,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    ROW_NUMBER</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>\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\"> customer_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date, order_id</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\"> order_seq</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:#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\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> first_order_channel, </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\"> customers</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> numbered</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_seq </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> channel</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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>يجلب الويب 268 عميلًا، والتطبيق 172، والـ marketplace 63. قارن ذلك بإجمالي الطلبات حسب القناة وسترى هل يجد الـ marketplace مشترين جددًا في الغالب أم يخدم مشترين حاليين. والعمود <code dir=\"ltr\">order_seq</code> مفيد بحدّ ذاته أيضًا: <code dir=\"ltr\">order_seq = 2</code> يجد الطلبات الثانية، وهي بداية كل تحليل لتكرار الشراء.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">شرائح بدل المواضع</h2>\n<p>أحيانًا لا يكون السؤال «من الأول؟» بل «في أي شريحة يقع كل عميل؟». يقسم فريق المالية لدى كريم العملاء إلى أرباع حسب إيرادهم الكلي لمقارنة استخدام الخصومات بين الشرائح. توزّع <code dir=\"ltr\">NTILE(n)</code> الصفوف المرتّبة على n شريحة متقاربة الحجم:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"3\"><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=\"3\" 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\"> lifetime</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\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">quantity</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">unit_price</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">discount</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\"> revenue</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> o</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> oi </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> oi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  WHERE</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">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:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">customer_id</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\">tiered </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\"> customer_id, revenue,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">         NTILE</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 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:#1F2328;--shiki-dark:#E6EDF3\"> revenue </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</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\"> quartile</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  FROM</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> lifetime</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:#1F2328;--shiki-dark:#E6EDF3\"> quartile,</span></span>\n<span class=\"line\"><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\"> customers,</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\">MIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue), </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\"> min_revenue,</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\">SUM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(revenue), </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\"> revenue</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> tiered</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> quartile</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> quartile;</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>إيراد الربع الأعلى يتضاءل أمامه إيراد الربع الأدنى، وهو الشكل المألوف في الغالبية العظمى من قواعد العملاء. لاحظ التدرّج: جمّع إلى صف واحد لكل عميل، ثم حدّد الشريحة بدالة نافذة، ثم جمّع مجددًا حسب الشريحة. دوال النافذة وGROUP BY ليسا متنافسين؛ معظم التحليلات الحقيقية تستخدم الاثنين، في خطوات منفصلة.</p>\n<p>تقسم <code dir=\"ltr\">NTILE</code> حسب عدد الصفوف لا حسب القيمة، فقد يقع عملاء متساوون في الإيراد في شرائح مختلفة. أما الشرائح ذات الحدود الثابتة («أكثر من 2,000 يعني ذهبي») فأداتها الصادقة هي تعبير CASE.</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>سمِّ النافذة مرة واحدة</span></p>\n<p>حين تتشارك عدة دوال النافذة نفسها، يتيح لك SQLite وPostgreSQL تعريفها مرة واحدة: <code dir=\"ltr\">... OVER w FROM orders WINDOW w AS (PARTITION BY customer_id ORDER BY order_date, order_id)</code>. هذا يُبقي قوائم SELECT الطويلة مقروءة ويضمن أن تتفق الدوال فيما بينها.</p>\n</aside>\n<p>الترتيب نصف دوال النافذة. أما النصف الآخر، المجاميع التراكمية والمقارنة بالصف السابق، فيحوّل الجدول الشهري إلى اتجاه، وهو موضوعنا التالي.</p>\n","toc":[{"id":"sec-1","text":"إبقاء الصفوف مع النظر عبرها","level":2},{"id":"sec-2","text":"ROW_NUMBER وRANK وDENSE_RANK","level":2},{"id":"sec-3","text":"أعلى N في كل مجموعة","level":2},{"id":"sec-4","text":"الأحداث الأولى: صف واحد لكل عميل","level":2},{"id":"sec-5","text":"شرائح بدل المواضع","level":2}],"takeaways":["دالة النافذة (window function) تحسب قيمة من مجموعة صفوف مترابطة لكنها تُبقي كل صف، على عكس GROUP BY الذي يضغطها.","يعيد PARTITION BY بدء الحساب لكل مجموعة؛ ويحدّد ORDER BY داخل OVER التسلسل الذي يتبعه الترتيب.","يعطي ROW_NUMBER مواضع فريدة، ويترك RANK فجوات بعد التعادل (1، 1، 3) بينما لا يتركها DENSE_RANK (1، 1، 2).","تُحسب دوال النافذة بعد WHERE، لذا لتصفية النتائج حسب الترتيب، احسبه في CTE ثم صفِّ في الـ query الخارجي.","اجعل ROW_NUMBER حتميًا بإضافة عمود فريد لكسر التعادل في الـ ORDER BY الخاص به."],"further":[{"title":"SQLite window functions","url":"https://www.sqlite.org/windowfunctions.html"},{"title":"PostgreSQL window functions tutorial","url":"https://www.postgresql.org/docs/current/tutorial-window.html"}],"quiz":[{"q":"<p>لثلاثة منتجات إيرادات قدرها 900 و900 و700. ماذا يعطي RANK وDENSE_RANK (بترتيب الإيراد تنازليًا) للمنتج صاحب 700؟</p>","options":[{"text":"RANK يعطي 3، وDENSE_RANK يعطي 2","why":"صحيح. يتخطّى RANK الموضع 2 لأن صفّين يتشاركان الموضع 1؛ ويكمل DENSE_RANK بالعدد الصحيح التالي."},{"text":"RANK يعطي 2، وDENSE_RANK يعطي 3","why":"هذا معكوس. DENSE_RANK هو الذي لا يترك فجوات، فلا يكون أبدًا أكبر من RANK."},{"text":"RANK يعطي 2، وDENSE_RANK يعطي 2","why":"كان هذا سيصحّ لو لم يكن هناك تعادل في القمة. قيمة RANK هي واحد زائد عدد الصفوف المرتّبة فوق الصف، أي 1 + 2 = 3."},{"text":"RANK يعطي 3، وDENSE_RANK يعطي 3","why":"لا يترك DENSE_RANK فجوات، فتحصل 700، وهي القيمة المختلفة الثانية، على 2 لا 3."}],"answer":0},{"q":"<p>لماذا يفشل <code dir=\"ltr\">SELECT ... WHERE ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) = 1</code>؟</p>","options":[{"text":"لأن ROW_NUMBER يحتاج إلى وسيط.","why":"لا يأخذ ROW_NUMBER أي وسيط؛ القوسان الفارغان صحيحان."},{"text":"لأن PARTITION BY لا يمكن استخدامه مع ORDER BY.","why":"صُمّما ليُستخدما معًا؛ الـ partition يعيد بدء الترقيم، والترتيب يحدّده."},{"text":"لأن دوال النافذة لا تعمل إلا على الأعمدة الرقمية.","why":"لا يقرأ ROW_NUMBER قيم أي عمود؛ إنه يرقّم الصفوف بالترتيب المعطى."},{"text":"لأن دوال النافذة تُحسب بعد WHERE، فلا يمكن استخدامها فيه.","why":"صحيح. احسب رقم الصف في CTE أو subquery، ثم صفِّ بـ <code dir=\"ltr\">WHERE rn = 1</code> في الـ query الخارجي."}],"answer":3},{"q":"<p>ترقّم طلبات كل عميل بـ <code dir=\"ltr\">ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY date(order_date))</code>. يحصل طلبان في اليوم نفسه على 1 و2 بترتيب لا يمكن التنبؤ به. ما الإصلاح؟</p>","options":[{"text":"استخدم RANK بدلًا منه، فيحصل الاثنان على 1.","why":"عندها يصبح للعميل طلبان «أوّلان»، والتصفية على الترتيب 1 تعيد الاثنين، فيتكرّر العميل."},{"text":"احذف PARTITION BY.","why":"بدون الـ partition يمتد الترقيم عبر كل العملاء، ولا يحصل على 1 إلا طلب واحد في الجدول كله."},{"text":"أضف عمودًا فريدًا لكسر التعادل، مثل <code dir=\"ltr\">order_id</code>، بعد التاريخ في ORDER BY الخاص بالنافذة.","why":"صحيح. مع مفتاح أخير فريد يصبح الترتيب كاملًا، فيحصل الصف نفسه على 1 في كل مرة."}],"answer":2},{"q":"<p>كم صفًا يعيد <code dir=\"ltr\">SELECT customer_id, COUNT(*) OVER (PARTITION BY customer_id) FROM orders</code>؟</p>","options":[{"text":"صفًا لكل طلب، يعرض كلٌّ منها عدد طلبات ذلك العميل.","why":"صحيح. تعدّ النافذة الصفوف في الـ partition الخاص بكل عميل لكنها لا تضغطها، فيبقى كل صف طلب."},{"text":"صفًا لكل عميل.","why":"هذا ما كان سيعيده <code dir=\"ltr\">GROUP BY customer_id</code>. دالة النافذة لا تغيّر عدد الصفوف أبدًا."},{"text":"صفًا واحدًا فيه العدد الكلي للطلبات.","why":"هذا <code dir=\"ltr\">COUNT(*)</code> عادي بلا GROUP BY ولا OVER."}],"answer":0}],"exercise":{"kind":"sql","starter":"-- Top 2 products by 2025 revenue in each category\nWITH product_revenue AS (\n  SELECT\n    c.name AS category,\n    p.name AS product,\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  JOIN products AS p ON p.product_id = oi.product_id\n  JOIN categories AS c ON c.category_id = p.category_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 c.name, p.name\n),\nranked AS (\n  SELECT\n    category,\n    product,\n    revenue,\n    ROW_NUMBER() OVER (ORDER BY revenue DESC) AS rnk\n  FROM product_revenue\n)\nSELECT category, product, ROUND(revenue, 2) AS revenue, rnk\nFROM ranked\nWHERE rnk <= 2;","solution":"WITH product_revenue AS (\n  SELECT\n    c.name AS category,\n    p.name AS product,\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  JOIN products AS p ON p.product_id = oi.product_id\n  JOIN categories AS c ON c.category_id = p.category_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 c.name, p.name\n),\nranked AS (\n  SELECT\n    category,\n    product,\n    revenue,\n    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) AS rnk\n  FROM product_revenue\n)\nSELECT category, product, ROUND(revenue, 2) AS revenue, rnk\nFROM ranked\nWHERE rnk <= 2;","orderMatters":false,"title":"المنتجان الأكثر مبيعًا في كل فئة","prompt":"<p>أكمل قائمة هدى للنشرة البريدية: <strong>أعلى منتجين من حيث إيراد 2025 داخل كل فئة</strong> (الطلبات غير الملغاة). إذا تعادل منتجان في المركز الثاني، فأبقِ الاثنين.</p>\n<p>أعِد <code dir=\"ltr\">category</code> و<code dir=\"ltr\">product</code> و<code dir=\"ltr\">revenue</code> (مقرّبًا إلى منزلتين عشريتين) و<code dir=\"ltr\">rnk</code> (ترتيب المنتج داخل فئته، 1 = الأفضل). ترتيب الصفوف لا يهم.</p>\n<p>الـ CTE المسمّى <code dir=\"ltr\">product_revenue</code> في الكود المبدئي صحيح. أما الترتيب فليس كذلك: إنه يعيد منتجين فقط للمتجر كله.</p>\n","hints":["أين يُعاد بدء الترتيب في الكود المبدئي؟ وأي جزء من <code dir=\"ltr\">OVER (...)</code> يجعله يُعاد لكل فئة؟","أضف <code dir=\"ltr\">PARTITION BY category</code> قبل الـ <code dir=\"ltr\">ORDER BY</code> داخل OVER.","لأن المتعادلين يجب أن يبقيا معًا، استخدم <code dir=\"ltr\">RANK()</code> بدل <code dir=\"ltr\">ROW_NUMBER()</code>."],"explanation":"<p>بدون PARTITION BY تكون النتيجة كلها نافذة واحدة، فيرقّم ROW_NUMBER كل المنتجات معًا، ويُبقي <code dir=\"ltr\">rnk &lt;= 2</code> أعلى منتجين في المتجر. أما <code dir=\"ltr\">PARTITION BY category</code> فيعيد بدء الترتيب في كل فئة، ويعطي RANK المنتجات المتعادلة الرقم نفسه فيبقى المتعادلان على المركز الثاني معًا. تحتوي Kitchen وOutdoor كلٌّ منهما على منتج واحد، فتعيد كلٌّ منهما صفًا واحدًا، وهذه هي مشكلة المنتجات الموضوعة في الفئة الخطأ من درس الـ JOIN تظهر مجددًا.</p>\n","checkLabels":{}},"words":611,"readMinutes":4}