ربط الجداول في الـ SQL باستخدام JOIN
السلام عليكم ورحمة الله وبركاته
المقدمة
الآن وصلنا إلى واحدة من أهم وأقوى ميزات الـ SQL وهي ربط الجداول باستخدام JOIN
في قواعد البيانات الحقيقية، لن تجد جميع البيانات في جدول واحد
بل نقسم البيانات على عدة جداول مختلفة، وكل جدول يحتوي على نوع معين من البيانات
فتخيل معي أنه لدينا نظام إدارة طلاب:
- جدول للطلاب
Studentsيحتوي على معلومات الطلاب - جدول للمحاضرين
Instructorsيحتوي على معلومات المحاضرين - جدول للدورات
Coursesيحتوي على الدورات المتاحة
هنا لاحظ أن كل ما تعلمناه حتى الآن عن استعلامات SELECT كان على جدول واحد فقط
الآن لدينا عدة جداول ونريد ربطها معًا للحصول على معلومات شاملة
فمثلًا نريد معرفة أي طالب سجل في أي دورة أو عدد الطلاب في كل دورة
فهنا يجب ربط الجداول معًا لعمل تلك الـ Query
قبل أن نبدأ، دعونا نجهز الجداول اللازمة للأمثلة العملية التي سنستخدمها في هذه المقالة
تجهيز الجداول للأمثلة العملية
كالعادة قبل أن نبدأ، دعنا نتذكر شكل جدول الـ Students الذي وصلنا إليه في المقالة السابقة:
+----+-----------------+------+--------------+-------+-------+--------------------------+------------+
| id | name | age | level | grade | city | email | deleted_at |
+----+-----------------+------+--------------+-------+-------+--------------------------+------------+
| 1 | Ahmed Moustafa | 21 | Intermediate | 85.5 | Cairo | ahmed@university.edu | NULL |
| 2 | Osama Ali | 21 | Intermediate | 88.0 | Cairo | osama.ali@university.edu | NULL |
| 3 | Mohamed Adel | 23 | Advanced | 92.0 | Alex | mohamed@university.edu | NULL |
| 4 | Kamal Mahmoud | 22 | Advanced | 90.0 | Alex | kamal@university.edu | NULL |
| 5 | Ayman Hassan | 21 | Advanced | 87.5 | Giza | ayman@university.edu | NULL |
| 6 | Adam Ibrahim | NULL | Beginner | 75.0 | Giza | adam@university.edu | NULL |
| 7 | Ismail Khaled | 23 | Advanced | 95.0 | Cairo | ismail@university.edu | NULL |
+----+-----------------+------+--------------+-------+-------+--------------------------+------------+
أولاً، لننشئ جدول للمحاضرين Instructors والذي يحتوي على معلومات المحاضرين في الجامعة:
CREATE TABLE Instructors (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
department VARCHAR(100) NOT NULL
);
هذا الجدول يحتوي على:
id: الكود المميز أو الرقم التسلسلي للمحاضرname: اسم المحاضرdepartment: القسم الذي يعمل به المحاضر
الآن لنضف بعض المحاضرين:
INSERT INTO Instructors (name, department) VALUES
('Dr. Ahmed', 'Computer Science'),
('Dr. Kamal', 'Information Technology'),
('Dr. Mohamed', 'Software Engineering'),
('Dr. Khaled', 'Computer Science'),
('Dr. Ali', 'Data Science');
لنرى شكل جدول الـ Instructors الآن:
+----+-------------+------------------------+
| id | name | department |
+----+-------------+------------------------+
| 1 | Dr. Ahmed | Computer Science |
| 2 | Dr. Kamal | Information Technology |
| 3 | Dr. Mohamed | Software Engineering |
| 4 | Dr. Khaled | Computer Science |
| 5 | Dr. Ali | Data Science |
+----+-------------+------------------------+
قد يقول أحدهم أننا يمكنك عمل جدول للـ Departments ونجعل عمود department في جدول Instructors يسمى department_id ويرتبط بجدول Departments باستخدام FOREIGN KEY
وهذا صحيح، لكننا نبسط الأمور هنا، لذا لا أريدك أن تشغل بالك كثيرًا بهذه الأمور
قد نتحدث عنها في مقالة تتحدث عن الـ Database Normalization لاحقًا
الآن لننشئ جدول الدورات Courses والذي يحتوي على معلومات عن الدورات المتاحة في الجامعة:
CREATE TABLE Courses (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
instructor_id INT NULL,
duration_hours INT NOT NULL,
FOREIGN KEY (instructor_id) REFERENCES Instructors(id)
);
لاحظ أن هذا الجدول يحتوي على:
id: الكود المميز أو الرقم التسلسلي للدورةname: اسم الدورةinstructor_id: رقم المحاضر ويشير إلى عمودidفي جدول الـInstructorsduration_hours: مدة الدورة بالساعات
لاحظ أننا استخدمنا FOREIGN KEY لربط العمود instructor_id بجدول الـ Instructors
هذا يضمن أن لا يمكن إضافة دورة لمحاضر غير موجود
لنجرب إضافة دورة لمحاضر غير موجود ونرى ماذا سيحدث:
INSERT INTO Courses (name, instructor_id, duration_hours) VALUES
('Machine Learning', 99, 55);
ستظهر لنا رسالة خطأ مثل هذه:
SQL ERROR: Cannot add or update a child row: a foreign key constraint fails (`school`.`courses`, CONSTRAINT `courses_ibfk_1` FOREIGN KEY (`instructor_id`) REFERENCES `instructors` (`id`))
هذا الخطأ يخبرنا أنه لا يمكن إضافة صف في جدول Courses لأن القيمة 99 في العمود instructor_id غير موجودة في جدول Instructors
هذه هي فائدة الـ FOREIGN KEY: حماية سلامة البيانات ومنع إضافة بيانات غير منطقية
ملحوظة: الـFOREIGN KEYهو قيد يُستخدم لربط جدولين معًا
ويضمن سلامة البيانات بحيث لا يمكن إضافة قيمة في العمود إلا إذا كانت موجودة في الجدول المرتبط
يمكنك قراءة المزيد عن الـFOREIGN KEYفي مقالة ما هى أنواع الـ SQL Constraints المختلفة
من فوائد أنه يساعدنا على فصل البيانات في جداول مختلفة بدلاً من تكرارها في جدول واحد
بالتالي هنا لدينا جدول للمحاضرين وآخر للدورات، وكل دورة مرتبطة بمحاضر عبر instructor_id
بحيث نضمن بأن الدورة تهتم فقط بالـ id الخاص بالمحاضر بغض النظر عن باقي بياناته
بالتالي إذا أردنا تغير اسم المحاضر أو قسمه، لا نحتاج لتحديث أي شيء في الدورات المرتبطة به، بل فقط نعدل البيانات في جدول الـ Instructors
ومن فوائد الـ FOREIGN KEY أيضًا أنه يساعدنا في ربط الجداول معًا لاحقًا باستخدام JOIN كما سنرى في هذه المقالة
هذه الرابطة بين جدول Instructors وجدول Courses تسمى One-to-Many Relationship
لأن محاضر واحد يمكنه تدريس عدة دورات، لكن كل دورة لها محاضر واحد فقط
الآن لنضف بعض الدورات:
INSERT INTO Courses (name, instructor_id, duration_hours) VALUES
('Database Fundamentals', 1, 40), -- Dr. Ahmed (instructor_id: 1)
('Web Development', 2, 60), -- Dr. Kamal (instructor_id: 2)
('Python Programming', 3, 45), -- Dr. Mohamed (instructor_id: 3)
('Data Structures', 4, 50), -- Dr. Khaled (instructor_id: 4)
('Artificial Intelligence', 1, 55), -- Dr. Ahmed (instructor_id: 1)
('Machine Learning', 3, 55), -- Dr. Mohamed (instructor_id: 3)
('Data Science Basics', NULL, 30); -- No instructor assigned
لاحظ أن المحاضر Dr. Ali صاحب الـ id رقم 5 ليس لديه أي دورة مرتبطة به
لكن باقي المحاضرين لديهم دورات مرتبطة بهم
ولاحظ أيضًا أننا أضفنا دورة بدون محاضر عبر وضع NULL في عمود instructor_id
لنرى شكل جدول الـ Courses الآن:
+----+-------------------------+---------------+----------------+
| id | name | instructor_id | duration_hours |
+----+-------------------------+---------------+----------------+
| 1 | Database Fundamentals | 1 | 40 |
| 2 | Web Development | 2 | 60 |
| 3 | Python Programming | 3 | 45 |
| 4 | Data Structures | 4 | 50 |
| 5 | Artificial Intelligence | 1 | 55 |
| 6 | Machine Learning | 3 | 55 |
| 7 | Data Science Basics | NULL | 30 |
+----+-------------------------+---------------+----------------+
الآن لدينا جدول للدورات
لكن السؤال المهم: كيف نربط الطلاب بالدورات ؟
هنا نحتاج لجدول وسيط ولنسميه على سبيل المثال Enrollments يحتوي على من سجل في أي دورة
لكننا لن نقوم بإنشاءه لتبسيط الشرح
في هذه المقالة سنستخدم جداول الـ Students و Instructors و Courses فقط لشرح الـ JOIN لا غير
جدول الـ Enrollments هو الذي يربط بين الطلاب والدورات التي سجلوا فيها
وهذه الرابطة بهذا الشكل تسمى Many-to-Many Relationship
وتعني أنه يمكن لأكثر من طالب أن يسجل في أكثر من دورة، ويمكن لكل دورة أن يكون فيها أكثر من طالب
ويوجد أنواع مختلفة من الروابط بين الجداول مثل:
One-to-Many Relationship: كما في مثالنا بين جدولInstructorsوجدولCoursesحيث يمكن لمحاضر واحد أن يدرس عدة دوراتOne-to-One Relationship: مثل علاقة بين جدولStudentsوجدولStudentProfilesMany-to-Many Relationship: كما في مثالنا بينStudentsوCoursesعبرEnrollments
ونرمز أو نختصر هذه الروابط برموز مثل 1..* أو *..* للدلالة عليها، مثلًا:
1..1أو1:1تعنيOne-to-One Relationship1..*أو1:NتعنيOne-to-Many Relationship*..*أوM:NتعنيMany-to-Many Relationship
لإشباع فضولك، إليك شكل جدول الـ Enrollments الذي يربط بين الطلاب والدورات:
CREATE TABLE Enrollments (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrollment_date DATE NOT NULL,
FOREIGN KEY (student_id) REFERENCES Students(id),
FOREIGN KEY (course_id) REFERENCES Courses(id)
);
لكن كما قلت، لن نستخدم هذا الجدول في هذه المقالة لتبسيط الشرح
بل كل الشرح سيكون على جداول Instructors و Courses فقط
المشكلة: كيف نحصل على بيانات من عدة جداول ؟
الآن لدينا الجداول جاهزة، لكن السؤال المهم:
كيف نحصل على معلومات من أكثر من جدول في نفس الوقت ؟
لنفترض أننا نريد معرفة اسم المحاضر صاحب الـ id رقم 1 واسماء الدورات التي يشرف عليها
المشكلة هنا أن اسم المحاضر في جدول Instructors واسماء الدورات في جدول Courses
وجدول Instructors و Courses مرتبطان عبر العمود instructor_id في جدول Courses
جلب البيانات بشكل منفصل
لنجرب الطريقة البدائية بحيث نجلب البيانات من كل جدول على حدة
أولاً، لنرى الدورات:
SELECT * FROM Courses;
النتيجة:
+----+-------------------------+---------------+----------------+
| id | name | instructor_id | duration_hours |
+----+-------------------------+---------------+----------------+
| 1 | Database Fundamentals | 1 | 40 |
| 2 | Web Development | 2 | 60 |
| 3 | Python Programming | 3 | 45 |
| 4 | Data Structures | 4 | 50 |
| 5 | Artificial Intelligence | 1 | 55 |
| 6 | Machine Learning | 3 | 55 |
| 7 | Data Science Basics | NULL | 30 |
+----+-------------------------+---------------+----------------+
حسنًا، المحاضر صاحب الـ instructor_id رقم 1 يشرف على الدورات رقم 1 و 5
لكن من هو هذا المحاضر ؟
هنا نحتاج لعمل SELECT أخرى لجلب البيانات من جدول Instructors
لنبحث عن اسم المحاضر:
SELECT name FROM Instructors WHERE id = 1;
النتيجة:
+-----------+
| name |
+-----------+
| Dr. Ahmed |
+-----------+
الآن لنبحث عن أسماء الدورات التي يُشرف عليها المحاضر:
SELECT name FROM Courses WHERE instructor_id = 1;
النتيجة:
+-------------------------+
| name |
+-------------------------+
| Database Fundamentals |
| Artificial Intelligence |
+-------------------------+
إذًا Dr. Ahmed يشرف على الدورات Database Fundamentals و Artificial Intelligence
لاحظ أننا لكي نستطيع معرفة الدورات التي يشرف عليها المحاضر، كان علينا تنفيذ اثنين Query منفصلة
واحدة لجلب اسم المحاضر من جدول Instructors، وأخرى لجلب أسماء الدورات من جدول Courses
وكل هذا بشكل يدوي بحيث أننا نظرنا في الدورات وجلبنا الـ instructor_id الخاص بالمحاضر، ثم استخدمناه في استعلام آخر لجلب اسم المحاضر بشكل يدوي
ماذا لو أردنا جلب هذه البيانات لجميع المحاضرين في نفس الوقت ؟
هل سنقوم بعمل هذا يدويًا لكل محاضر ؟
هل كل محاضر سنقوم بعمل Query في جدول الـ Courses ثم Query في جدول الـ Instructors بشكل يدوي ؟
بمعادلة صغيرة سنجد أننا سنحتاج لمئات الـ Query لجلب البيانات المطلوبة
لو لدينا العديد من المحاضرين والدورات، سيكون الأمر معقدًا جدًا
هنا لدينا عدة طرق لتسهيل هذه العمليات في Query واحدة فقط
منها استخدام Subquery أو طريقة الـ JOIN التي سنشرحها في هذه المقالة
في هذه المقالة سنركز على استخدام JOIN ويمكننا تخصيص مقالة أخرى للحديث عن Subquery لاحقًا
لكن قبل أن نبدأ في شرح الـ JOIN، دعنا نفهم أولًا ماذا يحدث عندما نحاول ربط الجداول بدون استخدام JOIN
ماذا لو دمجنا الجداول ؟
لنبدأ بمحاولة بسيطة جدًا لربط الجدولين معًا بدون أي شروط
ما رأيك بأن نجرب Query مباشرة وبسيطة جدًا
SELECT i.name, c.name
FROM Instructors AS i, Courses AS c;
لاحظ أننا استخدمنا أسماء مستعارة للجداول بالـ AS لتقصير الكتابة لا أكثر:
iهو اختصار لـInstructorscهو اختصار لـCourses
ولاحظ أننا نفرق بين عمود name في جدول Instructors وعمود name في جدول Courses باستخدام أسماء الجداول المستعارة
بحيث كتبنا i.name و c.name لتحديد أي عمود name نريد
لنرى ماذا سيحدث...
+-------------+-------------------------+
| name | name |
+-------------+-------------------------+
| Dr. Ali | Database Fundamentals |
| Dr. Khaled | Database Fundamentals |
| Dr. Mohamed | Database Fundamentals |
| Dr. Kamal | Database Fundamentals |
| Dr. Ahmed | Database Fundamentals |
| Dr. Ali | Web Development |
| Dr. Khaled | Web Development |
| Dr. Mohamed | Web Development |
| Dr. Kamal | Web Development |
| Dr. Ahmed | Web Development |
| Dr. Ali | Python Programming |
| Dr. Khaled | Python Programming |
| ... | ... |
+-------------+-------------------------+
Found rows: 35
ماذا حدث ؟! حصلنا على 35 صف !
هذا يشبه الـ Cross Join وهو يستند إلى فكرة الـ Cartesian Product أو الضرب الديكارتي
حيث تم ضرب كل صف في الجدول الأول بكل صف في الجدول الثاني:Rows = Instructors × Courses بالتالي لدينا 5 صفوف في جدول Instructors مضروبة في 7 صفوف في جدول Courses
بالتالي الناتج هو 5 × 7 = 35 صف
وهذا بسبب أننا كتبنا الجداول بدون أي شروط
SELECT i.name, c.name
FROM Instructors AS i, Courses AS c;
هنا نحن فقط قلنا للـ SQL: أحضر لي كل الأسماء من جدول Instructors و كل الأسماء من جدول Courses
دون أن نحدد له كيف ترتبط هذه الجداول ببعضها أو نعطيه بعض الشروط
بالتالي الـ SQL قام بعمل ضرب ديكارتي بين الجدولين
لذا ستلاحظ أن النتيجة غير منطقية تمامًا فلدينا كل محاضر مع كل دورة
تحديد شرط لدمج الجداول
المشكلة في المحاولة السابقة أننا لم نحدد كيف ترتبط الجداول ببعضها
نحتاج لإخبار الـ SQL أن يربط الصفوف التي لها علاقة منطقية
SELECT i.name AS instructor_name, c.name AS course_name
FROM Instructors AS i, Courses AS c
WHERE c.instructor_id = i.id;
لاحظ أننا أضفنا أمر WHERE لتحديد شرط الربط بين الجدولين:
c.instructor_id = i.idيعني اربط بين جدولCoursesوInstructorsحيث يكونinstructor_idفي جدولCoursesيساويidالمحاضر في جدولInstructors- استخدمنا أيضًا
ASلتسمية الأعمدة في النتيجة بأسماء أوضحinstructor_nameوcourse_name
النتيجة:
+-----------------+-------------------------+
| instructor_name | course_name |
+-----------------+-------------------------+
| Dr. Ahmed | Database Fundamentals |
| Dr. Ahmed | Artificial Intelligence |
| Dr. Kamal | Web Development |
| Dr. Mohamed | Python Programming |
| Dr. Mohamed | Machine Learning |
| Dr. Khaled | Data Structures |
+-----------------+-------------------------+
ممتاز، لاحظ أننا حصلنا على النتيجة الصحيحة الآن
بحيث أننا لدينا الآن أسماء المحاضرين مع أسماء الدورات التي يُشرفون عليها
وكل هذا في Query واحدة فقط
لاحظ أن Dr. Ali لم يظهر في النتيجة لأنه ليس لديه أي دورة مرتبطة به
ولاحظ أيضًا أن الدورة Data Science Basics لم تظهر لأنها ليست مرتبطة بأي محاضر
وهذا بسبب الشرط الذي وضعناه في أمر الـ WHERE وهو c.instructor_id = i.id
بمعنى أننا نريد فقط الصفوف يكون فيها ارتباط بين المحاضر والدورة
هذه الطريقة هى طريقة لربط الجداول بدون استخدام JOIN
وتسمى Implicit Join أو الربط الضمني وكانت الطريقة القديمة لربط الجداول في الـ SQL
لاحظ أننا استخدمنا أمر الـ WHERE لتحديد شروط الربط بين الجداول
ويمكننا إضافة شروط تصفية أخرى في نفس أمر الـ WHERE إذا أردنا
مثلاً، إذا أردنا جلب نفس البيانات ولكن فقط للدورات التي مدتها أكثر من 50 ساعة:
SELECT i.name AS instructor_name, c.name AS course_name, c.duration_hours
FROM Instructors AS i, Courses AS c
WHERE c.instructor_id = i.id
AND c.duration_hours > 50;
النتيجة ستكون:
+-----------------+-------------------------+----------------+
| instructor_name | course_name | duration_hours |
+-----------------+-------------------------+----------------+
| Dr. Kamal | Web Development | 60 |
| Dr. Ahmed | Artificial Intelligence | 55 |
| Dr. Mohamed | Machine Learning | 55 |
+-----------------+-------------------------+----------------+
المشكلة في هذه الطريقة:
- خلط شروط الربط بين الجداول مع شروط التصفية في
WHERE - في حالة نسيان شرط ربط واحد سيؤدي إلى عملية
Cartesian Productبين الجداول مما يؤدي إلى نتائج خاطئة - صعوبة قراءة وفهم الـ
Queryخصوصًا مع زيادة عدد الجداول وشروط الربط مع شروط التصفية الأخرى - صعوبة تنفيذ أنواع مختلفة من الـ
JOINمثلLEFT JOINأوRIGHT JOINأوFULL JOINوالتي سنشرحها لاحقًا
كما قلنا في البداية، هذه الطريقة كانت الطريقة القديمة لربط الجداول في الـ SQL
وكانت مستخدمة في الإصدارات القديمة من الـ SQL وبالطبع كان بها صعوبات كما ذكرنا
مما أدى إلى تطوير طريقة أفضل وأسهل لربط الجداول باستخدام JOIN
والتي حسنت من طريقة كتابة وربط الجداول وجعلتها أوضح وأسهل في القراءة والفهم
وأيضًا جعلت من السهل تنفيذ أنواع مختلفة من الـ JOIN
ما هو الـ JOIN ؟
الـ JOIN هو الطريقة الحديثة والموصى بها لربط الجداول، خصوصًا مع الجداول الكبيرة وزيادة تعقيد الـ Query
وهو يقوم بنفس الوظيفة التي قمنا بها في المثال السابق باستخدام WHERE لربط الجداول وقلنا أن هذا يدعى Implicit Join بدون استخدام JOIN
هنا مع JOIN نقوم بعمل Explicit Join بمعنى أننا فعلًا نربط بشكل صريح باستخدام كلمة JOIN في الـ SQL
وبالطبع الطريقة القدمية باستخدام WHERE ما زالت مدعومة في الـ SQL
لكن الآن تم تحويل الفكرة لأمر مستقل بذاته وهو الـ JOIN لجعل الأمور أوضح وأسهل في القراءة والفهم
وإذا فكرت قليلًا لتجد أنه طالما أنه أصبح لدينا أمر مخصص لربط الجداول وهو الـ JOIN، فهذا يعني أننا يمكنك تحسين الربط واعطاءه أهمية خاصة ومميزات إضافية وعمق أكبر في التعامل مع الجداول
أنواع الـ JOIN
هناك عدة أنواع من JOIN ولكل نوع استخدام مختلف:
INNER JOIN: يعرض الصفوف الصفوف المشتركة فقط من الجدولين
ويمكنك كتابةJOINفقط بدلاً منINNER JOINLEFT OUTER JOIN: يعرض جميع الصفوف من الجدول الأيسر + الصفوف المشتركة من الأيمن ويمكنك كتابةLEFT JOINفقط بدلاً منLEFT OUTER JOINRIGHT OUTER JOIN: يعرض جميع الصفوف من الجدول الأيمن + الصفوف المشتركة من الأيسر ويمكنك كتابةRIGHT JOINفقط بدلاً منRIGHT OUTER JOINFULL OUTER JOIN: يعرض جميع الصفوف من الجدولين ويمكنك كتابةFULL JOINفقط بدلاً منFULL OUTER JOINCROSS JOIN: يعرض كل صف من الجدول الأول مع كل صف من الجدول الثاني
تذكر أنه يستخدم الـCartesian Productبين الجدولين كما ذكرنا سابقًا
لكن هذه المرة أصبح نوع مخصص من الـJOINولا يستخدمONلأنه لا يوجد شرط ربط في الـCROSS JOINSELF JOIN: يربط الجدول بنفسه للمقارنة بين صفوفه
الشكل العام لجميع أنواع JOIN
جميع أنواع الـ JOIN تتبع نفس الشكل الأساسي:
SELECT columns
FROM table1
[JOIN_TYPE] table2 ON table1.column = table2.column;
حيث:
table1: الجدول الأول ويسمى الجدول الأيسرtable2: الجدول الثاني ويسمى الجدول الأيمن[JOIN_TYPE]: نوع الـJOINالمستخدمINNER JOIN,LEFT JOIN,RIGHT JOIN, إلخON: نحدد بعدها شرط الربط بين الجدولين
ملاحظات مهمة:
- كلمة
INNERفيINNER JOINاختيارية، يمكنك كتابةJOINفقط وسيتم اعتبارهINNER JOINبشكل افتراضي - كلمة
OUTERفيLEFT OUTER JOINوRIGHT OUTER JOINوFULL OUTER JOINاختيارية أيضًا، يمكنك كتابةLEFT JOINأوRIGHT JOINأوFULL JOINفقط - في
CROSS JOINلا نستخدمONلأنه لا يوجد شرط ربط - في
SELF JOINنستخدم أسماء مستعارة مختلفة لنفس الجدول للتمييز بينهما - الجدول الأيسر هو الجدول الذي يأتي أولًا في أمر الـ
FROM، والجدول الأيمن هو الذي يأتي بعده في أمر الـJOIN
سنتعرف على كل نوع من هذه الأنواع بالتفصيل مع أمثلة عملية ورسومات توضيحية لتوضيح الفكرة بشكل أفضل
1. INNER JOIN - التقاطع المشترك
يعد الـ INNER JOIN هو النوع الأكثر استخدامًا والنوع الافتراضي عند كتابة JOIN بدون تحديد النوع
وهو يعرض فقط الصفوف المشتركة من الجدولين وغالبًا ما تكون هي النتيجة المطلوبة في معظم الحالات
INNER JOIN - التقاطع المشترك فقط
Courses Instructors
+-------------+-------------+
| | |
| | |
| +----------+----------+ |
| |//////////|//////////| |
| |//////////|//////////| |
| |//////////|//////////| |
| +----------+----------+ |
| | |
| | |
+-------------+-------------+
////// = النتيجة
يعرض فقط الصفوف الصفوف المشتركة من الجدولين
الدورات التي لها محاضرين فقط
مثال عملي على INNER JOIN
الآن بعد أن فهمنا المشكلة مع الطريقة القديمة، دعنا نستخدم الـ JOIN لحل نفس المثال السابق
لدينا جدول Instructors وجدول Courses، وكل دورة مرتبطة بمحاضر عبر instructor_id
لنفترض أننا نريد معرفة اسم كل دورة مع اسم المحاضر الذي يدرسها
بدون JOIN، كنا سنكتب:
SELECT c.name AS course_name, i.name AS instructor_name
FROM Courses AS c, Instructors AS i
WHERE c.instructor_id = i.id;
أما باستخدام JOIN، نكتبها هكذا:
SELECT c.name AS course_name, i.name AS instructor_name
FROM Courses AS c
JOIN Instructors AS i ON c.instructor_id = i.id;
لاحظ الفرق وهو أننا لم نعد نستخدم أمر الـ WHERE لربط الجداول
بل استخدمنا كلمة JOIN ثم حددنا اسم الجدول الذي نريد ربطه ثم نحدد شرط الربط باستخدام ON
أظن أن استخدام الـ AS قد لا يجعل الأمر واضحًا جدًا، لذا سنزيله لتبسيط الأمور:
SELECT Courses.name, Instructors.name
FROM Courses
JOIN Instructors ON Courses.instructor_id = Instructors.id;
حتى أنك حين تقرأ الـ Query هذه، ستشعر وكأنك تتحدث مع قاعدة البيانات بلغة بشرية بسيطة
حتى وأنك تتعلم الـ JOIN لأول مرة، ستفهم ما تعنيه بسهولة
SELECT Courses.name, Instructors.nameأريد اسم الدورة واسم المحاضرFROM Coursesمن جدول الدوراتJOIN Instructorsاربطه بجدول المحاضرينON Courses.instructor_id = Instructors.idحيث يكون رقم المحاضر في جدول الدورات يساوي رقم المحاضر في جدول المحاضرين
وهذه من جمال الـ SQL، حيث يمكنك كتابة الـ Query بطريقة قريبة جدًا من اللغة الطبيعية
على أي حال، لنرى ماذا ستكون نتيجة هذه الـ Query:
+-------------------------+-------------+
| name | name |
+-------------------------+-------------+
| Database Fundamentals | Dr. Ahmed |
| Artificial Intelligence | Dr. Ahmed |
| Web Development | Dr. Kamal |
| Python Programming | Dr. Mohamed |
| Machine Learning | Dr. Mohamed |
| Data Structures | Dr. Khaled |
+-------------------------+-------------+
هنا لدينا كل دورة يقابلها اسم المحاضر الذي يدرسها
أهم شيء ستلاحظه هو أن المحاضر Dr. Ali لم يظهر في النتيجة لأنه ليس لديه أي دورات
وكذلك الدورة Data Science Basics لم تظهر لأنها ليس لها محاضر instructor_id = NULL
وهذه هي طبيعة INNER JOIN: فقط الصفوف الصفوف المشتركة تظهر
بمعنى أنه لن يظهر أي محاضر ليس لديه دورات، ولن تظهر أي دورة ليس لها محاضر
ولاحظ أن كلا العمودين في النتيجة يسمى name لأننا لم نستخدم AS لتسمية الأعمدة في النتيجة
لذا إذا أردنا تمييز الأعمدة في النتيجة، يمكننا استخدام AS كما فعلنا في المثال الأول حين كتبنا SELECT Courses.name AS course_name, Instructors.name AS instructor_name
لاحظ أيضًا أننا نكتب اسم الجدول قبل اسم العمود لتحديد من أي جدول نريد جلب العمود
وهذا أضمن في الحالات التي يكون فيها نفس اسم العمود موجود في أكثر من جدول
لكن لو كان العمود المراد جلبه موجود في جدول واحد فقط، فيمكننا كتابته بدون اسم الجدول وسيفهم الـ SQL من أي جدول نريد جلب العمود
2. LEFT JOIN - جميع البيانات من الجدول الأيسر
LEFT JOIN أو LEFT OUTER JOIN يختلف عن INNER JOIN في أنه يعرض جميع الصفوف من الجدول الأيسر
حتى لو لم يكن لها صفوف مشتركة في الجدول الأيمن
وفي حالة عدم وجود صفوف مشتركة، تظهر القيم كـ NULL
بمعنى أنه يعرض الصفوف المشتركة بالإضافة إلى جميع الصفوف من الجدول الأيسر
LEFT JOIN - الجدول الأيسر كاملاً
Courses Instructors
+-------------+-------------+
|/////////////| |
|/////////////| |
|/////////////+----------+ |
|/////////////|//////////| |
|/////////////|//////////| |
|/////////////|//////////| |
|/////////////+----------+ |
|/////////////| |
|/////////////| |
+-------------+-------------+
////// = النتيجة
يعرض جميع الصفوف من الجدول الأيسر
+ الصفوف المشتركة من الجدول الأيمن
جميع الدورات سواء لها محاضرين أم لا
مثال عملي على LEFT JOIN
في المثال السابق باستخدام INNER JOIN، لم تظهر الدورات التي ليس لها محاضر
لكن باستخدام LEFT JOIN، يمكننا عرض جميع الدورات سواء كان لها محاضر أم لا
لذا سنستخدم نفس الـ Query السابقة ولكن سنغير نوع الـ JOIN إلى LEFT JOIN:
SELECT Courses.name AS course_name, Instructors.name AS instructor_name
FROM Courses
LEFT JOIN Instructors ON Courses.instructor_id = Instructors.id;
النتيجة ستكون:
+-------------------------+-------------+
| name | name |
+-------------------------+-------------+
| Database Fundamentals | Dr. Ahmed |
| Web Development | Dr. Kamal |
| Python Programming | Dr. Mohamed |
| Data Structures | Dr. Khaled |
| Artificial Intelligence | Dr. Ahmed |
| Machine Learning | Dr. Mohamed |
| Data Science Basics | NULL |
+-------------------------+-------------+
لاحظ أن Data Science Basics ظهرت في النتيجة لكن بقيمة NULL في عمود اسم المحاضر
لأنها ليس لها محاضر أي أن instructor_id = NULL
لأن LEFT JOIN يعرض جميع الصفوف من الجدول الأيسر وهو جدول Courses في هذه الحالة
بغض النظر عما إذا كان لديها محاضر في جدول Instructors أم لا
لاحظ أيضًا أن المحاضر Dr. Ali لم يظهر في النتيجة لأنه ليس لديه أي دورات
لأن LEFT JOIN يهتم بالجدول الأيسر فقط وهو جدول الـ Courses، والمحاضرين الذين ليس لديهم دورات لن يظهروا
إن حاولت احضار الـ id و instructor_id و name و department من جدول الـ Instructors سترى أن جميع هذه القيم ستكون NULL بالنسبة للدورة Data Science Basics
لتوضيح ما أعنيه سنقوم بكتابة نفس الـ Query ولكن هذه المرة سنجعل SELECT يجلب جميع الأعمدة من كلا الجدولين:
SELECT *
FROM Courses
LEFT JOIN Instructors ON Courses.instructor_id = Instructors.id;
النتيجة ستكون:
+----+-------------------------+---------------+----------------+------+-------------+------------------------+
| id | name | instructor_id | duration_hours | id | name | department |
+----+-------------------------+---------------+----------------+------+-------------+------------------------+
| 1 | Database Fundamentals | 1 | 40 | 1 | Dr. Ahmed | Computer Science |
| 2 | Web Development | 2 | 60 | 2 | Dr. Kamal | Information Technology |
| 3 | Python Programming | 3 | 45 | 3 | Dr. Mohamed | Software Engineering |
| 4 | Data Structures | 4 | 50 | 4 | Dr. Khaled | Computer Science |
| 5 | Artificial Intelligence | 1 | 55 | 1 | Dr. Ahmed | Computer Science |
| 6 | Machine Learning | 3 | 55 | 3 | Dr. Mohamed | Software Engineering |
| 7 | Data Science Basics | NULL | 30 | NULL | NULL | NULL |
+----+-------------------------+---------------+----------------+------+-------------+------------------------+
لاحظ في الصف الأخير الذي يمثل الدورة Data Science Basics أن جميع أعمدة جدول Instructors هي NULL
لأن هذه الدورة ليس لها محاضر، وبالتالي لا توجد صفوف مشتركة من جدول الـ Instructors مرتبط بدورة Data Science Basics
بالمناسبة الـ id الأول في النتيجة هو id الخاص بجدول Courses
والـ id الثاني في النتيجة هو id الخاص بجدول Instructors
والـ name الأول هو اسم الدورة من جدول Courses
والـ name الثاني هو اسم المحاضر من جدول Instructors
هذا بسبب أننا استخدمنا SELECT * لجلب جميع الأعمدة من كلا الجدولين بدون وضع أسماء مستعارة للأعمدة باستخدام AS
إيجاد الدورات التي ليس لها محاضر
يمكننا استخدام LEFT JOIN مع WHERE لإيجاد الدورات التي ليس لها محاضر:
SELECT Courses.name, Courses.duration_hours
FROM Courses
LEFT JOIN Instructors ON Courses.instructor_id = Instructors.id
WHERE Instructors.id IS NULL;
لاحظ أننا استخدمنا LEFT JOIN لعرض جميع الدورات بغض النظر عن وجود محاضر لها في جدول Instructors أم لا
ثم استخدمنا أمر الـ WHERE لتصفية النتائج WHERE Instructors.id IS NULL
بحيث نقول فقط أريد الدورات التي ليس لها محاضر
لاحظ أن الشرط كان Instructors.id IS NULL لأنه كما قلنا، في حالة استخدامنا للـ LEFT JOIN ستكون جميع أعمدة جدول Instructors بقيم NULL بالنسبة للدورات التي ليس لها محاضر
بالتالي ستجد أن الـ id و name و department أو أي عمود خاص بجدول الـ Instructors ستكون NULL لهذه الدورات
بالتالي بو كان الشرط Instructors.id IS NULL أو Instructors.name IS NULL أو أي عمود آخر من جدول Instructors سنحصل على نفس النتيجة
النتيجة:
+---------------------+----------------+
| name | duration_hours |
+---------------------+----------------+
| Data Science Basics | 30 |
+---------------------+----------------+
هذه هي الدورة التي ليس لها محاضر يشرف عليها حتى الآن
3. RIGHT JOIN - جميع البيانات من الجدول الأيمن
RIGHT JOIN أو RIGHT OUTER JOIN هو عكس LEFT JOIN
يعرض جميع الصفوف من الجدول الأيمن حتى لو لم يكن لها صفوف مشتركة في الجدول الأيسر
وفي حالة عدم وجود صفوف مشتركة، تظهر القيم كـ NULL
بمعنى أنه يعرض المشترك بالإضافة إلى جميع الصفوف من الجدول الأيمن
RIGHT JOIN - الجدول الأيمن كاملاً
Courses Instructors
+-------------+-------------+
| |/////////////|
| |/////////////|
| +----------+/////////////|
| |//////////|/////////////|
| |//////////|/////////////|
| |//////////|/////////////|
| +----------+/////////////|
| |/////////////|
| |/////////////|
+-------------+-------------+
////// = النتيجة
يعرض جميع الصفوف من الجدول الأيمن
+ الصفوف المشتركة من الجدول الأيسر
جميع المحاضرين سواء لديهم دورات أم لا
مثال عملي على RIGHT JOIN
سنستخدم نفس الـ Query السابقة ولكن هذه المرة سنغير نوع الـ JOIN إلى RIGHT JOIN:
SELECT Courses.name AS course_name, Instructors.name AS instructor_name, Instructors.department
FROM Courses
RIGHT JOIN Instructors ON Courses.instructor_id = Instructors.id;
هنا نقول: "أريد جميع المحاضرين سواء لديهم دورات أم لا"
لاحظ أننا بدأنا بجدول Courses ثم عملنا RIGHT JOIN مع جدول Instructors
بالتالي الـ SQL سيعرض جميع المحاضرين الجدول الأيمن سواء كان لديهم دورات أم لا
النتيجة ستكون:
+-------------------------+-----------------+------------------------+
| course_name | instructor_name | department |
+-------------------------+-----------------+------------------------+
| Database Fundamentals | Dr. Ahmed | Computer Science |
| Artificial Intelligence | Dr. Ahmed | Computer Science |
| Web Development | Dr. Kamal | Information Technology |
| Python Programming | Dr. Mohamed | Software Engineering |
| Machine Learning | Dr. Mohamed | Software Engineering |
| Data Structures | Dr. Khaled | Computer Science |
| NULL | Dr. Ali | Data Science |
+-------------------------+-----------------+------------------------+
لاحظ أن المحاضر Dr. Ali ظهر في النتيجة لكن بقيمة NULL في عمود course_name
لأنه ليس لديه أي دورات بعد
وهذا هو سلوك RIGHT JOIN: يعرض جميع الصفوف من الجدول الأيمن بغض النظر عما إذا كان لديها قيم في الجدول الأيسر أم لا
إيجاد المحاضرين الذين ليس لديهم دورات
كل ما ذكرناه في LEFT JOIN ينطبق هنا أيضًا في RIGHT JOIN لكن بالعكس
بحيث أننا لو عرضنا كل الأعمدة من كلا الجدولين باستخدام SELECT * سنجد أن جميع أعمدة جدول Courses ستكون NULL بالنسبة للمحاضر Dr. Ali لأنه ليس لديه أي دورات
بالتالي يمكننا استخدام RIGHT JOIN مع WHERE لإيجاد المحاضرين الذين ليس لديهم دورات:
SELECT Instructors.name, Instructors.department
FROM Courses
RIGHT JOIN Instructors ON Courses.instructor_id = Instructors.id
WHERE Courses.id IS NULL;
النتيجة ستكون:
+-----------+--------------+
| name | department |
+-----------+--------------+
| Dr. Ali | Data Science |
+-----------+--------------+
العلاقة بين RIGHT JOIN و LEFT JOIN
في الحقيقة، الـ Query السابقة باستخدام RIGHT JOIN:
SELECT Courses.name AS course_name, Instructors.name AS instructor_name
FROM Courses
RIGHT JOIN Instructors ON Courses.instructor_id = Instructors.id;
تعطي نفس النتيجة التي تعطيها هذه الـ Query باستخدام LEFT JOIN:
SELECT Courses.name AS course_name, Instructors.name AS instructor_name
FROM Instructors
LEFT JOIN Courses ON Instructors.id = Courses.instructor_id;
الفرق الوحيد هو أننا عكسنا ترتيب الجداول
في RIGHT JOIN بدأنا بـ Courses الجدول الأيسر وربطناه بـ Instructors الجدول الأيمن
في LEFT JOIN بدأنا بـ Instructors الجدول الأيسر وربطناه بـ Courses
ملحوظة: في الحقيقة،RIGHT JOINنادرًا ما يُستخدم في الحياة العملية
لأنه يمكن دائمًا تحويله إلىLEFT JOINعن طريق تبديل ترتيب الجداول
فمثلاً بدلاً منFROM A RIGHT JOIN Bيمكنك كتابةFROM B LEFT JOIN Aوستحصل على نفس النتيجة
4. FULL OUTER JOIN - جميع البيانات من الجدولين
FULL OUTER JOIN أو FULL JOIN يجمع بين LEFT JOIN و RIGHT JOIN
يعرض جميع الصفوف من الجدولين سواء كان هناك تطابق أم لا
وفي حالة عدم وجود صفوف مشتركة، تظهر القيم كـ NULL في الجانب الذي لا يوجد فيه تطابق
FULL OUTER JOIN - الجدولان كاملان
Courses Instructors
+-------------+-------------+
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
|/////////////|/////////////|
+-------------+-------------+
////// = النتيجة
يعرض جميع الصفوف من الجدولين
سواء كان هناك تطابق أم لا
جميع الدورات + جميع المحاضرين
ملحوظة:MySQLلا يدعمFULL OUTER JOINبشكل مباشر
لكنPostgreSQLوSQL ServerوOracleيدعمونه
إذا كنت تستخدمMySQL، يمكنك محاكاةFULL OUTER JOINباستخدامUNIONبينLEFT JOINوRIGHT JOIN
مثال عملي على FULL OUTER JOIN
لنستمر مع جدول Courses و Instructors
تذكر أن لدينا Dr. Ali وهو محاضر ليس لديه أي دورات
وكذلك لدينا Data Science Basics وهي دورة ليس لها محاضر
وعندما كنا نقوم بعمل Courses LEFT JOIN Instructors كنا نرى الدورة Data Science Basics تملك قيم NULL في الأعمدة الخاصة بجدول Instructors لأنها ليس لها محاضر مرتبط بها
وعندما كنا نقوم بعمل Courses RIGHT JOIN Instructors كنا نرى المحاضر Dr. Ali يملك قيم NULL في الأعمدة الخاصة بجدول Courses لأنه ليس لديه أي دورات مرتبطة به
في حالة استخدامنا لـ FULL OUTER JOIN، سنرى كلا الحالتين معًا سنرى الدورة Data Science Basics والمحاضر Dr. Ali في نفس النتيجة مع قيم NULL في الأعمدة المناسبة
لنفترض أننا نستخدم قاعدة بيانات تدعم FULL OUTER JOIN مثل PostgreSQL
ونريد عرض جميع الدورات وجميع المحاضرين:
SELECT Courses.name AS course_name, Courses.duration_hours, Instructors.name AS instructor_name, Instructors.department
FROM Courses
FULL JOIN Instructors ON Courses.instructor_id = Instructors.id;
النتيجة ستكون:
+-------------------------+-----------------+-----------------+------------------------+
| course_name | duration_hours | instructor_name | department |
+-------------------------+-----------------+-----------------+------------------------+
| Database Fundamentals | 40 | Dr. Ahmed | Computer Science |
| Web Development | 60 | Dr. Kamal | Information Technology |
| Python Programming | 45 | Dr. Mohamed | Software Engineering |
| Data Structures | 50 | Dr. Khaled | Computer Science |
| Artificial Intelligence | 55 | Dr. Ahmed | Computer Science |
| Machine Learning | 55 | Dr. Mohamed | Software Engineering |
| Data Science Basics | 30 | NULL | NULL |
| NULL | NULL | Dr. Ali | Data Science |
+-------------------------+-----------------+-----------------+------------------------+
لاحظ أن Dr. Ali ظهر في النتيجة بقيم NULL في العمود course_name و duration_hours لأنها ليس لديه أي دورات مرتبطة به
وكذلك الدورة Data Science Basics ظهرت بقيم NULL في عمود instructor_name و department لأنها ليس لها محاضر مرتبط بها
وهذا هو سلوك FULL OUTER JOIN: يعرض جميع الصفوف من الجدولين
محاكاة FULL OUTER JOIN في MySQL
بما أن MySQL لا يدعم FULL OUTER JOIN مباشرة، يمكننا محاكاته باستخدام UNION:
-- LEFT JOIN
SELECT
Courses.name AS course_name,
Courses.duration_hours,
Instructors.name AS instructor_name,
Instructors.department
FROM Courses
LEFT JOIN Instructors ON Courses.instructor_id = Instructors.id
UNION
-- RIGHT JOIN
SELECT
Courses.name AS course_name,
Courses.duration_hours,
Instructors.name AS instructor_name,
Instructors.department
FROM Courses
RIGHT JOIN Instructors ON Courses.instructor_id = Instructors.id;
هنا استخدمنا UNION لدمج نتائج LEFT JOIN مع RIGHT JOIN
الـ UNION سيزيل التكرارات ويعطينا جميع الصفوف من الجدولين
النتيجة ستكون نفس نتيجة FULL JOIN التي رأيناها سابقًا
هل تتخيل أن كل هذا هو في Query واحدة فقط ؟
وتم اختصارها في نوع الـ FULL JOIN الذي يجعل الأمور أسهل في القراءة والفهم
ملحوظة: عند استخدامUNION، تأكد من أن كلا الجزئين لديهم نفس عدد الأعمدة ونفس أنواع البيانات في الأعمدة المقابلة لضمان نجاح العملية
ويمكنك استخدامUNION ALLلكنها تقوم بدمج كل النواتج من الجدولين بدون إزالة التكرارات
أماUNIONفيقوم بدمج النواتج ويزيل التكرارات
5. CROSS JOIN - الضرب الديكارتي
CROSS JOIN يقوم بعمل ضرب ديكارتي بين الجدولين
بمعنى أنه يربط كل صف من الجدول الأول مع كل صف من الجدول الثاني
وهذا ما رأيناه في بداية المقالة عندما حاولنا ربط الجداول بدون شروط
لاحظ أن CROSS JOIN لا يستخدم كلمة ON لأنه لا يوجد شرط ربط
فهو ببساطة يأخذ كل صف ويربطه مع جميع الصفوف في الجدول الآخر
CROSS JOIN - الضرب الديكارتي
Instructors Courses
+-------------+-------------+
| | |
| +-------+ | +-------+ |
| | i1 |--|--| c1 | |
| | i2 |--|--| c2 | |
| | i3 |--|--| c3 | |
| +-------+ | +-------+ |
| | |
+-------------+-------------+
|
v
+-------+-------+
| I | C |
+-------+-------+
| i1 | c1 | كل صف من الجدول الأول
| i1 | c2 | يرتبط مع
| i1 | c3 | كل صف من الجدول الثاني
| i2 | c1 |
| i2 | c2 |
| i2 | c3 |
| i3 | c1 |
| ... | ... |
+-------+-------+
عدد الصفوف = الجدول الأول × الجدول الثاني
مثال عملي على CROSS JOIN
لنستخدم جدول Instructors و Courses لفهم الـ CROSS JOIN
تذكر أن لدينا 5 محاضرين و 7 دورات
SELECT Instructors.name AS instructor_name, Courses.name AS course_name
FROM Instructors
CROSS JOIN Courses;
لاحظ أننا لم نستخدم ON لأن CROSS JOIN لا يحتاج لشرط ربط
النتيجة ستكون:
+-----------------+-----------------------+
| instructor_name | course_name |
+-----------------+-----------------------+
| Dr. Ali | Database Fundamentals |
| Dr. Khaled | Database Fundamentals |
| Dr. Mohamed | Database Fundamentals |
| Dr. Kamal | Database Fundamentals |
| Dr. Ahmed | Database Fundamentals |
| Dr. Ali | Web Development |
| Dr. Khaled | Web Development |
| Dr. Mohamed | Web Development |
| Dr. Kamal | Web Development |
| Dr. Ahmed | Web Development |
| Dr. Ali | Python Programming |
| Dr. Khaled | Python Programming |
| ... | ... |
+-----------------+-----------------------+
Found rows: 35
لاحظ أن كل محاضر مرتبط بكل دورة
لأن CROSS JOIN يقوم بعمل ضرب ديكارتي بين الجدولين
لذا سيكون لدينا 5 محاضرين مضروبة في 7 دورات فالنتيجة ستكون 5 × 7 = 35 صفًاCROSS JOIN نادرًا ما يُستخدم في الحياة العملية لأنه ينتج كمية كبيرة من البيانات
واستخداماته تكاد تكون معدومة وبلا فائدة في معظم الحالات
لأنه فقط يقوم بإنشاء جميع الاجتماعات الممكنة بين الصفوف في الجدولين
6. SELF JOIN - ربط الجدول بنفسه
SELF JOIN ليس نوعًا منفصلًا من JOIN، بل هو استخدام خاص لأي نوع من الـ JOIN
حيث نقوم بربط الجدول بنفسه للمقارنة بين صفوفه المختلفة
لكن كيف نربط جدول بنفسه ؟
الفكرة هي أننا نتعامل مع نفس الجدول كأنه جدولين مختلفين باستخدام أسماء مستعارة مختلفة لكل نسخة من الجدول
SELF JOIN - ربط الجدول بنفسه
Students s1 Students s2
+-------------+-------------+
| | |
| +-------+ | +-------+ |
| | Ahmed | | | Ahmed | |
| | Osama |--|--| Osama | |
| | Kamal | | | Kamal | |
| +-------+ | +-------+ |
| | |
+-------------+-------------+
|
v
نفس الجدول مرتين بأسماء مستعارة مختلفة
لمقارنة الصفوف مع بعضها البعض
مثال عملي على SELF JOIN
لنفترض أننا نريد معرفة أي طلاب يسكنون في نفس المدينة
الفكرة هي أننا سنربط جدول Students بنفسه حيث نقارن مدينة كل طالب مع مدينة باقي الطلاب:
SELECT s1.name AS student_1, s2.name AS student_2, s1.city
FROM Students s1
INNER JOIN Students s2 ON s1.city = s2.city AND s1.id < s2.id;
لاحظ أننا:
- استخدمنا
s1وs2كأسماء مستعارة لنفس الجدولStudents - استخدمنا
INNER JOINلعرض الصفوف المشتركة - شرط الـ
JOINوهوs1.city = s2.cityيعني الطلاب الذين لهم نفس المدينة أي الطالب من الجدولs1و الطالب من الجدولs2يسكنان في نفس المدينة - الشرط
s1.id < s2.idمهم جدًا لسببين:- لمنع ظهور الطالب مع نفسه مثلاً
Ahmed MoustafaمعAhmed Moustafa - لمنع التكرار مثلاً لا نريد ظهور
Ahmed MoustafaمعOsama AliوOsama AliمعAhmed Moustafaفي نفس النتيجة
- لمنع ظهور الطالب مع نفسه مثلاً
النتيجة:
+-----------------+-----------------+-------+
| student_1 | student_2 | city |
+-----------------+-----------------+-------+
| Ahmed Moustafa | Osama Ali | Cairo |
| Ahmed Moustafa | Ismail Khaled | Cairo |
| Osama Ali | Ismail Khaled | Cairo |
| Mohamed Adel | Kamal Mahmoud | Alex |
| Ayman Hassan | Adam Ibrahim | Giza |
+-----------------+-----------------+-------+
- في حالة استخدامنا للشرط
s1.id = s2.idسنحصل على تكرار لكل طالب مع نفسه - وفي حالة استخدامنا للشرط
s1.id != s2.idسنحصل على التكرار المزدوج لكل زوج من الطلاب الذين يسكنون في نفس المدينة
بمعنى أنAhmed MoustafaمعOsama AliوOsama AliمعAhmed Moustafaسيظهران في النتيجة
بمعنى آخر أننا سنجدAhmedموجود في العمودstudent_1وOsamaفي العمودstudent_2
ثم في صف آخر نجدOsamaفي العمودstudent_1وAhmedفي العمودstudent_2 - وفي حالة استخدامنا للشرط
s1.id < s2.idسنتجنب هذه المشاكل ونحصل على النتيجة المطلوبة فقط - وفي حال عكسنا الشرط إلى
s1.id > s2.idسنحصل على نفس النتيجة ولكن بترتيب مختلف
يمكنك تجربة هذه الشروط المختلفة لترى الفرق في النتائج
مثال آخر: إيجاد الطلاب الأكبر سنًا من طالب معين
SELECT s1.name AS student, s2.name AS older_student, s1.age AS age_1, s2.age AS age_2
FROM Students s1
INNER JOIN Students s2 ON s1.age < s2.age
WHERE s1.name = 'Ahmed Moustafa';
هنا نقارن عمر Ahmed Moustafa مع أعمار باقي الطلاب ونعرض من هم أكبر منه سنًا
النتيجة:
+-----------------+---------------+--------+--------+
| student | older_student | age_1 | age_2 |
+-----------------+---------------+--------+--------+
| Ahmed Moustafa | Mohamed Adel | 21 | 23 |
| Ahmed Moustafa | Kamal Mahmoud | 21 | 22 |
| Ahmed Moustafa | Ismail Khaled | 21 | 23 |
+-----------------+---------------+--------+--------+
لاحظ أننا وجدنا الطلاب الذين هم أكبر سنًا من Ahmed Moustafa وعرضنا أعمارهم أيضًا
وإذا أردنا العثور على الطلاب الأصغر سنًا منه، يمكننا تغيير شرط الـ JOIN إلى s1.age > s2.age
وإذا أردت معرفة كل الطلاب بالنسبة لباقي الطلاب من هم أكبر منهم وأصغر يمكنك ازالة أمر الـ WHERE تمامًا
ويمكنك استخدام أي نوع من أنواع الـ JOIN الأخرى في الـ SELF JOIN حسب احتياجك
الخلاصة
حسنًا لا أريد أن أطيل أكثر من ذلك، كنت أود إعطاء أمثلة أكثر في كل نوع من أنواع الـ JOIN لكن المقالة ستكون طويلة جدًا
لذا سنكتفي بهذا القدر في هذه المقالة
وأظن أنني قد أديت ما يكفي من توضيح فكرة الـ JOIN وأنواعه المختلفة
وهو في الحقيقة من أهم المواضيع التي يجب أن يتقنها أي شخص يعمل مع قواعد البيانات
وأيضًا لكي لا أنسى، تعمدت أن الأمثلة على جدولين فقط لكي لا تتشتت الأفكار
لكن في الحقيقة يمكنك ربط أكثر من جدولين في نفس الـ Query باستخدام JOIN عدة مرات
بمعنى أننا نستطيع استخدام JOIN لربط جدول ثالث ورابع وخامس وهكذا في نفس الـ Query
لكن لم أرد أن أعقد الأمور أكثر في هذه المقالة ولا ازيد من طولها
وأيضًا لأجعلك تبحث قليلًا من مصادر أخرى وتجرب بنفسك وتتعلم من أشخاص آخرين
لكي لا تأخذ المعلومة من زاوية واحدة فقط
ولأن ما الممتع في التعلم إذا لم تبحث وتكتشف بنفسك أمور جديدة ؟
لذا أرجو أن تكون قد استفدت من هذه المقالة وفهمت كيفية ربط الجداول باستخدام JOIN
ملخص أنواع JOIN:
| النوع | الوصف |
|---|---|
INNER JOIN |
الصفوف المشتركة فقط |
LEFT JOIN |
جميع بيانات الجدول الأيسر + الصفوف المشتركة |
RIGHT JOIN |
جميع بيانات الجدول الأيمن + الصفوف المشتركة |
FULL JOIN |
جميع البيانات من الجدولين |
CROSS JOIN |
كل صف مع كل صف الضرب الديكارتي |
SELF JOIN |
ربط الجدول بنفسه |
نصائح مهمة:
- استخدم أسماء مستعارة للجداول لتسهيل كتابة الاستعلامات مثل
sللـStudents - انتبه لـ شرط الربط في
ONوتأكد من أنه صحيح - استخدم النوع المناسب من
JOINحسب احتياجك - يمكنك ربط أكثر من جدولين في نفس الاستعلام
- استخدم
LEFT JOINمعWHERE IS NULLلإيجاد الصفوف التي ليس لها صفوف مشتركة
في المقالة القادمة، سنتعلم عن مواضيع متقدمة أكثر في الـ SQL مثل الـ Subqueries و الـ Views و الـ Indexes
لكن أظن أن المقالة القادمة ستكون عن الـ Normalization وكيفية تصميم قواعد البيانات بشكل صحيح