الربط بين الجداول: JOIN
نعيد جمع المعلومات الموزعة على الجداول في نتيجة واحدة. ⏱ نحو 35 دقيقة.
- تربط جدولين بـ
JOIN…ONبالمفتاح الأجنبي. - تستخدم الأسماء المستعارة للجداول وتحل غموض الأعمدة.
- تفرّق بين
INNER JOINوLEFT JOINوتجد الصفوف غير المتطابقة. - تربط ثلاثة جداول أو أكثر مع التجميع، وتتجنب الضرب الديكارتي.
١لماذا نحتاج الربط؟
بعد التطبيع صارت المعلومة موزعة: الطلب في orders لا يحمل اسم العميل، بل رقمه فقط:
SELECT id, customer_id, order_date FROM orders LIMIT 4;| id | customer_id | order_date |
|---|---|---|
| 1 | 1 | 2025-01-10 |
| 2 | 2 | 2025-01-12 |
| 3 | 1 | 2025-02-05 |
| 4 | 3 | 2025-02-18 |
JOIN يجمع الصفوف من جدولين حين يتطابق عمودان بينهما، فنرى الطلب واسم صاحبه في صف واحد:
SELECT orders.id, customers.name, orders.order_dateFROM ordersJOIN customers ON customers.id = orders.customer_id;
| id | name | order_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 ضرورية حين يوجد العمود نفسه في الجدولين.
٢الأسماء المستعارة للجداول
لاختصار الكتابة نعطي كل جدول حرفًا:
SELECT o.id, c.name, c.city, o.statusFROM orders AS oJOIN customers AS c ON c.id = o.customer_idWHERE c.city = 'المدينة المنورة';
| id | name | city | status |
|---|---|---|---|
| 1 | سعود العتيبي | المدينة المنورة | مُسلَّم |
| 3 | سعود العتيبي | المدينة المنورة | مُسلَّم |
| 4 | خالد الحربي | المدينة المنورة | مُسلَّم |
| 9 | هند السبيعي | المدينة المنورة | جديد |
| 10 | سعود العتيبي | المدينة المنورة | جديد |
| 12 | خالد الحربي | المدينة المنورة | جديد |
SELECT id, nameFROM orders oJOIN customers c ON c.id = o.customer_id;
SELECT o.id, c.nameFROM orders oJOIN customers c ON c.id = o.customer_id;
| id | name |
|---|---|
| 1 | سعود العتيبي |
| 2 | نورة القحطاني |
| 3 | سعود العتيبي |
| 4 | خالد الحربي |
| 5 | ريم الشهري |
| 6 | فهد الدوسري |
| 7 | نورة القحطاني |
| 8 | سارة المطيري |
| 9 | هند السبيعي |
| 10 | سعود العتيبي |
| 11 | ماجد الغامدي |
| 12 | خالد الحربي |
العمود id موجود في الجدولين، فلم يعرف النظام أيهما تقصد.
٣INNER JOIN و LEFT JOIN
JOIN وحدها تعني INNER JOIN: الصفوف المتطابقة فقط. فالعميل الذي لم يطلب أبدًا لا يظهر. أما LEFT JOIN فيعرض كل صفوف الجدول الأيسر (الأول)، ومن لا مطابق له تظهر أعمدته من الجدول الثاني NULL:
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);
| name | order_id |
|---|---|
| فهد الدوسري | 6 |
| سارة المطيري | 8 |
| عبدالله الزهراني | NULL |
| لمى العنزي | NULL |
وأشهر استخدام: «العملاء الذين لم يطلبوا قط»:
SELECT c.name, c.phoneFROM customers cLEFT JOIN orders o ON o.customer_id = c.idWHERE o.id IS NULL;
| name | phone |
|---|---|
| عبدالله الزهراني | NULL |
| لمى العنزي | 0551000010 |
٤ربط ثلاثة جداول وأكثر
تفاصيل الطلب رقم 3: اسم العميل، واسم كل منتج، والكمية. نتنقل عبر المفاتيح: الطلب ← العميل، والطلب ← الأصناف ← المنتج:
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;
| customer | product | qty | price |
|---|---|---|---|
| سعود العتيبي | دلة قهوة | 1 | 75 |
| سعود العتيبي | فناجين (6 قطع) | 1 | 40 |
| سعود العتيبي | هيل 100غ | 2 | 28 |
ومع التجميع نجيب عن أسئلة الإدارة الحقيقية: «إيراد كل تصنيف من الطلبات المسلّمة»:
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;
| category | revenue |
|---|---|
| عسل | 270 |
| تمور | 270 |
| مشروبات | 207 |
| أدوات | 115 |
| بهارات | 56 |
٥احذر: الربط بلا شرط
إن نسيت ON (أو كتبت شرطًا خاطئًا) يربط النظام كل صف من الأول بـكل صف من الثاني، ويسمى «الضرب الديكارتي» Cartesian Product:
SELECT COUNT(*) AS rows FROM customers, orders;| rows |
|---|
| 120 |
10 عملاء × 12 طلبًا = 120 صفًّا لا معنى لها. ومع جدولين فيهما مليون صف: تريليون صف يوقف الخادم!
توقّع وتحقّق
JOIN وحدها تعني:
لعرض كل العملاء حتى من لا طلب له:
شرط الربط يُكتب بعد:
JOIN … ON …ربط 10 صفوف بـ 12 بلا شرط يعطي:
الخلاصة
JOINجدولONمفتاح_أجنبي = مفتاح_أساسي.INNER: المتطابق فقط.LEFT: كل صفوف الأيسر، وغير المتطابقNULL.LEFT JOIN…WHERE x.id IS NULLتجد ما لا مقابل له.- نربط أي عدد من الجداول بسلسلة
JOIN، ثم نجمع بـGROUP BY. - بلا
ON: ضرب ديكارتي ضخم.
تحدٍّ برمجي: أفضل العملاء
صعببرنامج الولاء يريد قائمة العملاء وإجمالي مشترياتهم من الطلبات غير الملغاة، من الأعلى إنفاقًا.
- الأعمدة: اسم العميل، ثم إجمالي مشترياته (مجموع الكمية × السعر).
- استبعد الطلبات الملغاة.
- رتب من الأعلى إنفاقًا. والعملاء بلا طلبات لا يظهرون.
👀 اعرض النتيجة المطلوبة
| name | total |
|---|---|
| سعود العتيبي | 383 |
| نورة القحطاني | 295 |
| خالد الحربي | 240 |
| سارة المطيري | 240 |
| فهد الدوسري | 203 |
| ماجد الغامدي | 150 |
| هند السبيعي | 105 |
- الكود يعمل بلا أخطاء
- العملاء الصحيحون وإجمالياتهم بالترتيب الصحيح
- تربط جدول order_items
💡 تلميح 1
💡 تلميح 2
WHERE o.status <> 'ملغى' ثم GROUP BY c.id, c.nameحاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
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;