تخطَّ إلى المحتوى

ربط الجداول في SQL: JOIN

الدرس 7 من 12 · SQL

قواعد البيانات الحقيقية ليست جدولاً واحداً ضخماً، بل عدة جداول مترابطة: جدول للطلاب، وجدول للكورسات، وجدول يربط بينهما. هذا التنظيم اسمه قاعدة البيانات العلائقية (Relational Database)، وهو سر قوة SQL. في هذا الدرس ستتعلم JOIN: الأمر الذي يجمع البيانات من عدة جداول في نتيجة واحدة.

لماذا لا نضع كل شيء في جدول واحد؟

Section titled “لماذا لا نضع كل شيء في جدول واحد؟”

تخيّل جدولاً واحداً للاشتراكات:

student course
سارة Python
سارة SQL
منى Python

يبدو بسيطاً، لكن ماذا لو غيّرت سارة اسمها في النظام؟ أو تغيّر اسم كورس Python؟ ستحتاج تعديل كل صف يظهر فيه الاسم، ولو نسيت صفاً واحداً أصبحت البيانات متناقضة.

الحل: كل معلومة تُخزَّن في مكان واحد فقط، والجداول تشير إلى بعضها بالأرقام.

إلى جانب جدول الطلاب الذي نعرفه (سارة، وعمر، ومنى، ويوسف، وليلى، وأحمد، بالأرقام من 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 فتفحصه افتراضياً.)

تخيّل المفتاح الأجنبي مثل رقم الغرفة على مفتاح الفندق: يجب أن يشير لغرفة موجودة فعلاً.

الآن نريد الأسماء الحقيقية بدلاً من الأرقام:

SELECT students.name, courses.title
FROM enrollments
JOIN students ON enrollments.student_id = students.id
JOIN 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.title
FROM enrollments e
JOIN students s ON e.student_id = s.id
JOIN courses c ON e.course_id = c.id
WHERE c.title = 'Python';

الناتج:

name title
سارة Python
منى Python
أحمد Python

enrollments e تعني «سأسمي هذا الجدول e». ولاحظ أن WHERE تعمل مع JOIN كالمعتاد.

من الطلاب الذين لم يشتركوا في أي كورس؟ الربط الداخلي لن يُظهرهم أبداً، لأنه لا يعرض إلا ما له تطابق. هنا نحتاج LEFT JOIN:

SELECT s.name, e.course_id
FROM students s
LEFT JOIN enrollments e ON e.student_id = s.id
ORDER BY s.id;

الناتج:

name course_id
سارة 1
سارة 3
عمر 2
منى 1
يوسف 3
ليلى NULL
أحمد 1

LEFT JOIN تُظهر كل صفوف الجدول الأيسر (الأول بعد FROM)، حتى التي لا تطابق لها، وتملأ الخانات الناقصة بـ NULL. لذلك ظهرت ليلى مع NULL.

وللحصول على غير المشتركين فقط:

SELECT s.name
FROM students s
LEFT JOIN enrollments e ON e.student_id = s.id
WHERE e.student_id IS NULL;

الناتج: ليلى.

INNER JOIN LEFT JOIN
ماذا تعرض؟ الصفوف المتطابقة في الجدولين فقط كل صفوف الجدول الأيسر، ومعها المتطابق من الأيمن
غير المتطابق يختفي يظهر مع NULL

تخيّل حفلة: INNER JOIN قائمة بالأزواج الذين حضروا معاً فقط. أما LEFT JOIN فقائمة بكل المدعوين، ومع كل واحد مرافقه إن وُجد، أو خانة فارغة إن جاء وحده.

  1. اعرض أسماء الطلاب المشتركين في كورس SQL.
  2. اعرض اسم كل طالب مع عنوان كل كورس اشترك فيه، ورتب النتيجة بالاسم.
  3. اعرض كل الكورسات، ومعها أسماء الطلاب المشتركين، بحيث يظهر الكورس حتى لو لم يشترك فيه أحد (تلميح: ابدأ FROM courses واستخدم LEFT JOIN مرتين).
  4. تحدٍّ: اعرض الطلاب من القاهرة فقط مع كورساتهم.
  • نسيان شرط ON: يربط كل صف بكل صف، فتحصل على نتيجة ضخمة بلا معنى.
  • عمود غامض: إذا وُجد عمود بنفس الاسم في جدولين، مثل id، ستحصل على خطأ ambiguous column name. حدد الجدول: s.id.
  • استخدام INNER JOIN للبحث عن الغائبين: لن يظهروا أبداً. استخدم LEFT JOIN مع IS NULL.

سؤال

أي نوع ربط يُظهر كل الطلاب حتى غير المشتركين في أي كورس؟

سؤال

ما وظيفة المفتاح الأجنبي FOREIGN KEY؟

  • نقسم البيانات على جداول مترابطة، وكل معلومة تُخزَّن في مكان واحد فقط.
  • المفتاح الأجنبي REFERENCES يضمن أن الروابط تشير لبيانات موجودة.
  • JOIN ... ON يربط الجداول بشرط، و INNER JOIN يعرض المتطابق فقط.
  • LEFT JOIN يعرض كل صفوف الجدول الأيسر، مع NULL لغير المتطابق.
  • الأسماء المختصرة مثل students s تجعل الاستعلامات أقصر وأوضح.