التخلص من التكرار باستخدام DISTINCT في SQLite
عند تنفيذ SELECT بشكل افتراضي، يُرجع لك SQLite كل الصفوف المطابقة بما فيها المكررة. هنا يأتي دور DISTINCT الذي يطلب من SQLite دمج الصفوف المتطابقة في الأعمدة التي اخترتها، فلا يظهر لك إلا تركيب فريد واحد لكل حالة.
خمسة صفوف دخلت، وثلاثة خرجت. نظرت SQLite إلى عمود customer، وتجاهلت القيم المكررة، ثم أعادت صفًا واحدًا لكل قيمة فريدة. لا تتوقّع ترتيبًا معيّنًا للنتائج، فإن كان الترتيب يهمّك فأضف ORDER BY.
DISTINCT يطبَّق على قائمة الأعمدة كاملةً
هذه نقطة يقع فيها كثيرون. DISTINCT لا يختار عمودًا واحدًا لإزالة التكرار منه، بل يزيل التكرار على مستوى الصف كاملًا اعتمادًا على كل عمود ذكرته في SELECT.
كل زوج فريد من (customer, country) يظهر مرة واحدة فقط. لو ظهر نفس العميل مع دولتين مختلفتين، فسترى كلا الصفين — لأن SQLite لا يعتبرهما مكررين.
لا يوجد في SQLite صياغة اسمها DISTINCT(customer) تتجاهل بقية الأعمدة. صحيح أن الأقواس تبدو مغرية، لكن SELECT DISTINCT(customer), country يُفسَّر بنفس طريقة SELECT DISTINCT customer, country تمامًا — الأقواس هنا مجرد تجميع لتعبير. إذا كنت فعلًا تريد صفًا واحدًا لكل عميل مع اختيار دولة معينة، فهذه مهمة GROUP BY مع دالة تجميعية.
COUNT(DISTINCT col) لحساب القيم الفريدة
من أكثر الاحتياجات شيوعًا: كم عدد القيم الفريدة في عمود معين؟ الدالة COUNT(*) تحسب الصفوف، وCOUNT(col) تحسب القيم غير الفارغة (non-NULL)، أما COUNT(DISTINCT col) فتحسب القيم الفريدة غير الفارغة.
خمسة طلبات، ثلاثة عملاء فريدين، وثلاث دول مختلفة. تُعدّ COUNT(DISTINCT ...) أكثر صيغة تجميعية مفيدة لـ DISTINCT — ستلجأ إليها كلما أردت معرفة "كم عنصرًا مختلفًا ظهر فعلاً".
انتبه إلى أن SQLite لا تسمح إلا بعمود واحد فقط داخل COUNT(DISTINCT ...). ولحساب التركيبات الفريدة من عدة أعمدة، ضعها داخل استعلام فرعي هكذا: SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t).
كيف تتعامل DISTINCT مع NULL
تتمتع NULL بسمعة غريبة في SQL، فالمقارنة NULL = NULL تُرجع NULL لا TRUE. لكن DISTINCT تصنع استثناءً خاصًا: عند إزالة التكرار، تُعامل جميع قيم NULL على أنها متساوية فيما بينها.
ستحصل على ثلاثة صفوف: 'ada@example.com' و 'dan@example.com' وصف واحد بقيمة NULL. أي أن قيم NULL الثلاث اندمجت في صف واحد فقط. نفس القاعدة تنطبق على GROUP BY وعلى عمليات المجموعات مثل UNION — احفظ هذه النقطة جيدًا، فهي تفيدك حين تتساءل: "لماذا يظهر صف NULL مرة واحدة بدلًا من ثلاث مرات؟"
ترتيب تنفيذ DISTINCT قبل ORDER BY و LIMIT
جُمل SELECT تُنفَّذ وفق ترتيب منطقي ثابت: FROM ← WHERE ← GROUP BY ← HAVING ← SELECT/DISTINCT ← ORDER BY ← LIMIT. بمعنى أن DISTINCT يقوم أولًا بحذف التكرار في sql، ثم يأتي ORDER BY ليرتّب ما تبقى، وأخيرًا يقتطع LIMIT العدد المطلوب.
WHERE يُبقي أربعة صفوف، ثم يأتي DISTINCT ليتخلّص من تكرار اسم Boris، بعدها يرتّب ORDER BY النتائج أبجدياً، وأخيراً يُرجع LIMIT أول صفّين فقط. خُذ دقيقة لتتبّع هذه الخطوات بنفسك، فاللخبطة في ترتيب النتائج عادةً ما تأتي من نسيان أي خطوة تنفّذ قبل الأخرى.
الفرق بين DISTINCT و GROUP BY في SQLite
إذا كان هدفك مجرّد حذف التكرار في SQL، فإن الاستعلامين التاليين يُرجعان نفس الصفوف تماماً:
نفس النتيجة. الفرق يظهر فيما يمكنك فعله بعدها:
DISTINCTمهمته فقط: "أعطني الصفوف الفريدة" ولا شيء غير ذلك.GROUP BYمهمته: "جمّع الصفوف في مجموعات واحسب شيئًا لكل مجموعة" — مثلCOUNT(*)أوSUM(amount)أوMAX(created_at)وهكذا.
إذا وجدت نفسك تلجأ إلى DISTINCT ثم اكتشفت أنك تحتاج أيضًا إلى إجمالي لكل عميل، فهذه إشارة واضحة للانتقال إلى GROUP BY:
صفّ واحد لكل عميل، مع التجميعات التي تريدها. لا يستطيع DISTINCT فعل ذلك — فهو لا يملك طريقة للتعبير عن "صف واحد لكل مجموعة بالإضافة إلى مجموع."
أمور ينبغي الانتباه إليها
- الأداء. عادةً ما يحتاج
DISTINCTمن SQLite إلى ترتيب الصفوف أو تجزئتها (hash) ليعثر على المكرر. مع نتائج ضخمة، يساعد وجود فهرس على العمود (أو الأعمدة) المُستخدمة في إزالة التكرار. وإذا كنت تستخدمSELECT DISTINCTعلى كل أعمدة جدول عريض، اسأل نفسك: هل تحتاج فعلاً كل تلك الأعمدة؟ DISTINCT *نادر الاستخدام. إنه صحيح من ناحية اللغة — فـSELECT DISTINCT * FROM tيحذف الصفوف المكررة بالكامل — لكن إذا كان جدولك يحوي مفتاحاً أساسياً، فكل صف فريد أصلاً، وبالتالي لن يقدّم لك هذا الاستعلام شيئاً مفيداً.- لا تخلط بينه وبين
UNIQUE. فـUNIQUEقيدٌ على الجدول يمنع إدخال قيم مكررة من الأساس، أماDISTINCTفهو مرشِّح يعمل وقت الاستعلام لإخفاء المكرر من النتيجة. أداتان مختلفتان لمهمتين مختلفتين.
التالي: تعبيرات CASE
بعد أن أصبحت قادراً على تشكيل صفوف النتيجة باستخدام SELECT وWHERE وORDER BY وDISTINCT، تأتي الخطوة التالية: المنطق الشرطي داخل الاستعلام نفسه. تعبيرات CASE تتيح لك إرجاع قيم مختلفة بناءً على شروط معيّنة — وهي المكافئ في SQL لسلسلة if/else، وستتناولها الصفحة التالية.
الأسئلة الشائعة
كيف تعمل جملة SELECT DISTINCT في SQLite؟
تقوم SELECT DISTINCT بحذف الصفوف المكررة من نتيجة الاستعلام. يقارن SQLite كل الأعمدة المذكورة في قائمة SELECT ويُبقي صفًا واحدًا فقط لكل تركيبة فريدة. يتم تنفيذها بعد WHERE و JOIN، ولكن قبل ORDER BY و LIMIT.
هل يمكن استخدام DISTINCT على أكثر من عمود في SQLite؟
نعم، لكن انتبه: DISTINCT تُطبَّق دائمًا على قائمة الأعمدة بالكامل وليس على عمود واحد فقط. مثلًا SELECT DISTINCT city, country FROM users تُرجع كل زوج فريد من (city, country). لا يوجد في SQLite صياغة مثل DISTINCT(city) تتجاهل بقية الأعمدة، فإن احتجت لذلك استخدم GROUP BY مع دالة تجميعية.
كيف يتعامل DISTINCT مع قيم NULL في SQLite؟
عند تطبيق DISTINCT، يعتبر SQLite أن أي قيمتي NULL متساويتان لأغراض إزالة التكرار، وبالتالي تُدمج كل الصفوف التي تحتوي على NULL في صف واحد. هذا السلوك يختلف عن عامل = داخل WHERE حيث تكون نتيجة NULL = NULL غير معروفة (unknown). إنها قاعدة خاصة تنطبق فقط على DISTINCT و GROUP BY و UNION.
ما الفرق بين DISTINCT و GROUP BY في SQLite؟
إذا كان هدفك مجرد إزالة التكرار، فإن SELECT DISTINCT col و SELECT col FROM t GROUP BY col تعطيان نفس النتيجة تمامًا. الفرق في النية: استخدم DISTINCT عندما تريد فقط صفوفًا فريدة، واستخدم GROUP BY عندما تحتاج أيضًا إلى حساب دوال تجميعية مثل COUNT(*) أو SUM(amount) لكل مجموعة.