{"id":"l-sql-1-2","courseId":"sql-for-analysts","lang":"ar","kind":"lesson","summary":"اكتب عبارات WHERE تقول ما تقصده بالضبط باستخدام AND وOR وIN وBETWEEN وLIKE، ورتّب النتائج ترتيبًا حتميًا بـ ORDER BY متعدّد المفاتيح وLIMIT وOFFSET.","html":"<p>تكتب هدى صالح، نائبة الرئيس للتسويق: «هل يمكنك أن تستخرج الطلبات التي استخدمت SPRING15 أو WELCOME10 على قنواتنا الخاصة، الويب أو التطبيق؟ أريد أن أعرف هل يجذب عرض الترحيب الناس إلى التطبيق». في جملتها «أو» مرتين و«و» مرة واحدة. هذا هو شكل الطلبات الذي ينتج أرقامًا خاطئة دون أي إنذار، لذا يستحق <code dir=\"ltr\">WHERE</code> أن تضبطه تمامًا.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">AND قبل OR</h2>\n<p>هذه هي المسودة الأولى التي يكتبها معظم الناس، بترجمة كلماتها من اليسار إلى اليمين:</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\">SELECT</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:#1F2328;--shiki-dark:#E6EDF3\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'web'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> OR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'mobile_app'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> coupon_code </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">IN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SPRING15'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</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>1,191 طلبًا. هذا أكثر من نصف كل الطلبات خلال سنتين، من أجل كوبونين محدودين. الرقم نفسه هو جرس الإنذار: <code dir=\"ltr\">AND</code> يرتبط أقوى من <code dir=\"ltr\">OR</code>، فقرأ SQL الشرط هكذا: «كل طلبات الويب، إضافةً إلى طلبات التطبيق التي استخدمت كوبونًا». كل طلب ويب موجود في النتيجة، بكوبون أو بدونه.</p>\n<p>الأقواس تقول ما قصدته هدى:</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\">SELECT</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:#1F2328;--shiki-dark:#E6EDF3\"> (channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'web'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> OR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'mobile_app'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> coupon_code </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">IN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SPRING15'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</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>110 طلبات. والأفضل أن يستخدم اختبار القناة <code dir=\"ltr\">IN</code> أيضًا، فيختفي OR تمامًا: <code dir=\"ltr\">channel IN ('web', 'mobile_app')</code>. يفحص <code dir=\"ltr\">IN</code> عمودًا واحدًا مقابل قائمة، والقائمة لا يمكن أن تُجمَّع بشكل خاطئ.</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>أن تثق بالـ query لأنه نُفّذ دون أخطاء</span></p>\n<p>الـ query الخاطئ لم يُظهر أي خطأ. أعاد رقمًا يبدو معقولًا. كلما خلط شرطٌ بين AND وOR، ضع كل مجموعة OR بين قوسين، وألقِ نظرة على العدد: إذا أبقى شرطٌ «محدود» نصف الجدول، فالمنطق خاطئ.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">بقية أدوات التصفية</h2>\n<p>بضعة معاملات تغطي في الغالب كل شرط ستكتبه:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>المعامل</th>\n<th>مثال</th>\n<th>ملاحظة</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">IN (...)</code></td>\n<td><code dir=\"ltr\">status IN ('cancelled', 'returned')</code></td>\n<td><code dir=\"ltr\">NOT IN</code> للاستبعاد؛ احذر من NULL (الدرس التالي)</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">BETWEEN a AND b</code></td>\n<td><code dir=\"ltr\">shipping_fee BETWEEN 5 AND 10</code></td>\n<td>يشمل الطرفين</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">LIKE</code></td>\n<td><code dir=\"ltr\">name LIKE '%mat%'</code></td>\n<td><code dir=\"ltr\">%</code> أي سلسلة من الأحرف، و<code dir=\"ltr\">_</code> حرف واحد بالضبط</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">&lt;&gt;</code> / <code dir=\"ltr\">!=</code></td>\n<td><code dir=\"ltr\">status &lt;&gt; 'cancelled'</code></td>\n<td>كلاهما يعني «لا يساوي»</td>\n</tr>\n</tbody>\n</table></div><p>لمقارنة النصوص في SQLite خصوصية. <code dir=\"ltr\">=</code> حسّاس لحالة الأحرف، فـ <code dir=\"ltr\">'a' = 'A'</code> قيمته false، بينما يتجاهل <code dir=\"ltr\">LIKE</code> حالة الأحرف الإنجليزية، فـ <code dir=\"ltr\">'a' LIKE 'A'</code> قيمته true. لهذا يجد <code dir=\"ltr\">name LIKE '%mat%'</code> كلًّا من “Desk Mat XL” و&quot;Cork Yoga Mat&quot;. أما <code dir=\"ltr\">LIKE</code> في PostgreSQL فحسّاس لحالة الأحرف، وهو فرق ستقابله مجددًا في درس اللهجات.</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\">SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> product_id, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, unit_price</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> products</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> LIKE</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '%mat%'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> unit_price </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 20</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 80</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<h2 id=\"sec-3\" tabindex=\"-1\">قول «لا» بشكل صحيح</h2>\n<p>النفي هو المكان الذي يتعثّر فيه حتى الحذِرون. لنفترض أن هدى تريد الآن الطلبات التي <em>ليست</em> ملغاة <em>ولا</em> مرتجعة. يمكنك كتابتها بطريقتين متكافئتين:</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\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</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\">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\"> NOT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> OR</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</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\"> not_either,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</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\">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 style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</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\"> 'returned'</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\"> both_not;</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>كلاهما يعيد 1,864. القاعدة وراء ذلك هي قانون De Morgan: إدخال NOT إلى داخل القوسين يقلب كل OR إلى AND (وكل AND إلى OR). الخطأ الشائع أن تقلب المقارنات وتنسى قلب أداة الربط: <code dir=\"ltr\">status &lt;&gt; 'cancelled' OR status &lt;&gt; 'returned'</code> قيمته TRUE لكل صف، لأن أي حالة تختلف عن واحدة من الاثنتين على الأقل. حين تكتب شرطًا منفيًا، فإن <code dir=\"ltr\">NOT IN ('cancelled', 'returned')</code> هو أوضح صيغة على الإطلاق.</p>\n<p>وهناك عادة أخرى تؤتي ثمارها هنا. قبل أن تثق بشرط فيه عدة أجزاء، عُدّ الصفوف لكل جزء وحده. لشرط هدى ثلاثة أجزاء: الويب أو التطبيق يُبقي 1,848 صفًا، والكوبونان يُبقيان 127، و«ليست ملغاة ولا مرتجعة» يُبقي 1,864. ولا يمكن لربطها بـ AND أن يُبقي أكثر من أصغرها، أي 127؛ فإن حدث ذلك فالمنطق خاطئ. فحص كهذا يستغرق عشر ثوانٍ ويلتقط معظم الأخطاء المنطقية قبل أن يراها أحد غيرك.</p>\n<p>التواريخ في Cartwheel نصوص بصيغة ISO (<code dir=\"ltr\">2025-11-28 19:55:30</code>)، وترتيبها الأبجدي يطابق ترتيبها الزمني. لذا يعمل <code dir=\"ltr\">order_date &gt;= '2025-11-01'</code> كشرط على التاريخ. تتناول الدروس القادمة الفخاخ، بدءًا بما يحدث عند نهاية اليوم.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">ترتيب يمكنك الاعتماد عليه</h2>\n<p>يقبل <code dir=\"ltr\">ORDER BY</code> عدة مفاتيح، لكلٍّ منها اتجاهه. المفتاح الثاني لا يؤثّر إلا بين الصفوف المتساوية في الأول:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"4\"><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=\"4\" 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\">SELECT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_id, customer_id, order_date, coupon_code</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:#1F2328;--shiki-dark:#E6EDF3\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'marketplace'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> coupon_code </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">IS NOT NULL</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 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p>لماذا نضيف <code dir=\"ltr\">order_id</code> والتواريخ نادرًا ما تتساوى؟ لأن «نادرًا» ليست «أبدًا». حين يتساوى صفّان في كل مفاتيح الترتيب، لا يَعِد SQL بأي ترتيب بينهما، وقد يختلف من تشغيل إلى آخر. إنهاء الـ <code dir=\"ltr\">ORDER BY</code> بعمود فريد، وهو عادةً المفتاح الأساسي، يجعل الترتيب حتميًا تمامًا.</p>\n<p>وهذا أهم ما يكون في تقسيم الصفحات. <code dir=\"ltr\">LIMIT 20 OFFSET 40</code> يعني «تخطَّ 40 صفًا وأعِد الـ 20 التالية»، أي الصفحة الثالثة من 20. إذا لم يكن الترتيب حتميًا، فقد يظهر صف في الصفحة الثانية ثم مرة أخرى في الثالثة.</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>اكتب الجملة، ثم الـ WHERE</span></p>\n<p>قبل أن تكتب شرطًا، صُغه في جملة واحدة مع أقواس: «الكوبون هو (SPRING15 أو WELCOME10) والقناة هي (الويب أو التطبيق) والحالة ليست ملغاة». الأقواس في الجملة تصبح الأقواس في SQL.</p>\n</aside>\n<p>أُجيب عن سؤال هدى عن الكوبونات: 110 طلبات. وسؤالها التالي في التمرين أدناه. بعد ذلك تأتي القيمة التي تُربك كل هذه المعاملات بطرق مفاجئة: <code dir=\"ltr\">NULL</code>.</p>\n","toc":[{"id":"sec-1","text":"AND قبل OR","level":2},{"id":"sec-2","text":"بقية أدوات التصفية","level":2},{"id":"sec-3","text":"قول «لا» بشكل صحيح","level":2},{"id":"sec-4","text":"ترتيب يمكنك الاعتماد عليه","level":2}],"takeaways":["AND يرتبط أقوى من OR، لذا ضع كل مجموعة OR بين قوسين حين تجاور AND.","IN يغني عن سلسلة من اختبارات المساواة المربوطة بـ OR على عمود واحد، وهو أسهل قراءة وأصعب خطأً.","في SQLite تكون المقارنة بـ = على النص حسّاسة لحالة الأحرف، بينما يتجاهل LIKE حالة الأحرف الإنجليزية (ASCII).","أضف عمودًا فريدًا كآخر مفتاح في ORDER BY كي تُرتَّب الصفوف المتساوية بالطريقة نفسها في كل مرة.","LIMIT مع OFFSET يقسّم النتيجة إلى صفحات، لكن الصفحات لا تثبت إلا مع ORDER BY حتمي."],"further":[{"title":"SQLite expressions and operator precedence","url":"https://www.sqlite.org/lang_expr.html"},{"title":"SQLite SELECT, ORDER BY and LIMIT","url":"https://www.sqlite.org/lang_select.html#orderby"}],"quiz":[{"q":"<p>ما الصفوف التي يُبقيها هذا الشرط؟</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\">WHERE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> OR</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> channel </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'web'</span></span></code></pre></div>\n","options":[{"text":"الطلبات المسلَّمة أو المشحونة، لكن من قناة الويب فقط.","why":"هذا على الأرجح ما قصده الكاتب، لكن AND يُقيَّم أولًا، فلا ينطبق اختبار القناة إلا على جانب الطلبات المشحونة."},{"text":"الطلبات التي هي مسلَّمة ومشحونة معًا فقط، على الويب.","why":"لا يمكن لحالة واحدة أن تحمل قيمتين في آن واحد؛ OR ما زال OR هنا، والمفاجأة في طريقة التجميع."},{"text":"خطأ في الصياغة لأن AND وOR تحتاجان إلى أقواس.","why":"يقبل SQL خلط AND وOR بدون أقواس، ولهذا بالضبط يمرّ هذا الخطأ بصمت."},{"text":"كل الطلبات المسلَّمة من أي قناة، إضافةً إلى الطلبات المشحونة من الويب.","why":"صحيح. يُقرأ الشرط على أنه <code dir=\"ltr\">status = 'delivered' OR (status = 'shipped' AND channel = 'web')</code>."}],"answer":3},{"q":"<p>تحتاج إلى طلبات الويب من العميل 42، وقد تكون قيمة القناة مخزّنة بالشكل ‘Web’ أو ‘web’. أي اختبار يطابق الاثنين؟</p>","options":[{"text":"<code dir=\"ltr\">channel = 'web'</code>","why":"في SQLite يقارن = النص مع مراعاة حالة الأحرف، فلن تطابق ‘Web’."},{"text":"<code dir=\"ltr\">channel IN ('web')</code>","why":"IN يستخدم مقارنة المساواة نفسها التي يستخدمها =، فهو حسّاس لحالة الأحرف أيضًا."},{"text":"<code dir=\"ltr\">channel LIKE 'web'</code>","why":"صحيح. LIKE في SQLite لا يميّز حالة الأحرف الإنجليزية افتراضيًا، فيطابق الكتابتين. و<code dir=\"ltr\">LOWER(channel) = 'web'</code> يعمل أيضًا."}],"answer":2},{"q":"<p>لوحة معلومات تعرض الطلبات 20 في كل صفحة باستخدام <code dir=\"ltr\">ORDER BY order_date LIMIT 20 OFFSET 40</code>. يشتكي المستخدمون من ظهور طلب في صفحتين. ما الإصلاح الأرجح؟</p>","options":[{"text":"أضف <code dir=\"ltr\">order_id</code> كمفتاح ترتيب ثانٍ حتى تأتي الصفوف ذات التواريخ المتساوية بالترتيب نفسه دائمًا.","why":"صحيح. حين تتساوى قيم <code dir=\"ltr\">order_date</code> قد تعيد قاعدة البيانات الصفوف المتساوية بترتيب مختلف في كل query، فيقع صف واحد في صفحتين."},{"text":"استبدل OFFSET بـ LIMIT أكبر حتى تقلّ الصفحات المطلوبة.","why":"هذا يخفي العَرَض في الجداول الصغيرة، لكنه لا يجعل الترتيب حتميًا."},{"text":"رتّب بـ DESC بدل ASC.","why":"الاتجاه لا يهم؛ الصفوف المتساوية غامضة الترتيب في الاتجاه التنازلي أيضًا."}],"answer":0},{"q":"<p><code dir=\"ltr\">BETWEEN 10 AND 20</code> على العمود <code dir=\"ltr\">shipping_fee</code>: أي قيمة لا تدخل في النتيجة؟</p>","options":[{"text":"10","why":"BETWEEN يشمل حدّه الأدنى؛ <code dir=\"ltr\">x BETWEEN a AND b</code> يعني <code dir=\"ltr\">x &gt;= a AND x &lt;= b</code>."},{"text":"20","why":"BETWEEN يشمل حدّه الأعلى أيضًا، وهذا ما يوقع الناس في المشاكل مع التواريخ لاحقًا."},{"text":"15.5","why":"أي قيمة بين الحدّين تدخل، والكسور كذلك."},{"text":"20.01","why":"صحيح. إنها أكبر من الحدّ الأعلى. أما الحدّان نفساهما فيدخلان."}],"answer":3}],"exercise":{"kind":"sql","starter":"-- BLACKFRIDAY25 used outside November 2025, web or app, not cancelled\nSELECT order_id, order_date, channel\nFROM orders\nWHERE coupon_code = 'BLACKFRIDAY25'\n  AND channel = 'web' OR channel = 'mobile_app'\n  AND status <> 'cancelled'\n  AND order_date >= '2025-01-01'\n  AND order_date < '2025-11-01' OR order_date >= '2025-12-01'\nORDER BY order_date;","solution":"SELECT order_id, order_date, channel\nFROM orders\nWHERE coupon_code = 'BLACKFRIDAY25'\n  AND channel IN ('web', 'mobile_app')\n  AND status <> 'cancelled'\n  AND order_date >= '2025-01-01'\n  AND (order_date < '2025-11-01' OR order_date >= '2025-12-01')\nORDER BY order_date, order_id;","orderMatters":true,"title":"هل يتسرّب BLACKFRIDAY25؟","prompt":"<p>سؤال هدى التالي: «كان BLACKFRIDAY25 مخصّصًا لشهر نوفمبر فقط. أرني كل طلب في 2025 استخدمه <strong>خارج نوفمبر</strong>، على <strong>الويب أو التطبيق</strong>، و<strong>لم يُلغَ</strong>».</p>\n<p>أعِد <code dir=\"ltr\">order_id</code> و<code dir=\"ltr\">order_date</code> و<code dir=\"ltr\">channel</code>، مرتّبة حسب <code dir=\"ltr\">order_date</code> (الأقدم أولًا). الترتيب مهم.</p>\n<p>الكود المبدئي يعمل، لكنه يعيد مئات الصفوف. أصلح منطقه.</p>\n","hints":["عُدّ مرات OR في الكود المبدئي. كل واحدة منها تقسم عبارة WHERE كلها إلى قسمين لأن AND يرتبط أقوى.","استبدل OR الخاص بالقناة بـ <code dir=\"ltr\">channel IN ('web', 'mobile_app')</code>.","شرط التاريخ يحتاج إلى أقواس: <code dir=\"ltr\">AND (order_date &lt; '2025-11-01' OR order_date &gt;= '2025-12-01')</code>، بعد السطر الذي يُبقي 2025 فقط."],"explanation":"<p>بدون أقواس، يقسم كل <code dir=\"ltr\">OR</code> الشرط إلى بدائل منفصلة، فأبقى الكود المبدئي كل طلبات التطبيق التي طابقت الشروط اللاحقة وكل الطلبات من ديسمبر فصاعدًا، بكوبون أو بدونه. استخدام <code dir=\"ltr\">IN</code> للقناة يزيل OR واحدًا، ووضع بدائل التاريخ بين قوسين يُبقي الآخر محصورًا في مكانه. النتيجة 22 طلبًا، معظمها على الويب: أمر يستحق أن تنبّه إليه هدى، لأن الكوبون ما زال يعمل طوال السنة بوضوح.</p>\n","checkLabels":{}},"words":681,"readMinutes":4}