انتقال للمقال

ربط الجداول في الـ SQL باستخدام JOIN

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 25 دقيقة (بمعدل فنجان واحد من القهوة 😊)

المقدمة

الآن وصلنا إلى واحدة من أهم وأقوى ميزات الـ 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 في جدول الـ Instructors
  • duration_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 وجدول StudentProfiles
  • Many-to-Many Relationship: كما في مثالنا بين Students و Courses عبر Enrollments

ونرمز أو نختصر هذه الروابط برموز مثل 1..* أو *..* للدلالة عليها، مثلًا:

  • 1..1 أو 1:1 تعني One-to-One Relationship
  • 1..* أو 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 هو اختصار لـ Instructors
  • c هو اختصار لـ 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 ولكل نوع استخدام مختلف:

  1. INNER JOIN: يعرض الصفوف الصفوف المشتركة فقط من الجدولين
    ويمكنك كتابة JOIN فقط بدلاً من INNER JOIN
  2. LEFT OUTER JOIN: يعرض جميع الصفوف من الجدول الأيسر + الصفوف المشتركة من الأيمن ويمكنك كتابة LEFT JOIN فقط بدلاً من LEFT OUTER JOIN
  3. RIGHT OUTER JOIN: يعرض جميع الصفوف من الجدول الأيمن + الصفوف المشتركة من الأيسر ويمكنك كتابة RIGHT JOIN فقط بدلاً من RIGHT OUTER JOIN
  4. FULL OUTER JOIN: يعرض جميع الصفوف من الجدولين ويمكنك كتابة FULL JOIN فقط بدلاً من FULL OUTER JOIN
  5. CROSS JOIN: يعرض كل صف من الجدول الأول مع كل صف من الجدول الثاني
    تذكر أنه يستخدم الـ Cartesian Product بين الجدولين كما ذكرنا سابقًا
    لكن هذه المرة أصبح نوع مخصص من الـ JOIN ولا يستخدم ON لأنه لا يوجد شرط ربط في الـ CROSS JOIN
  6. SELF 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 مهم جدًا لسببين:
    1. لمنع ظهور الطالب مع نفسه مثلاً Ahmed Moustafa مع Ahmed Moustafa
    2. لمنع التكرار مثلاً لا نريد ظهور 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 وكيفية تصميم قواعد البيانات بشكل صحيح