Menu
flag Ar iconالعربيةdown icon

SQLite Self Join: ربط الجدول بنفسه

تعرّف على كيفية عمل Self Join في SQLite لمطابقة الصفوف داخل نفس الجدول باستخدام الـ aliases، مع أمثلة عملية على علاقة الموظف بالمدير والبيانات الهرمية.

تحتوي هذه الصفحة على محررات قابلة للتشغيل — حرّر، شغّل، وشاهد النتيجة فوراً.

الـ Self Join مجرد ربط مع Aliases

ما في شي مميز بخصوص الـ self join في SQLite. هو عبارة عن JOIN عادي، لكن صدف إن الطرفين هما نفس الجدول. الحيلة هنا إن SQLite يحتاج طريقة يميّز فيها بين النسختين، فبتعطي كل واحدة منهن alias مختلف.

بتلجأ لهالأسلوب لما يكون عندك صف في جدول يشير إلى صف آخر في نفس الجدول. المثال الكلاسيكي: جدول employees فيه كل صف يحتوي على manager_id يشير لموظف ثاني:

آدا ليس لها مدير. بوريس وكليو يتبعان لآدا. دييغو وإيسمي يتبعان لبوريس. كل هذه العلاقات موجودة داخل جدول واحد فقط، وهنا بالضبط تظهر فائدة الـ self join.

الشكل الأساسي لـ self join

لكي نحصل على كل موظف مقترنًا باسم مديره، نربط جدول employees بنفسه. نسخة تلعب دور "الموظف"، والنسخة الأخرى تلعب دور "المدير":

اقرأ الاستعلام كأنه جدولان مختلفان يتشاركان نفس التخزين. فـ e يمثّل صف الموظف، وm يمثّل صف المدير. وشرط الربط e.manager_id = m.id هو الذي يوازي بينهما: لكل موظف، ابحث في m عن الصف الذي يطابق id فيه قيمة manager_id للموظف.

لاحظ أن "آدا" لم تظهر في النتيجة. السبب أن قيمة manager_id لديها هي NULL، وINNER JOIN يتجاهل الصفوف التي لا تجد لها مطابقًا.

الإبقاء على الصفوف غير المطابقة باستخدام LEFT JOIN

إذا أردت أن تظهر جميع الأسماء في النتيجة، بما في ذلك من ليس لهم مدير، فاستبدل النوع بـ LEFT JOIN:

الآن تظهر Ada مع قيمة NULL في عمود المدير. نفس آلية self join، لكن نوع الربط يقوم بما يفعله LEFT JOIN دائمًا: يحتفظ بكل صف من الجانب الأول، ويملأ الفراغات بقيم فارغة حين لا يوجد تطابق.

هذه هي الصيغة التي تحتاجها عادةً عند عرض قائمة بالأشخاص. فعبارة "بدون مدير" معلومة مهمة بحد ذاتها، وحذف الصف ليس خيارًا منطقيًا.

الـ Aliases ليست اختيارية

جرّب تنفيذ الـ join بدون aliases، وستجد أن SQLite لا يفهم ما تقصده:

SELECT name, manager_id FROM employees JOIN employees ON manager_id = id;
-- خطأ: اسم العمود غامض: name

كل عمود يظهر مرتين — مرة من كل نسخة من الجدول — وSQLite ما يقدر يحدد أي واحد تقصد. الحل هو استخدام alias في SQL، بحيث نعطي كل نسخة اسمًا خاصًا بها. اختر أسماء مستعارة تصف الدور الذي يلعبه الصف، لا اسم الجدول:

  • e و m للموظف والمدير.
  • parent و child للعلاقات الهرمية.
  • a و b حين تقارن أزواجًا عشوائية.

هذا الـ alias هو السبب الرئيسي التي يخلي self join مقروءًا وواضحًا.

إيجاد الأزواج المتطابقة داخل نفس الجدول

الـ self join في SQLite ما يقتصر على البيانات الهرمية فقط. أي مرة تحتاج فيها تقارن صفوفًا داخل نفس الجدول، هذا النمط يناسبك. خذ مثلًا قائمة منتجات، ونبغى نطلع كل زوج من المنتجات التي لها نفس السعر:

