طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 121 · مبادئ قواعد البيانات
الدرس 18 · الوحدة 7: استرجاع وتحليل البيانات

الربط بين الجداول: JOIN

نعيد جمع المعلومات الموزعة على الجداول في نتيجة واحدة. ⏱ نحو 35 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تربط جدولين بـ JOIN … ON بالمفتاح الأجنبي.
  • تستخدم الأسماء المستعارة للجداول وتحل غموض الأعمدة.
  • تفرّق بين INNER JOIN و LEFT JOIN وتجد الصفوف غير المتطابقة.
  • تربط ثلاثة جداول أو أكثر مع التجميع، وتتجنب الضرب الديكارتي.

١لماذا نحتاج الربط؟

بعد التطبيع صارت المعلومة موزعة: الطلب في orders لا يحمل اسم العميل، بل رقمه فقط:

query.sql
SELECT id, customer_id, order_date FROM orders LIMIT 4;
النتيجة · 4 صفوف
idcustomer_idorder_date
112025-01-10
222025-01-12
312025-02-05
432025-02-18

JOIN يجمع الصفوف من جدولين حين يتطابق عمودان بينهما، فنرى الطلب واسم صاحبه في صف واحد:

query.sql
SELECT orders.id, customers.name, orders.order_dateFROM ordersJOIN customers ON customers.id = orders.customer_id;
النتيجة · 12 صفًّا
idnameorder_date
1سعود العتيبي2025-01-10
2نورة القحطاني2025-01-12
3سعود العتيبي2025-02-05
4خالد الحربي2025-02-18
5ريم الشهري2025-03-02
6فهد الدوسري2025-03-15
7نورة القحطاني2025-04-01
8سارة المطيري2025-04-09
9هند السبيعي2025-04-20
10سعود العتيبي2025-05-03
11ماجد الغامدي2025-05-07
12خالد الحربي2025-05-12

ON تحدد شرط التطابق: المفتاح الأجنبي = المفتاح الأساسي. وكتابة اسم الجدول قبل العمود orders.id ضرورية حين يوجد العمود نفسه في الجدولين.

٢الأسماء المستعارة للجداول

لاختصار الكتابة نعطي كل جدول حرفًا:

query.sql
SELECT o.id, c.name, c.city, o.statusFROM orders AS oJOIN customers AS c ON c.id = o.customer_idWHERE c.city = 'المدينة المنورة';
النتيجة · 6 صفوف
idnamecitystatus
1سعود العتيبيالمدينة المنورةمُسلَّم
3سعود العتيبيالمدينة المنورةمُسلَّم
4خالد الحربيالمدينة المنورةمُسلَّم
9هند السبيعيالمدينة المنورةجديد
10سعود العتيبيالمدينة المنورةجديد
12خالد الحربيالمدينة المنورةجديد
✗ خطأ
query.sql
SELECT id, nameFROM orders oJOIN customers c ON c.id = o.customer_id;
✗ خطأ (السطر 1): اسم العمود id موجود في أكثر من جدول: اكتب اسم الجدول قبله، مثل t.id
✓ صواب
query.sql
SELECT o.id, c.nameFROM orders oJOIN customers c ON c.id = o.customer_id;
النتيجة · 12 صفًّا
idname
1سعود العتيبي
2نورة القحطاني
3سعود العتيبي
4خالد الحربي
5ريم الشهري
6فهد الدوسري
7نورة القحطاني
8سارة المطيري
9هند السبيعي
10سعود العتيبي
11ماجد الغامدي
12خالد الحربي

العمود id موجود في الجدولين، فلم يعرف النظام أيهما تقصد.

٣INNER JOIN و LEFT JOIN

customersordersINNER JOINcustomersordersLEFT JOIN
INNER: العملاء الذين لهم طلبات فقط. LEFT: كل العملاء، ومن لا طلب له تظهر أعمدة طلبه NULL.

JOIN وحدها تعني INNER JOIN: الصفوف المتطابقة فقط. فالعميل الذي لم يطلب أبدًا لا يظهر. أما LEFT JOIN فيعرض كل صفوف الجدول الأيسر (الأول)، ومن لا مطابق له تظهر أعمدته من الجدول الثاني NULL:

