قيم NULL في التجميعات وعمليات الربط وDISTINCT
اختلاف سلوك NULL بين التجميع والربط والتفرّد
قيم NULL في التجميعات وعمليات الربط وDISTINCT درس مجاني في SQL Interview Prep على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في SQL Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة SQL Interview Prep 4 دروس في المجموع.
NULL في ثلاثة مواضع مفاجئة
لا يتصرف NULL بالطريقة نفسها في كل موضع. يغطي الدرس الأخير السياقات الثلاثة التي تفاجئ سلوكياتها المرشحين أكثر من غيرها: دوال التجميع وعمليات JOIN وDISTINCT / GROUP BY.
تتمثل المفارقة المتكررة في أن دوال التجميع والتصفية تتعامل مع NULL على أنه «تجاهلني»، بينما يتعامل التجميع وDISTINCT معه على أنه «قيمة تساوي قيم NULL الأخرى». وهذا التناقض تحديدًا هو ما يختبره القائمون بالمقابلات.
إذا أتقنتم هذه النقاط، فستكونون قد أحطتم بأكثر أسئلة NULL شيوعًا في مقابلات SQL.
دوال التجميع تتجاهل NULL
القاعدة الأساسية هي: دوال التجميع تتجاوز قيم NULL. إذ تتجاهل SUM وAVG وMIN وMAX وCOUNT(column) مدخلات NULL بالكامل، بدلًا من معاملتها كأنها صفر.
ولهذا السبب قد تُرجع AVG رقمًا مختلفًا عما تتوقعون. فهي تقسم مجموع القيم غير NULL على عدد القيم غير NULL، وليس على إجمالي عدد الصفوف.
-- bonus values: 100, 200, NULL
SELECT
SUM(bonus) AS total, -- 300 (NULL ignored)
AVG(bonus) AS average, -- 150 = 300 / 2, not / 3
COUNT(bonus) AS cnt -- 2 (NULL not counted)
FROM employees;COUNT(*) مقابل COUNT(column)
هذا هو السؤال الأكثر شيوعًا عن NULL في دوال التجميع. تحسب COUNT(*) عدد الصفوف، بما فيها الصفوف التي تحتوي على NULL. أما COUNT(column) فتحسب فقط الصفوف التي تكون فيها قيمة العمود غير NULL.
لذلك فإن الفرق بينهما يساوي بالضبط عدد قيم NULL في ذلك العمود. أما COUNT(DISTINCT column) فتذهب خطوة أبعد، إذ تتجاهل NULL أيضًا أثناء إزالة التكرارات.
SELECT
COUNT(*) AS rows_total, -- all rows
COUNT(bonus) AS non_null_bonus, -- excludes NULLs
COUNT(DISTINCT bonus) AS distinct_bonus, -- excludes NULLs + dups
COUNT(*) - COUNT(bonus) AS null_bonus
FROM employees;AVG مقابل SUM/COUNT(*): حيلة شائعة
يسأل القائمون بالمقابلات: «هل AVG(x) تساوي SUM(x) / COUNT(*)؟» والإجابة هي لا عند وجود قيم NULL.
تساوي AVG(x) التعبير SUM(x) / COUNT(x)، إذ تقسم على عدد القيم غير NULL. أما القسمة على COUNT(*) فتعامل قيم NULL كما لو كانت أصفارًا، مما يخفض المتوسط.
إذا كنتم تريدون فعلًا احتساب قيم NULL كأصفار، فيجب التصريح بذلك بوضوح باستخدام COALESCE.
-- These differ when bonus has NULLs:
SELECT
AVG(bonus) AS avg_ignoring_nulls,
SUM(bonus) * 1.0 / COUNT(*) AS avg_nulls_as_zero,
AVG(COALESCE(bonus, 0)) AS explicit_nulls_as_zero
FROM employees;الحالة الخاصة لتجميع جميع القيم NULL
ماذا تُرجع دالة التجميع عندما تكون كل المدخلات NULL، أو عندما لا توجد صفوف؟ إليكم تمييزًا دقيقًا يحبه القائمون بالمقابلات:
- تُرجع
SUMوAVGوMINوMAXعند التعامل مع صفوف كلها NULL أو مع صفر صفوف قيمة NULL. - تُرجع
COUNTدائمًا القيمة 0، ولا تُرجع NULL مطلقًا.
لذلك، إذا عرض تقرير إجماليات فارغة، فقد يكون السبب هو أن SUM تعاملت مع قيم كلها NULL. غلّفوها باستخدام COALESCE لعرض 0.
-- No matching rows or all bonuses NULL:
SELECT SUM(bonus) FROM employees WHERE 1 = 0; -- NULL
SELECT COUNT(bonus) FROM employees WHERE 1 = 0; -- 0
-- Present a clean zero:
SELECT COALESCE(SUM(bonus), 0) FROM employees;NULL في شروط JOIN
في عبارة ON الخاصة بعملية JOIN، تظل NULL = NULL نتيجتها UNKNOWN، ولذلك لا تتطابق مفاتيح NULL مطلقًا في equi-join. فلن يتم إقران صفين يحتوي كلاهما على NULL في مفتاح JOIN.
تتسبب هذه الحالة في إرباك من يجرون JOIN باستخدام مفاتيح خارجية اختيارية. وإذا كان مطابقة NULL مع NULL هو السلوك المقصود، فستحتاجون إلى عامل آمن مع NULL، مثل IS NOT DISTINCT FROM أو <=> من الدرس السابق.
-- Rows with region IS NULL on both sides do NOT match
SELECT *
FROM a JOIN b ON a.region = b.region;
-- To match NULL-to-NULL (ANSI):
SELECT *
FROM a JOIN b ON a.region IS NOT DISTINCT FROM b.region;قيم NULL الناتجة عن عمليات JOIN الخارجية
تقوم عمليات JOIN الخارجية بإنشاء قيم NULL للصفوف غير المتطابقة. وبعد LEFT JOIN، تكون قيمة كل عمود في الجانب الأيمن NULL للصفوف اليسرى التي لم تجد تطابقًا.
وهذا هو أساس نمط anti-join: استخدموا المرشح WHERE right_table.key IS NULL للعثور على الصفوف التي لا تملك تطابقًا، مثل العملاء الذين لا يملكون طلبات.
لكن انتبهوا: قد تؤدي تصفية عمود ناتج عن JOIN خارجية في WHERE إلى تحويلها عن طريق الخطأ إلى JOIN داخلية، وهذا هو موضوع المشهد التالي.
-- Find customers who have never ordered (anti-join)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;حيلة NULL في WHERE مع JOIN خارجية
هذه من الحيل المربكة الشائعة. تُجرون LEFT JOIN للطلبات، ثم تضيفون WHERE o.status = 'shipped'. وفجأة يختفي العملاء الذين لا يملكون طلبات، فتحوّلون JOIN الخارجية فعليًا إلى JOIN داخلية.
لماذا؟ في الصفوف غير المتطابقة تكون قيمة o.status هي NULL، وتكون نتيجة NULL = 'shipped' هي UNKNOWN، ولذلك تستبعدها WHERE. وللحفاظ على الصفوف غير المتطابقة، انقلوا الشرط إلى عبارة ON بدلًا من ذلك.
-- Accidental inner join: drops customers with no orders
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped';
-- Correct: keep unmatched customers
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id AND o.status = 'shipped';DISTINCT يعامل جميع قيم NULL على أنها متساوية
إليك التناقض الذي يفاجئ الجميع. تتجاهل الدوال التجميعية NULL، لكن DISTINCT يحتفظ بقيمة NULL واحدة بالضبط، ويعامل جميع قيم NULL على أنها نسخ مكررة من بعضها.
لذلك فإن SELECT DISTINCT bonus عند تطبيقه على القيم 100 و100 وNULL وNULL يعيد ثلاثة صفوف: 100 وNULL، وهذا كل شيء. تُدمج قيمتا NULL في قيمة واحدة، رغم أن NULL = NULL تكون UNKNOWN في المواضع الأخرى.
-- bonus: 100, 100, NULL, NULL, 200
SELECT DISTINCT bonus FROM employees;
-- Returns: 100, 200, NULL (the two NULLs become one row)GROUP BY يضم قيم NULL في مجموعة واحدة
يتبع GROUP BY القاعدة نفسها التي يتبعها DISTINCT: تُجمع جميع مفاتيح NULL في مجموعة واحدة. وهذا عكس منطق المقارنة، حيث لا تتساوى قيم NULL مع بعضها أبدًا.
لذلك يمنحك التجميع حسب عمود يقبل NULL صفًا واحدًا يمثل جميع السجلات التي يكون مفتاحها NULL، وهو غالبًا ما تحتاج إليه في التقارير. اذكر هذا التباين (بين التجميع والمقارنة) لإظهار عمق فهمك.
-- All employees with NULL department form ONE group
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department;
-- A single row where department is NULL totals all of themنقاط للحديث في المقابلة
إليك الخلاصة الجامعة التي تثير إعجاب المحاورين:
- تتجاهل الدوال التجميعية NULL؛ إذ يقسم AVG على COUNT(column)، وليس على COUNT(*).
- يحصي COUNT(*) الصفوف؛ بينما يتجاهل COUNT(col) وCOUNT(DISTINCT col) قيم NULL.
- تعيد SUM/AVG/MIN/MAX فوق أي صفوف غير موجودة القيمة NULL؛ بينما يعيد COUNT القيمة 0.
- في عمليات الربط، لا تتطابق مفاتيح NULL أبدًا؛ كما أن تصفية عمود من عملية ربط خارجية في WHERE تتحول ضمنيًا إلى عملية ربط داخلية.
- يتعامل DISTINCT وGROUP BY مع جميع قيم NULL على أنها متساوية، وهو عكس منطق المقارنة.
الخلاصة في سطر واحد: «يُتجاهل NULL عند التجميع والمقارنة، لكنه يُجمع مع القيم المماثلة عند إزالة التكرارات.»
تحقق سريع
اختبر الفرق بين التجميع والدوال التجميعية.
مراجعة
لقد أتممت الآن موضوع التعامل مع NULL استعدادًا للمقابلات:
- تقوم الدوال التجميعية بتجاهل NULL؛ إذ يقسم AVG على عدد القيم غير NULL، بينما تكون نتيجة SUM التي تحتوي على قيم NULL فقط هي NULL، وتكون نتيجة COUNT هي 0.
- يتضمن
COUNT(*)الصفوف التي تحتوي على NULL؛ أماCOUNT(col)فلا يتضمنها، ويساوي الفرق بينهما عدد قيم NULL. - مفاتيح الربط التي تكون NULL لا تتطابق أبدًا؛ وقد تؤدي تصفية أعمدة الربط الخارجي في WHERE إلى تحويله إلى ربط داخلي.
- يعمل DISTINCT وGROUP BY على ضم جميع قيم NULL في مجموعة واحدة، وهو عكس منطق المقارنة.
تذكر القاعدة: يُتجاهل NULL عند التجميع والمقارنة، لكنه يُجمع مع القيم المماثلة عند إزالة التكرارات. هذه الفكرة وحدها تجيب عن معظم أسئلة المقابلات المتعلقة بـ NULL.
الأسئلة الشائعة
هل درس «قيم NULL في التجميعات وعمليات الربط وDISTINCT» مجاني؟
نعم — نص درس «قيم NULL في التجميعات وعمليات الربط وDISTINCT» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة SQL Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة SQL Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «قيم NULL في التجميعات وعمليات الربط وDISTINCT»؟
اختلاف سلوك NULL بين التجميع والربط والتفرّد تتمرن على SQL Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ SQL Interview Prep؟
لا تُشترط خبرة سابقة. SQL Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «قيم NULL في التجميعات وعمليات الربط وDISTINCT»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس SQL Interview Prep هذا؟
نعم. كل درس في SQL Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- المنطق ثلاثي القيم وUNKNOWN
- IS NULL وIS NOT NULL والمساواة الآمنة مع NULL
- COALESCE وNULLIF وISNULL
- قيم NULL في التجميعات وعمليات الربط وDISTINCT