شيئان ينبغي ملاحظتهما هنا. الأول: a.price = b.price هو شرط المطابقة الفعلي. الثاني: a.id < b.id هو ما يمنع الاستعلام من إرجاع كل زوج مرتين (مرة كـ (Mug, Notebook) ومرة أخرى كـ (Notebook, Mug))، ويمنعه أيضًا من إقران كل صف مع نفسه. حيلة < هذه تستحق الحفظ — ستصادفها كلما احتجت إلى سرد أزواج من البيانات.

الصعود مستويين في التسلسل الهرمي

self join في SQLite يتعامل مع قفزة واحدة فقط في التسلسل الهرمي. لكن ماذا لو أردت معرفة "مدير المدير" لكل موظف؟ الحل: اربط الجدول بنفسه ثلاث مرات:

كل alias جديد يعني صعود مستوى إضافي في الشجرة. هذي الطريقة تشتغل تمام لمستويين أو ثلاثة، لكنها تنهار بسرعة — لأنك تحتاج تعرف عمق التسلسل الهرمي وقت كتابة الاستعلام نفسه، وتضيف join لكل مستوى. وهذا بالضبط الجدار التي جاءت الـ recursive CTEs لكي تكسره.

متى لا تلجأ إلى self join؟

الـ self join هو الأداة المناسبة لما تحتاج أعمدة من طرفَي العلاقة في النتيجة النهائية. أما لو كنت تبغى فقط تصفّي البيانات — مثلاً تجيب كل موظف مديره هو "علي" — فإن استخدام subquery يكون عادةً أوضح وأسهل في القراءة:

لا داعي لألعاب الـ aliases، والمقصد واضح من أول نظرة. القاعدة الذهبية: هل تحتاج بيانات من كلا الصفّين في النتيجة؟ استخدم self join. هل تحتاج فقط قيمة تقارن بها؟ استخدم subquery.

أما حين تكون الهرمية بعمق غير محدّد (هياكل تنظيمية، أشجار ملفات، تعليقات متشعّبة)، فلا هذا الأسلوب ولا ذاك يصمد. هنا ندخل عالم recursive CTE.

التالي: الاستعلامات الفرعية (Subqueries)

self join و subqueries يتقاطعان في حلّ كثير من المسائل، ومعرفة أيّهما يناسب الموقف توفّر عليك ساعات من التحديق في كود SQL لاحقًا. الصفحة التالية تغوص في الاستعلامات الفرعية بأنواعها — scalar و correlated وصيغة IN — وتشرح متى يتألّق كلٌّ منها.

الأسئلة الشائعة

ما هو الـ Self Join في SQLite؟

الـ Self Join هو ببساطة JOIN عادي لكن الجدول يُربط مع نفسه. تُعطي نفس الجدول اسمين مستعارين (aliases) مختلفين حتى يتعامل معهما SQLite كأنهما مصدران منفصلان للصفوف، ثم تطابق الصفوف عبر عمود يربط صفًا بآخر — غالبًا علاقة أب/ابن مثل علاقة الموظف بالمدير.

لماذا أحتاج إلى aliases في الـ Self Join؟

بدون aliases لن يعرف SQLite أي نسخة من الجدول تقصد عند كتابة اسم العمود. إعطاء كل نسخة اسمها المستعار (مثلاً e للموظف و m للمدير) يسمح لك بكتابة e.manager_id = m.id بشكل واضح بلا لبس. هذه الـ aliases ليست اختيارية — الاستعلام لن يعمل أصلاً بدونها.

متى أستخدم Self Join بدلًا من Subquery؟

استخدم Self Join عندما تريد إظهار أعمدة من كلا الصفين في النتيجة — مثلًا اسم الموظف واسم مديره في نفس السطر. أما الـ subquery فيناسبك حين تحتاج فقط لتصفية النتائج أو جلب قيمة واحدة. أما إذا كانت لديك بيانات هرمية متعددة المستويات، فلا هذا ولا ذاك يفي بالغرض، والحل الصحيح هو الـ Recursive CTE.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن