ربط الجداول في SQL: JOIN
قواعد البيانات الحقيقية ليست جدولاً واحداً ضخماً، بل عدة جداول مترابطة: جدول للطلاب، وجدول للكورسات، وجدول يربط بينهما. هذا التنظيم اسمه قاعدة البيانات العلائقية (Relational Database)، وهو سر قوة SQL. في هذا الدرس ستتعلم JOIN: الأمر الذي يجمع البيانات من عدة جداول في نتيجة واحدة.
لماذا لا نضع كل شيء في جدول واحد؟
Section titled “لماذا لا نضع كل شيء في جدول واحد؟”تخيّل جدولاً واحداً للاشتراكات:
| student | course |
|---|---|
| سارة | Python |
| سارة | SQL |
| منى | Python |
يبدو بسيطاً، لكن ماذا لو غيّرت سارة اسمها في النظام؟ أو تغيّر اسم كورس Python؟ ستحتاج تعديل كل صف يظهر فيه الاسم، ولو نسيت صفاً واحداً أصبحت البيانات متناقضة.
الحل: كل معلومة تُخزَّن في مكان واحد فقط، والجداول تشير إلى بعضها بالأرقام.
الجداول الثلاثة
Section titled “الجداول الثلاثة”إلى جانب جدول الطلاب الذي نعرفه (سارة، وعمر، ومنى، ويوسف، وليلى، وأحمد، بالأرقام من 1 إلى 6)، نضيف:
CREATE TABLE courses ( id INTEGER PRIMARY KEY, title TEXT NOT NULL);INSERT INTO courses (id, title) VALUES (1, 'Python'), (2, 'HTML'), (3, 'SQL');
CREATE TABLE enrollments ( student_id INTEGER REFERENCES students(id), course_id INTEGER REFERENCES courses(id));INSERT INTO enrollments (student_id, course_id) VALUES (1, 1), (1, 3), (2, 2), (3, 1), (4, 3), (6, 1);جدول enrollments (الاشتراكات) لا يحتوي أسماء، بل أرقاماً فقط: الصف (1, 3) يعني «الطالب رقم 1 مشترك في الكورس رقم 3»، أي سارة في SQL.
المفتاح الأجنبي (FOREIGN KEY)
Section titled “المفتاح الأجنبي (FOREIGN KEY)”REFERENCES students(id) تجعل student_id مفتاحاً أجنبياً: قيمته يجب أن تكون رقماً موجوداً فعلاً في جدول الطلاب. جرّب إضافة اشتراك لطالب غير موجود:
INSERT INTO enrollments (student_id, course_id) VALUES (99, 1);الناتج: FOREIGN KEY constraint failed
قاعدة البيانات رفضت ربط اشتراك بطالب لا وجود له. (في SQLite يجب تفعيل هذا الفحص بالأمر PRAGMA foreign_keys = ON;، أما MySQL فتفحصه افتراضياً.)
تخيّل المفتاح الأجنبي مثل رقم الغرفة على مفتاح الفندق: يجب أن يشير لغرفة موجودة فعلاً.
INNER JOIN: الربط الداخلي
Section titled “INNER JOIN: الربط الداخلي”الآن نريد الأسماء الحقيقية بدلاً من الأرقام:
SELECT students.name, courses.titleFROM enrollmentsJOIN students ON enrollments.student_id = students.idJOIN courses ON enrollments.course_id = courses.id;الناتج:
| name | title |
|---|---|
| سارة | Python |
| سارة | SQL |
| عمر | HTML |
| منى | Python |
| يوسف | SQL |
| أحمد | Python |
JOIN students ON ...: اربط كل اشتراك بالطالب الذي يطابق رقمه.ON: شرط الربط: أي عمود يساوي أي عمود.students.name: اسم الجدول ثم نقطة ثم العمود، لنحدد من أي جدول نأخذه.JOINوحدها تعنيINNER JOIN: الربط الداخلي، الذي يُظهر فقط الصفوف التي لها تطابق في الجدولين.
الأسماء المختصرة للجداول
Section titled “الأسماء المختصرة للجداول”كتابة أسماء الجداول كاملة متعبة. أعطِ كل جدول اسماً قصيراً:
SELECT s.name, c.titleFROM enrollments eJOIN students s ON e.student_id = s.idJOIN courses c ON e.course_id = c.idWHERE c.title = 'Python';الناتج:
| name | title |
|---|---|
| سارة | Python |
| منى | Python |
| أحمد | Python |
enrollments e تعني «سأسمي هذا الجدول e». ولاحظ أن WHERE تعمل مع JOIN كالمعتاد.
LEFT JOIN: الربط اليساري
Section titled “LEFT JOIN: الربط اليساري”من الطلاب الذين لم يشتركوا في أي كورس؟ الربط الداخلي لن يُظهرهم أبداً، لأنه لا يعرض إلا ما له تطابق. هنا نحتاج LEFT JOIN:
SELECT s.name, e.course_idFROM students sLEFT JOIN enrollments e ON e.student_id = s.idORDER BY s.id;الناتج:
| name | course_id |
|---|---|
| سارة | 1 |
| سارة | 3 |
| عمر | 2 |
| منى | 1 |
| يوسف | 3 |
| ليلى | NULL |
| أحمد | 1 |
LEFT JOIN تُظهر كل صفوف الجدول الأيسر (الأول بعد FROM)، حتى التي لا تطابق لها، وتملأ الخانات الناقصة بـ NULL. لذلك ظهرت ليلى مع NULL.
وللحصول على غير المشتركين فقط:
SELECT s.nameFROM students sLEFT JOIN enrollments e ON e.student_id = s.idWHERE e.student_id IS NULL;الناتج: ليلى.
| INNER JOIN | LEFT JOIN | |
|---|---|---|
| ماذا تعرض؟ | الصفوف المتطابقة في الجدولين فقط | كل صفوف الجدول الأيسر، ومعها المتطابق من الأيمن |
| غير المتطابق | يختفي | يظهر مع NULL |
تخيّل حفلة: INNER JOIN قائمة بالأزواج الذين حضروا معاً فقط. أما LEFT JOIN فقائمة بكل المدعوين، ومع كل واحد مرافقه إن وُجد، أو خانة فارغة إن جاء وحده.
جرّب بنفسك
Section titled “جرّب بنفسك”- اعرض أسماء الطلاب المشتركين في كورس SQL.
- اعرض اسم كل طالب مع عنوان كل كورس اشترك فيه، ورتب النتيجة بالاسم.
- اعرض كل الكورسات، ومعها أسماء الطلاب المشتركين، بحيث يظهر الكورس حتى لو لم يشترك فيه أحد (تلميح: ابدأ
FROM coursesواستخدمLEFT JOINمرتين). - تحدٍّ: اعرض الطلاب من القاهرة فقط مع كورساتهم.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان شرط ON: يربط كل صف بكل صف، فتحصل على نتيجة ضخمة بلا معنى.
- عمود غامض: إذا وُجد عمود بنفس الاسم في جدولين، مثل
id، ستحصل على خطأambiguous column name. حدد الجدول:s.id. - استخدام INNER JOIN للبحث عن الغائبين: لن يظهروا أبداً. استخدم
LEFT JOINمعIS NULL.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
أي نوع ربط يُظهر كل الطلاب حتى غير المشتركين في أي كورس؟
LEFT JOIN يعرض كل صفوف الجدول الأيسر، ويضع NULL لمن لا تطابق له.
سؤال
ما وظيفة المفتاح الأجنبي FOREIGN KEY؟
المفتاح الأجنبي يمنع الإشارة لبيانات غير موجودة، فيحافظ على تماسك الروابط بين الجداول.
الخلاصة
Section titled “الخلاصة”- نقسم البيانات على جداول مترابطة، وكل معلومة تُخزَّن في مكان واحد فقط.
- المفتاح الأجنبي
REFERENCESيضمن أن الروابط تشير لبيانات موجودة. JOIN ... ONيربط الجداول بشرط، وINNER JOINيعرض المتطابق فقط.LEFT JOINيعرض كل صفوف الجدول الأيسر، معNULLلغير المتطابق.- الأسماء المختصرة مثل
students sتجعل الاستعلامات أقصر وأوضح.