{"id":"l-sql-4-1","courseId":"sql-for-analysts","lang":"ar","kind":"lesson","summary":"اكتب فحوص جودة البيانات على شكل queries تعيد الصفوف المخالفة، واجمعها في مجموعة فحوص قابلة لإعادة الاستخدام، وتعامل مع كل إخفاق كسؤال يُطرح على أصحاب العمل لا كخطأ يُخفى.","html":"<p>يعرض كريم نتائج 2025 على مجلس الإدارة الأسبوع القادم. طلبه قصير وجادّ: «قبل أن توضع هذه الأرقام على شريحة، ما مدى ثقتنا بالبيانات؟». كل query كتبته حتى الآن يفترض أن البيانات تعني ما تقوله الوثائق. يدور هذا الدرس حول فحص هذا الافتراض بشكل منهجي، حتى تستند عبارة «أنا واثق» إلى queries لا إلى مشاعر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الفحص query يعيد الصفوف السيئة</h2>\n<p>أنفع عادة في جودة البيانات أن تصوغ كل قاعدة كـ query يعيد الصفوف التي <strong>تخالفها</strong>. النتيجة الفارغة تعني أن القاعدة صامدة؛ وأي شيء آخر دليل يمكنك أن تُطلع عليه أحدًا. هذه القاعدة «المنتج يقع في فئة فرعية، وليس مباشرةً في فئة من المستوى الأعلى»، من وثائق البيانات:</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\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">product_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </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\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> categories </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\">category_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category_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\">parent_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> IS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> NULL</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>صفّان: Picnic Blanket مصنّف مباشرةً تحت Outdoor، وBamboo Cutting Board تحت Kitchen. وهذا يفسّر صفّي “Kitchen” و&quot;Outdoor&quot; الغريبين في جدول إيراد الفئات من القسم 2. الفحص الذي يعيد صفوفًا يمكنك تسميتها أقنع بكثير من مجرد عدد.</p>\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>مثال من Cartwheel</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>التفرّد</td>\n<td>هل المفتاح فريد فعلًا؟</td>\n<td>عناوين بريد مكرّرة، بتجاهل حالة الأحرف</td>\n</tr>\n<tr>\n<td>الاكتمال</td>\n<td>هل القيم موجودة حيث يجب؟</td>\n<td>تذاكر بلا <code dir=\"ltr\">agent_id</code></td>\n</tr>\n<tr>\n<td>الصلاحية</td>\n<td>هل القيم ضمن نطاقها المسموح؟</td>\n<td>خصم بين 0 و0.25</td>\n</tr>\n<tr>\n<td>السلامة المرجعية</td>\n<td>هل يشير كل مرجع إلى صف حقيقي؟</td>\n<td>عناصر طلب لا وجود لطلبها</td>\n</tr>\n<tr>\n<td>الاتساق</td>\n<td>هل تروي الأعمدة والجداول المترابطة القصة نفسها؟</td>\n<td>طلبات موسومة ‘returned’ بلا سجل إرجاع؛ تذاكر مفتوحة لديها تقييم بالفعل</td>\n</tr>\n</tbody>\n</table></div><p>قيود قاعدة البيانات (<code dir=\"ltr\">PRIMARY KEY</code> و<code dir=\"ltr\">NOT NULL</code> و<code dir=\"ltr\">CHECK</code> و<code dir=\"ltr\">FOREIGN KEY</code>) تفرض بعض هذه القواعد عند الكتابة، إن كانت قد عُرّفت، وفي حالة SQLite إن كان فرض المفاتيح الأجنبية مفعّلًا. نادرًا ما يتحكّم المحلّلون في ذلك، وقواعد الاتساق بين الجداول لا يمكن عادةً التعبير عنها كقيود أصلًا. لذا تفحص بنفسك.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">مجموعة فحوص يمكنك إعادة تشغيلها</h2>\n<p>الفحص الواحد query؛ ودزينة فحوص مجموعة. أعطِ كل فحص اسمًا، واعدد إخفاقاته، وكدّسها بـ <code dir=\"ltr\">UNION ALL</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\">SELECT</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'customers: duplicate email (any case)'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> check_name, </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\"> failures</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LOWER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(email) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> LOWER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(email) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">HAVING</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\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</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:#0A3069;--shiki-dark:#A5D6FF\"> 'order_items: no matching order'</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</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>\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:#CF222E;--shiki-dark:#FF7B72\"> EXISTS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><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 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\">order_id</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\">order_id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</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:#0A3069;--shiki-dark:#A5D6FF\"> 'order_items: discount outside 0-0.25'</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_items</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> discount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">NOT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> BETWEEN</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</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\">25</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:#0A3069;--shiki-dark:#A5D6FF\"> 'products: filed under a top-level category'</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> products </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> categories </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\">category_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category_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\">parent_id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> IS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> NULL</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:#0A3069;--shiki-dark:#A5D6FF\"> 'orders: returned but no return record'</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>\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\">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\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> NOT</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> EXISTS</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\"> 1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> FROM</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    JOIN</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> returns</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> r </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> r</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order_item_id</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\">order_item_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    WHERE</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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</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:#0A3069;--shiki-dark:#A5D6FF\"> 'tickets: rated while still open'</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> support_tickets</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> closed_at </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">IS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> NULL</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> satisfaction </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">IS NOT NULL</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>ثلاثة فحوص تفشل. كل جزء في الـ query المركّب يجب أن يعيد العدد نفسه من الأعمدة؛ والـ SELECT الأول هو الذي يسمّيها. يُبقي <code dir=\"ltr\">UNION ALL</code> كل صف، بينما يزيل <code dir=\"ltr\">UNION</code> التكرار، وهذا يكلّف وقتًا وقد يدمج فحصين صادف أن تشاركا الاسم والعدد. احفظ المجموعة بجانب تحليلك وشغّلها في كل مرة تُحدَّث فيها البيانات؛ فالفحص الذي نجح في أكتوبر قد يفشل في ديسمبر.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الإخفاقات أسئلة، لا أحكام</h2>\n<p>الآن اقرأ الإخفاقات كمحلّل، لا كأداة تدقيق آلية:</p>\n<ul>\n<li><strong>طلبان مرتجعان بلا سجلات إرجاع</strong> قُدّما كلاهما في الأسبوعين الأخيرين من ديسمبر 2025. هذا يوحي بتأخير في الإجراءات (يُسجَّل الإرجاع حين يصل الطرد عائدًا)، لا بتلف في البيانات. اسأل العمليات.</li>\n<li><strong>ثلاث تذاكر مفتوحة لها تقييم رضا</strong> فُتحت في الأيام الأخيرة من ديسمبر. ربما يُرسل الاستبيان قبل إغلاق التذكرة رسميًا. اسأل جوليا.</li>\n<li><strong>منتجان في الفئة الخطأ</strong> هما على الأرجح خطأ في الكتالوج. أبلغ عنه من يتولّى الكتالوج، وقرّر في الأثناء كيف يتعامل تقرير الفئات معهما.</li>\n</ul>\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>تصفية المشكلات بصمت</span></p>\n<p>الحركة المغرية هي إضافة <code dir=\"ltr\">AND status &lt;&gt; 'returned'</code> أو <code dir=\"ltr\">WHERE parent_id IS NOT NULL</code> حتى تبدو الأرقام نظيفة. عندها تختلف نتيجتك عن نتائج الجميع، ولا يعرف أحد السبب. أبقِ الصفوف، ودوّن المشكلة وطريقة تعاملك معها في التقرير («يُحسب منتجان تحت فئتهما الأم إلى حين إصلاح الكتالوج»)، ودع صاحب البيانات يصلح المصدر.</p>\n</aside>\n<p>هناك إخفاق آخر مختبئ في هذه البيانات، وهو الأكثر إثارة للاهتمام: منتجات تظهر في طلبات <em>قبل</em> تاريخ إطلاقها المسجّل. العثور عليه هو تمرينك.</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>أقوى فحص يقارن نتيجتك برقم من خارج الـ query: الإيراد المسجّل لدى المالية، أو إجمالي مزوّد الدفع، أو تقرير الشهر الماضي المنشور. إذا اختلف إيرادك لعام 2025 عن رقم المالية بنسبة 0.1%، يمكنك تفسير ذلك. وإذا اختلف بنسبة 10%، فقد وجدت عدم تطابق في التعريفات قبل أن يجده مجلس الإدارة.</p>\n</aside>\n<p>في الدرس التالي ستنظر إلى الوجه الآخر للثقة، أي الأداء: لماذا يعمل بعض هذه الـ queries فورًا ويزحف بعضها الآخر، وكيف ترى ما تفعله قاعدة البيانات فعلًا.</p>\n","toc":[{"id":"sec-1","text":"الفحص query يعيد الصفوف السيئة","level":2},{"id":"sec-2","text":"العائلات الخمس للفحوص","level":2},{"id":"sec-3","text":"مجموعة فحوص يمكنك إعادة تشغيلها","level":2},{"id":"sec-4","text":"الإخفاقات أسئلة، لا أحكام","level":2}],"takeaways":["صُغ كل فحص لجودة البيانات كـ query يعيد الصفوف التي تخالف قاعدة ما، فتعني صفر صفوف أن الفحص ناجح.","غطِّ التفرّد والاكتمال والنطاقات الصالحة والسلامة المرجعية والاتساق بين الجداول؛ كلٌّ منها يلتقط نوعًا مختلفًا من المشكلات.","اجمع الفحوص في query واحد بـ UNION ALL فيه اسم وعدد إخفاقات لكل فحص، وشغّله قبل أي تحليل مهم.","الفحص الفاشل سؤال تطرحه على صاحب البيانات، لا شيء تصفّيه بصمت؛ دوّن ما وجدته وما قرّرته."],"further":[{"title":"SQLite compound SELECT statements (UNION ALL)","url":"https://www.sqlite.org/lang_select.html#compound_select_statements"},{"title":"SQLite CREATE TABLE constraints","url":"https://www.sqlite.org/lang_createtable.html#constraints"},{"title":"PostgreSQL constraints","url":"https://www.postgresql.org/docs/current/ddl-constraints.html"}],"quiz":[{"q":"<p>أي query هو أفضل فحص لجودة البيانات للقاعدة «كل عنصر طلب ينتمي إلى طلب موجود»؟</p>","options":[{"text":"<code dir=\"ltr\">SELECT COUNT(*) FROM order_items JOIN orders USING (order_id)</code>","why":"الـ inner join يُسقط العناصر اليتيمة بصمت، فيبدو العدد سليمًا سواء وُجدت أم لا."},{"text":"<code dir=\"ltr\">SELECT * FROM order_items oi WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.order_id = oi.order_id)</code>","why":"صحيح. إنه يعيد بالضبط الصفوف التي تخالف القاعدة، فتعني النتيجة الفارغة نجاح الفحص، وأي صف دليل."},{"text":"<code dir=\"ltr\">SELECT COUNT(DISTINCT order_id) FROM order_items</code>","why":"هذا يعدّ الطلبات التي تشير إليها العناصر، لكنه لا يستطيع أن يخبرك هل هذه الطلبات موجودة."},{"text":"<code dir=\"ltr\">SELECT * FROM orders WHERE order_id IS NOT NULL</code>","why":"هذا يفحص الجدول الخطأ والقاعدة الخطأ؛ والمفاتيح الأساسية لا تكون NULL أصلًا."}],"answer":1},{"q":"<p>طلبان حالتهما ‘returned’ لكن لا صف لهما في <code dir=\"ltr\">returns</code>. وكلاهما قُدّم في الأسبوعين الأخيرين من ديسمبر 2025. ما الخطوة التالية الأكثر منطقية؟</p>","options":[{"text":"احذف الطلبين من تحليلك حتى تكون الأرقام نظيفة.","why":"حذف الصفوف يخفي المشكلة ويغيّر الإيراد. المحلّلون يُبلغون عن البيانات ويستعلمون عنها؛ ولا يعيدون كتابتها بصمت."},{"text":"غيّر حالتهما إلى ‘delivered’ في الـ query باستخدام تعبير CASE.","why":"هذا يخترع حقيقة. قد تكون الحالة صحيحة وسجل الإرجاع لم يُنشأ بعد فحسب."},{"text":"دوّنهما، واسأل فريق العمليات هل تُسجَّل المرتجعات بتأخير، واذكر في تقريرك كيف تعاملت معهما.","why":"صحيح. التوقيت يشير إلى تأخير في الإجراءات لا إلى خطأ. صاحب البيانات وحده يستطيع التأكيد، ويجب أن يصرّح تقريرك بالافتراض."}],"answer":2},{"q":"<p>لماذا نستخدم <code dir=\"ltr\">UNION ALL</code> بدل <code dir=\"ltr\">UNION</code> لجمع نتائج الفحوص في مجموعة واحدة؟</p>","options":[{"text":"UNION يزيل الصفوف المكرّرة، وقد يدمج فحصين صادف أن تشاركا الاسم والعدد نفسيهما، ويبذل جهدًا إضافيًا لإزالة التكرار.","why":"صحيح. صف كل فحص يُفترض أن يظهر مرة واحدة بالضبط؛ UNION ALL يُبقي كل صف ويتخطّى خطوة إزالة التكرار."},{"text":"UNION لا يستطيع جمع queries تستخدم دوال التجميع.","why":"بل يستطيع؛ المعاملان يقبلان أي SELECT بعدد أعمدة متطابق."},{"text":"UNION ALL يرتّب النتيجة حسب اسم الفحص.","why":"لا يَعِد أيٌّ من المعاملين بترتيب. أضف ORDER BY إن أردت ترتيبًا."}],"answer":0},{"q":"<p>ثلاثة منتجات متوقفة (<code dir=\"ltr\">is_active = 0</code>) ما زالت لها طلبات في ديسمبر 2025. ما نوع هذه المشكلة؟</p>","options":[{"text":"مشكلة تفرّد.","why":"لا يوجد مفتاح مكرّر هنا؛ الصفوف مختلفة كلها."},{"text":"مشكلة سلامة مرجعية.","why":"المنتجات موجودة، فكل عنصر طلب ما زال يشير إلى منتج حقيقي."},{"text":"مشكلة صلاحية نطاق.","why":"العلامة <code dir=\"ltr\">is_active</code> تحمل 0 أو 1 الصالحين؛ القيمة نفسها ليست خارج النطاق."},{"text":"سؤال اتساق بين الجداول، قد يكون خطأً في البيانات أو حالة عمل مشروعة مثل تصفية المخزون.","why":"صحيح. العلامة والمبيعات لا تتفقان. أمّا هل هذا خطأ فيعتمد على معنى «متوقف» في Cartwheel، وهذا سؤال لفريق المنتج."}],"answer":3}],"exercise":{"kind":"sql","starter":"-- Data-quality check: products sold before their launch date\n-- Write it from scratch. One row per offending product with:\n--   product_id, name, launched_on, first_sold (YYYY-MM-DD), lines\nSELECT product_id, name, launched_on\nFROM products;","solution":"SELECT\n  p.product_id,\n  p.name,\n  p.launched_on,\n  date(MIN(o.order_date)) AS first_sold,\n  COUNT(*) AS lines\nFROM order_items AS oi\nJOIN orders AS o ON o.order_id = oi.order_id\nJOIN products AS p ON p.product_id = oi.product_id\nWHERE o.order_date < p.launched_on\nGROUP BY p.product_id, p.name, p.launched_on;","orderMatters":false,"title":"بيع قبل الإطلاق","prompt":"<p>اكتب هذا الفحص <strong>من الصفر</strong>. القاعدة: لا يمكن طلب منتج قبل تاريخ <code dir=\"ltr\">launched_on</code> الخاص به.</p>\n<p>أعِد صفًا واحدًا لكل منتج يخالف القاعدة، فيه <code dir=\"ltr\">product_id</code> و<code dir=\"ltr\">name</code> و<code dir=\"ltr\">launched_on</code> و<code dir=\"ltr\">first_sold</code> (تاريخ أقدم طلب له بالصيغة ‘YYYY-MM-DD’) و<code dir=\"ltr\">lines</code> (عدد سطور الطلبات التي قُدّمت قبل تاريخ الإطلاق). اعدد السطور التي قبل الإطلاق فقط. ترتيب الصفوف لا يهم.</p>\n","hints":["تحتاج إلى ثلاثة جداول: <code dir=\"ltr\">order_items</code> (السطور)، و<code dir=\"ltr\">orders</code> (التاريخ)، و<code dir=\"ltr\">products</code> (تاريخ الإطلاق).","شرط المخالفة يقارن عمودين من جدولين مختلفين: <code dir=\"ltr\">o.order_date &lt; p.launched_on</code>. نصوص ISO تُقارَن بشكل صحيح.","جمّع حسب أعمدة المنتج، ثم استخدم <code dir=\"ltr\">date(MIN(o.order_date))</code> و<code dir=\"ltr\">COUNT(*)</code>."],"explanation":"<p>ربط السطور بطلباتها ومنتجاتها يضع تاريخ الطلب وتاريخ الإطلاق في الصف نفسه، فتصبح القاعدة شرطًا في WHERE. والتجميع حسب المنتج يحوّل 149 سطرًا مخالفًا إلى 8 منتجات. بعض الفجوات ضخمة: كانت Adjustable Dumbbells تُباع منذ فبراير 2024 لكنها «أُطلقت» في مايو 2025. هذا النمط يوحي بأن <code dir=\"ltr\">launched_on</code> يسجّل شيئًا آخر، ربما إعادة إطلاق أو تاريخ استيراد الكتالوج، لا تاريخ التوفّر الأول الحقيقي. اطرح هذا السؤال على فريق المنتج قبل استخدام <code dir=\"ltr\">launched_on</code> في أي تحليل لـ«المنتجات الجديدة».</p>\n","checkLabels":{}},"words":622,"readMinutes":4}