query.sql
SELECT c.name, o.id AS order_idFROM customers cLEFT JOIN orders o ON o.customer_id = c.idWHERE c.id IN (5, 6, 7, 10);
النتيجة · 4 صفوف
nameorder_id
فهد الدوسري6
سارة المطيري8
عبدالله الزهرانيNULL
لمى العنزيNULL

وأشهر استخدام: «العملاء الذين لم يطلبوا قط»:

query.sql
SELECT c.name, c.phoneFROM customers cLEFT JOIN orders o ON o.customer_id = c.idWHERE o.id IS NULL;
النتيجة · صفّان
namephone
عبدالله الزهرانيNULL
لمى العنزي0551000010

٤ربط ثلاثة جداول وأكثر

تفاصيل الطلب رقم 3: اسم العميل، واسم كل منتج، والكمية. نتنقل عبر المفاتيح: الطلب ← العميل، والطلب ← الأصناف ← المنتج:

query.sql
SELECT c.name AS customer, p.name AS product, i.qty, i.priceFROM orders oJOIN customers   c ON c.id = o.customer_idJOIN order_items i ON i.order_id = o.idJOIN products    p ON p.id = i.product_idWHERE o.id = 3;
النتيجة · 3 صفوف
customerproductqtyprice
سعود العتيبيدلة قهوة175
سعود العتيبيفناجين (6 قطع)140
سعود العتيبيهيل 100غ228

ومع التجميع نجيب عن أسئلة الإدارة الحقيقية: «إيراد كل تصنيف من الطلبات المسلّمة»:

query.sql
SELECT p.category, SUM(i.qty * i.price) AS revenueFROM order_items iJOIN products p ON p.id = i.product_idJOIN orders   o ON o.id = i.order_idWHERE o.status = 'مُسلَّم'GROUP BY p.categoryORDER BY revenue DESC;
النتيجة · 5 صفوف
categoryrevenue
عسل270
تمور270
مشروبات207
أدوات115
بهارات56

٥احذر: الربط بلا شرط

إن نسيت ON (أو كتبت شرطًا خاطئًا) يربط النظام كل صف من الأول بـكل صف من الثاني، ويسمى «الضرب الديكارتي» Cartesian Product:

query.sql
SELECT COUNT(*) AS rows FROM customers, orders;
النتيجة · صف واحد
rows
120

10 عملاء × 12 طلبًا = 120 صفًّا لا معنى لها. ومع جدولين فيهما مليون صف: تريليون صف يوقف الخادم!

🧪 ملعب SQLقاعدة الكلية. جرّب: اسم كل طالب واسم كل مقرر سجّله ودرجته (ثلاثة جداول). ثم الطلاب الذين لم يسجلوا أي مقرر.

توقّع وتحقّق

JOIN وحدها تعني:

المتطابق فقط.

لعرض كل العملاء حتى من لا طلب له:

الأيسر كاملًا.

شرط الربط يُكتب بعد:

JOIN … ON …

ربط 10 صفوف بـ 12 بلا شرط يعطي:

ضرب ديكارتي.

الخلاصة

تحدٍّ برمجي: أفضل العملاء

صعب

برنامج الولاء يريد قائمة العملاء وإجمالي مشترياتهم من الطلبات غير الملغاة، من الأعلى إنفاقًا.

المطلوب:
  • الأعمدة: اسم العميل، ثم إجمالي مشترياته (مجموع الكمية × السعر).
  • استبعد الطلبات الملغاة.
  • رتب من الأعلى إنفاقًا. والعملاء بلا طلبات لا يظهرون.
👀 اعرض النتيجة المطلوبة
النتيجة المطلوبة
nametotal
سعود العتيبي383
نورة القحطاني295
خالد الحربي240
سارة المطيري240
فهد الدوسري203
ماجد الغامدي150
هند السبيعي105
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء
  • العملاء الصحيحون وإجمالياتهم بالترتيب الصحيح
  • تربط جدول order_items

💡 تلميح 1
ثلاثة جداول: customers ← orders ← order_items.
💡 تلميح 2
WHERE o.status <> 'ملغى' ثم GROUP BY c.id, c.name

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

solution.sql
SELECT c.name, SUM(i.qty * i.price) AS totalFROM customers cJOIN orders o      ON o.customer_id = c.idJOIN order_items i ON i.order_id = o.idWHERE o.status <> 'ملغى'GROUP BY c.id, c.nameORDER BY total DESC;
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس