EduFlow
SQL للمحلّلين
⁦0%⁩

الوحدة 4 · SQL في العالم الحقيقي

فحوص جودة البيانات

16 دقراءة 4 دتمرين SQL

الدرس في لمحة

اكتب فحوص جودة البيانات على شكل queries تعيد الصفوف المخالفة، واجمعها في مجموعة فحوص قابلة لإعادة الاستخدام، وتعامل مع كل إخفاق كسؤال يُطرح على أصحاب العمل لا كخطأ يُخفى.

  1. 1الفحص query يعيد الصفوف السيئة
  2. 2العائلات الخمس للفحوص
  3. 3مجموعة فحوص يمكنك إعادة تشغيلها
  4. 4الإخفاقات أسئلة، لا أحكام

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

يعرض كريم نتائج 2025 على مجلس الإدارة الأسبوع القادم. طلبه قصير وجادّ: «قبل أن توضع هذه الأرقام على شريحة، ما مدى ثقتنا بالبيانات؟». كل query كتبته حتى الآن يفترض أن البيانات تعني ما تقوله الوثائق. يدور هذا الدرس حول فحص هذا الافتراض بشكل منهجي، حتى تستند عبارة «أنا واثق» إلى queries لا إلى مشاعر.

الفحص query يعيد الصفوف السيئة

أنفع عادة في جودة البيانات أن تصوغ كل قاعدة كـ query يعيد الصفوف التي تخالفها. النتيجة الفارغة تعني أن القاعدة صامدة؛ وأي شيء آخر دليل يمكنك أن تُطلع عليه أحدًا. هذه القاعدة «المنتج يقع في فئة فرعية، وليس مباشرةً في فئة من المستوى الأعلى»، من وثائق البيانات:

SQL
SELECT p.product_id, p.name, c.name AS category
FROM products AS p
JOIN categories AS c ON c.category_id = p.category_id
WHERE c.parent_id IS NULL;

صفّان: Picnic Blanket مصنّف مباشرةً تحت Outdoor، وBamboo Cutting Board تحت Kitchen. وهذا يفسّر صفّي “Kitchen” و"Outdoor" الغريبين في جدول إيراد الفئات من القسم 2. الفحص الذي يعيد صفوفًا يمكنك تسميتها أقنع بكثير من مجرد عدد.

العائلات الخمس للفحوص

تقع معظم القواعد في خمس عائلات. تشغيل فحص واحد على الأقل من كل عائلة يلتقط الغالبية العظمى من المشكلات:

العائلة السؤال مثال من Cartwheel
التفرّد هل المفتاح فريد فعلًا؟ عناوين بريد مكرّرة، بتجاهل حالة الأحرف
الاكتمال هل القيم موجودة حيث يجب؟ تذاكر بلا agent_id
الصلاحية هل القيم ضمن نطاقها المسموح؟ خصم بين 0 و0.25
السلامة المرجعية هل يشير كل مرجع إلى صف حقيقي؟ عناصر طلب لا وجود لطلبها
الاتساق هل تروي الأعمدة والجداول المترابطة القصة نفسها؟ طلبات موسومة ‘returned’ بلا سجل إرجاع؛ تذاكر مفتوحة لديها تقييم بالفعل

قيود قاعدة البيانات (PRIMARY KEY وNOT NULL وCHECK وFOREIGN KEY) تفرض بعض هذه القواعد عند الكتابة، إن كانت قد عُرّفت، وفي حالة SQLite إن كان فرض المفاتيح الأجنبية مفعّلًا. نادرًا ما يتحكّم المحلّلون في ذلك، وقواعد الاتساق بين الجداول لا يمكن عادةً التعبير عنها كقيود أصلًا. لذا تفحص بنفسك.

مجموعة فحوص يمكنك إعادة تشغيلها

الفحص الواحد query؛ ودزينة فحوص مجموعة. أعطِ كل فحص اسمًا، واعدد إخفاقاته، وكدّسها بـ UNION ALL:

SQL
SELECT 'customers: duplicate email (any case)' AS check_name, COUNT(*) AS failures
FROM (SELECT LOWER(email) FROM customers GROUP BY LOWER(email) HAVING COUNT(*) > 1)
UNION ALL
SELECT 'order_items: no matching order', COUNT(*)
FROM order_items AS oi
WHERE NOT EXISTS (SELECT 1 FROM orders AS o WHERE o.order_id = oi.order_id)
UNION ALL
SELECT 'order_items: discount outside 0-0.25', COUNT(*)
FROM order_items
WHERE discount NOT BETWEEN 0 AND 0.25
UNION ALL
SELECT 'products: filed under a top-level category', COUNT(*)
FROM products AS p
JOIN categories AS c ON c.category_id = p.category_id
WHERE c.parent_id IS NULL
UNION ALL
SELECT 'orders: returned but no return record', COUNT(*)
FROM orders AS o
WHERE o.status = 'returned'
  AND NOT EXISTS (
    SELECT 1 FROM order_items AS oi
    JOIN returns AS r ON r.order_item_id = oi.order_item_id
    WHERE oi.order_id = o.order_id)
UNION ALL
SELECT 'tickets: rated while still open', COUNT(*)
FROM support_tickets
WHERE closed_at IS NULL AND satisfaction IS NOT NULL;

ثلاثة فحوص تفشل. كل جزء في الـ query المركّب يجب أن يعيد العدد نفسه من الأعمدة؛ والـ SELECT الأول هو الذي يسمّيها. يُبقي UNION ALL كل صف، بينما يزيل UNION التكرار، وهذا يكلّف وقتًا وقد يدمج فحصين صادف أن تشاركا الاسم والعدد. احفظ المجموعة بجانب تحليلك وشغّلها في كل مرة تُحدَّث فيها البيانات؛ فالفحص الذي نجح في أكتوبر قد يفشل في ديسمبر.

الإخفاقات أسئلة، لا أحكام

الآن اقرأ الإخفاقات كمحلّل، لا كأداة تدقيق آلية:

  • طلبان مرتجعان بلا سجلات إرجاع قُدّما كلاهما في الأسبوعين الأخيرين من ديسمبر 2025. هذا يوحي بتأخير في الإجراءات (يُسجَّل الإرجاع حين يصل الطرد عائدًا)، لا بتلف في البيانات. اسأل العمليات.
  • ثلاث تذاكر مفتوحة لها تقييم رضا فُتحت في الأيام الأخيرة من ديسمبر. ربما يُرسل الاستبيان قبل إغلاق التذكرة رسميًا. اسأل جوليا.
  • منتجان في الفئة الخطأ هما على الأرجح خطأ في الكتالوج. أبلغ عنه من يتولّى الكتالوج، وقرّر في الأثناء كيف يتعامل تقرير الفئات معهما.

هناك إخفاق آخر مختبئ في هذه البيانات، وهو الأكثر إثارة للاهتمام: منتجات تظهر في طلبات قبل تاريخ إطلاقها المسجّل. العثور عليه هو تمرينك.

في الدرس التالي ستنظر إلى الوجه الآخر للثقة، أي الأداء: لماذا يعمل بعض هذه الـ queries فورًا ويزحف بعضها الآخر، وكيف ترى ما تفعله قاعدة البيانات فعلًا.

أهم النقاط

  • صُغ كل فحص لجودة البيانات كـ query يعيد الصفوف التي تخالف قاعدة ما، فتعني صفر صفوف أن الفحص ناجح.
  • غطِّ التفرّد والاكتمال والنطاقات الصالحة والسلامة المرجعية والاتساق بين الجداول؛ كلٌّ منها يلتقط نوعًا مختلفًا من المشكلات.
  • اجمع الفحوص في query واحد بـ UNION ALL فيه اسم وعدد إخفاقات لكل فحص، وشغّله قبل أي تحليل مهم.
  • الفحص الفاشل سؤال تطرحه على صاحب البيانات، لا شيء تصفّيه بصمت؛ دوّن ما وجدته وما قرّرته.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

أي query هو أفضل فحص لجودة البيانات للقاعدة «كل عنصر طلب ينتمي إلى طلب موجود»؟

السؤال 2 من 4

طلبان حالتهما ‘returned’ لكن لا صف لهما في returns. وكلاهما قُدّم في الأسبوعين الأخيرين من ديسمبر 2025. ما الخطوة التالية الأكثر منطقية؟

السؤال 3 من 4

لماذا نستخدم UNION ALL بدل UNION لجمع نتائج الفحوص في مجموعة واحدة؟

السؤال 4 من 4

ثلاثة منتجات متوقفة (is_active = 0) ما زالت لها طلبات في ديسمبر 2025. ما نوع هذه المشكلة؟

تدريب

تمرين SQL

بيع قبل الإطلاق

اكتب هذا الفحص من الصفر. القاعدة: لا يمكن طلب منتج قبل تاريخ launched_on الخاص به.

أعِد صفًا واحدًا لكل منتج يخالف القاعدة، فيه product_id وname وlaunched_on وfirst_sold (تاريخ أقدم طلب له بالصيغة ‘YYYY-MM-DD’) وlines (عدد سطور الطلبات التي قُدّمت قبل تاريخ الإطلاق). اعدد السطور التي قبل الإطلاق فقط. ترتيب الصفوف لا يهم.

الجداول
customers
(customer_id, first_name, last_name, email, country, city, signup_date, segment, referred_by)
orders
(order_id, customer_id, order_date, status, channel, coupon_code, shipping_fee)
order_items
(order_item_id, order_id, product_id, quantity, unit_price, discount)
products
(product_id, name, category_id, unit_price, unit_cost, launched_on, is_active)
categories
(category_id, name, parent_id)
returns
(return_id, order_item_id, return_date, reason, refund_amount)
employees
(employee_id, first_name, last_name, title, department, manager_id, hire_date, salary)
support_tickets
(ticket_id, customer_id, order_id, opened_at, closed_at, priority, channel, satisfaction, agent_id)
web_sessions
(session_id, customer_id, started_at, source, device, pages_viewed, added_to_cart, purchased)
-- Data-quality check: products sold before their launch date
-- Write it from scratch. One row per offending product with:
--   product_id, name, launched_on, first_sold (YYYY-MM-DD), lines
SELECT product_id, name, launched_on
FROM products;جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.