الربط الأيسر LEFT JOIN
مقدمة حول LEFT JOIN في SQL
الـ LEFT JOIN (أو LEFT OUTER JOIN) هو نوع من أنواع الربط في SQL يُرجع جميع الصفوف من الجدول الأيسر (الأول)، حتى لو لم يكن هناك تطابق في الجدول الأيمن (الثاني). عندما لا يوجد تطابق، تُملأ الأعمدة من الجدول الأيمن بقيم NULL.
هذا يختلف عن INNER JOIN الذي يُرجع فقط الصفوف المتطابقة في كلا الجدولين. LEFT JOIN مفيد جداً عندما تريد رؤية جميع السجلات من جدول معين، بغض النظر عما إذا كانت لها علاقات في جدول آخر أم لا.
لماذا LEFT JOIN مهم؟
- عرض جميع السجلات من جدول رئيسي حتى بدون تطابق
- إيجاد السجلات التي ليس لها علاقات (مثل: عملاء بدون طلبات)
- إنشاء تقارير شاملة تتضمن جميع البيانات
- تجنب فقدان البيانات المهمة في التقارير
- تحليل البيانات الناقصة أو غير المكتملة
الفرق الأساسي بين INNER JOIN و LEFT JOIN:
- INNER JOIN: يُرجع فقط الصفوف المتطابقة في كلا الجدولين
- LEFT JOIN: يُرجع جميع الصفوف من الجدول الأيسر + الصفوف المتطابقة من الجدول الأيمن
الصيغة الأساسية لـ LEFT JOIN
الصيغة العامة:
SELECT columns
FROM table1
LEFT JOIN table2
ON table1.column = table2.column;
أو بشكل كامل:
SELECT columns
FROM table1
LEFT OUTER JOIN table2
ON table1.column = table2.column;
كيف يعمل LEFT JOIN؟
تخيل أن لديك:
- الجدول الأيسر (table1): يحتوي على 5 صفوف
- الجدول الأيمن (table2): يحتوي على 3 صفوف متطابقة فقط
النتيجة ستكون: 5 صفوف (جميع صفوف الجدول الأيسر)، حيث الصفوف الـ 2 التي لا تطابق ستحتوي على NULL في أعمدة الجدول الأيمن.
مقارنة بصرية: INNER JOIN vs LEFT JOIN
إنشاء جداول للمثال:
-- جدول العملاء
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
customer_name VARCHAR(100),
email VARCHAR(100),
city VARCHAR(50)
);
-- جدول الطلبات
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT,
order_date DATE,
total_amount DECIMAL(10, 2)
);
-- إدراج بيانات العملاء
INSERT INTO customers (customer_name, email, city) VALUES
('أحمد محمد', 'ahmed@example.com', 'الرياض'),
('فاطمة علي', 'fatima@example.com', 'جدة'),
('خالد حسن', 'khaled@example.com', 'الدمام'),
('سارة أحمد', 'sara@example.com', 'مكة'),
('محمد عبدالله', 'mohammed@example.com', 'المدينة');
-- إدراج بيانات الطلبات (فقط لـ 3 عملاء)
INSERT INTO orders (customer_id, order_date, total_amount) VALUES
(1, '2026-01-15', 500.00),
(1, '2026-01-20', 750.00),
(2, '2026-01-18', 1200.00),
(3, '2026-01-22', 300.00);
مقارنة النتائج:
-- استخدام INNER JOIN
SELECT
c.customer_name,
c.city,
o.order_id,
o.total_amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- النتيجة: 4 صفوف فقط (العملاء الذين لديهم طلبات)
-- استخدام LEFT JOIN
SELECT
c.customer_name,
c.city,
o.order_id,
o.total_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- النتيجة: 5 صفوف (جميع العملاء، حتى بدون طلبات)
| customer_name | city | order_id | total_amount |
|---|---|---|---|
| أحمد محمد | الرياض | 1 | 500.00 |
| أحمد محمد | الرياض | 2 | 750.00 |
| فاطمة علي | جدة | 3 | 1200.00 |
| خالد حسن | الدمام | 4 | 300.00 |
| سارة أحمد | مكة | NULL | NULL |
| محمد عبدالله | المدينة | NULL | NULL |
لاحظ: العملاء "سارة أحمد" و "محمد عبدالله" ظهروا في النتيجة مع قيم NULL في أعمدة الطلبات، لأنهم لا يملكون طلبات. هذا هو الفرق الرئيسي عن INNER JOIN.
إيجاد السجلات بدون تطابق
واحدة من أهم استخدامات LEFT JOIN هي إيجاد السجلات التي ليس لها علاقات في جدول آخر.
مثال 1 - العملاء بدون طلبات:
-- البحث عن العملاء الذين لم يقوموا بأي طلبات
SELECT
c.customer_id,
c.customer_name,
c.email,
c.city
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
| customer_id | customer_name | city | |
|---|---|---|---|
| 4 | سارة أحمد | sara@example.com | مكة |
| 5 | محمد عبدالله | mohammed@example.com | المدينة |
الفكرة الأساسية: نستخدم LEFT JOIN للحصول على جميع العملاء، ثم نستخدم WHERE o.order_id IS NULL لتصفية فقط العملاء الذين لا يملكون طلبات (حيث قيمة order_id هي NULL).
مثال 2 - عد العملاء بدون طلبات:
-- حساب عدد العملاء غير النشطين
SELECT
COUNT(*) AS عدد_العملاء_غير_النشطين
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
معالجة القيم الفارغة (NULL)
عند استخدام LEFT JOIN، غالباً ما تحتاج إلى معالجة القيم الفارغة لجعل النتائج أكثر وضوحاً.
استخدام IFNULL و COALESCE:
-- استبدال NULL بقيم افتراضية
SELECT
c.customer_name AS العميل,
c.city AS المدينة,
IFNULL(o.order_id, 'لا يوجد') AS رقم_الطلب,
COALESCE(o.total_amount, 0) AS المبلغ,
CASE
WHEN o.order_id IS NULL THEN 'عميل غير نشط'
ELSE 'عميل نشط'
END AS الحالة
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
ORDER BY c.customer_name;
حساب إحصائيات مع معالجة NULL:
-- عرض جميع العملاء مع عدد طلباتهم
SELECT
c.customer_id,
c.customer_name AS العميل,
c.city AS المدينة,
COUNT(o.order_id) AS عدد_الطلبات,
COALESCE(SUM(o.total_amount), 0) AS إجمالي_المشتريات,
CASE
WHEN COUNT(o.order_id) = 0 THEN 'لم يشتري بعد'
WHEN COUNT(o.order_id) = 1 THEN 'عميل جديد'
WHEN COUNT(o.order_id) <= 3 THEN 'عميل عادي'
ELSE 'عميل مميز'
END AS التصنيف
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name, c.city
ORDER BY إجمالي_المشتريات DESC;
| customer_id | العميل | المدينة | عدد_الطلبات | إجمالي_المشتريات | التصنيف |
|---|---|---|---|---|---|
| 1 | أحمد محمد | الرياض | 2 | 1250.00 | عميل عادي |
| 2 | فاطمة علي | جدة | 1 | 1200.00 | عميل جديد |
| 3 | خالد حسن | الدمام | 1 | 300.00 | عميل جديد |
| 4 | سارة أحمد | مكة | 0 | 0.00 | لم يشتري بعد |
| 5 | محمد عبدالله | المدينة | 0 | 0.00 | لم يشتري بعد |
استخدام LEFT JOIN المتعددة
يمكنك ربط عدة جداول باستخدام LEFT JOIN المتعددة للحصول على تقارير شاملة.
إنشاء جداول إضافية:
-- جدول المنتجات
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100),
price DECIMAL(10, 2),
category VARCHAR(50)
);
-- جدول تفاصيل الطلبات
CREATE TABLE order_items (
item_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
product_id INT,
quantity INT
);
-- إدراج بيانات
INSERT INTO products (product_name, price, category) VALUES
('لابتوب Dell', 3500.00, 'إلكترونيات'),
('هاتف iPhone', 4000.00, 'إلكترونيات'),
('سماعات Sony', 450.00, 'إكسسوارات'),
('ماوس Logitech', 80.00, 'إكسسوارات');
INSERT INTO order_items (order_id, product_id, quantity) VALUES
(1, 4, 2),
(2, 3, 1),
(3, 1, 1),
(4, 4, 1);
ربط متعدد مع LEFT JOIN:
-- تقرير شامل لجميع العملاء مع تفاصيل طلباتهم
SELECT
c.customer_name AS العميل,
c.city AS المدينة,
o.order_id AS رقم_الطلب,
o.order_date AS التاريخ,
p.product_name AS المنتج,
p.category AS الفئة,
COALESCE(oi.quantity, 0) AS الكمية,
COALESCE(p.price, 0) AS السعر,
COALESCE(oi.quantity * p.price, 0) AS الإجمالي
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.product_id
ORDER BY c.customer_name, o.order_date;
ملاحظة: عند استخدام LEFT JOIN المتعددة، تأكد من الترتيب الصحيح. كل LEFT JOIN يعتمد على الجدول السابق في السلسلة.
أمثلة عملية متقدمة
مثال 1: تقرير العملاء الشامل
-- تقرير يوضح نشاط جميع العملاء
SELECT
c.customer_id,
c.customer_name AS العميل,
c.email AS البريد,
c.city AS المدينة,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
COUNT(DISTINCT oi.item_id) AS عدد_المنتجات,
COALESCE(SUM(o.total_amount), 0) AS إجمالي_المشتريات,
COALESCE(MAX(o.order_date), 'لم يطلب بعد') AS آخر_طلب,
DATEDIFF(CURDATE(), MAX(o.order_date)) AS أيام_منذ_آخر_طلب,
CASE
WHEN COUNT(o.order_id) = 0 THEN 'غير نشط'
WHEN DATEDIFF(CURDATE(), MAX(o.order_date)) > 30 THEN 'خامل'
WHEN DATEDIFF(CURDATE(), MAX(o.order_date)) > 7 THEN 'نشاط متوسط'
ELSE 'نشط جداً'
END AS مستوى_النشاط
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY c.customer_id, c.customer_name, c.email, c.city
ORDER BY إجمالي_المشتريات DESC;
مثال 2: تحليل المنتجات غير المباعة
-- المنتجات التي لم تُباع أبداً
SELECT
p.product_id,
p.product_name AS المنتج,
p.category AS الفئة,
p.price AS السعر,
'لم يُباع بعد' AS الحالة
FROM products p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
WHERE oi.item_id IS NULL
ORDER BY p.price DESC;
مثال 3: تقرير المبيعات حسب المدينة
-- مبيعات كل مدينة (بما في ذلك المدن بدون مبيعات)
SELECT
c.city AS المدينة,
COUNT(DISTINCT c.customer_id) AS عدد_العملاء,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
COUNT(DISTINCT CASE WHEN o.order_id IS NOT NULL THEN c.customer_id END) AS العملاء_النشطون,
COUNT(DISTINCT CASE WHEN o.order_id IS NULL THEN c.customer_id END) AS العملاء_غير_النشطين,
COALESCE(SUM(o.total_amount), 0) AS إجمالي_المبيعات,
COALESCE(AVG(o.total_amount), 0) AS متوسط_قيمة_الطلب,
ROUND(
COUNT(DISTINCT CASE WHEN o.order_id IS NOT NULL THEN c.customer_id END) * 100.0 /
COUNT(DISTINCT c.customer_id),
2
) AS نسبة_التفعيل
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.city
ORDER BY إجمالي_المبيعات DESC;
مثال 4: العملاء المحتملون للحملات التسويقية
-- العملاء الذين لم يشتروا منذ فترة أو لم يشتروا أبداً
SELECT
c.customer_id,
c.customer_name AS العميل,
c.email AS البريد,
c.city AS المدينة,
COUNT(o.order_id) AS عدد_الطلبات_السابقة,
COALESCE(MAX(o.order_date), 'لم يطلب أبداً') AS آخر_طلب,
CASE
WHEN MAX(o.order_date) IS NULL THEN 'عميل جديد - لم يشتري بعد'
WHEN DATEDIFF(CURDATE(), MAX(o.order_date)) > 60 THEN 'عميل قديم - غير نشط'
WHEN DATEDIFF(CURDATE(), MAX(o.order_date)) > 30 THEN 'عميل خامل'
ELSE NULL
END AS نوع_الحملة
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name, c.email, c.city
HAVING
MAX(o.order_date) IS NULL
OR DATEDIFF(CURDATE(), MAX(o.order_date)) > 30
ORDER BY آخر_طلب;
الفرق بين WHERE و ON في LEFT JOIN
موضع شرط التصفية مهم جداً في LEFT JOIN. استخدام WHERE أو ON يمكن أن يعطي نتائج مختلفة تماماً.
استخدام الشرط في ON:
-- الشرط في ON: يُطبق قبل الربط
SELECT
c.customer_name,
o.order_id,
o.total_amount
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
AND o.total_amount > 500;
-- النتيجة: جميع العملاء، لكن فقط الطلبات > 500
استخدام الشرط في WHERE:
-- الشرط في WHERE: يُطبق بعد الربط
SELECT
c.customer_name,
o.order_id,
o.total_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.total_amount > 500;
-- النتيجة: فقط العملاء الذين لديهم طلبات > 500
-- (يتحول إلى INNER JOIN فعلياً!)
قاعدة مهمة:
- شروط الربط: ضعها في ON
- شروط التصفية للجدول الأيسر: يمكن وضعها في WHERE
- شروط التصفية للجدول الأيمن: ضعها في ON للحفاظ على سلوك LEFT JOIN
مثال توضيحي:
-- صحيح: الحصول على عملاء الرياض مع طلباتهم (إن وجدت)
SELECT
c.customer_name,
c.city,
o.order_id,
o.total_amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.city = 'الرياض';
-- صحيح: جميع العملاء مع الطلبات الكبيرة فقط
SELECT
c.customer_name,
o.order_id,
o.total_amount
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
AND o.total_amount > 1000;
أخطاء شائعة وكيفية تجنبها
1. تحويل LEFT JOIN إلى INNER JOIN بالخطأ
خطأ شائع: استخدام WHERE على أعمدة الجدول الأيمن
-- خطأ: هذا يتحول إلى INNER JOIN!
SELECT c.customer_name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.total_amount > 500;
-- العملاء بدون طلبات لن يظهروا!
-- الصحيح: استخدم ON أو IS NOT NULL
SELECT c.customer_name, o.order_id
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
AND o.total_amount > 500;
-- أو
SELECT c.customer_name, o.order_id
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.total_amount > 500 OR o.order_id IS NULL;
2. عدم معالجة NULL في الحسابات
خطأ شائع: نسيان أن NULL + أي رقم = NULL
-- خطأ: قد تحصل على NULL في النتائج
SELECT
c.customer_name,
SUM(o.total_amount) AS الإجمالي
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
-- الصحيح: استخدم COALESCE أو IFNULL
SELECT
c.customer_name,
COALESCE(SUM(o.total_amount), 0) AS الإجمالي
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
3. الخلط بين COUNT(*) و COUNT(column)
ملاحظة مهمة: الفرق في التعامل مع NULL
-- COUNT(*) يحسب جميع الصفوف (حتى مع NULL)
SELECT
c.customer_name,
COUNT(*) AS العدد
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
-- العميل بدون طلبات سيحصل على 1!
-- COUNT(o.order_id) يحسب فقط القيم غير NULL
SELECT
c.customer_name,
COUNT(o.order_id) AS عدد_الطلبات
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
-- العميل بدون طلبات سيحصل على 0 ✓
4. ترتيب خاطئ في LEFT JOIN المتعددة
خطأ شائع: عدم مراعاة تسلسل الجداول
-- قد يعطي نتائج غير متوقعة
SELECT c.customer_name, p.product_name
FROM customers c
LEFT JOIN products p ON ??? -- لا توجد علاقة مباشرة!
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- الصحيح: اتبع التسلسل المنطقي
SELECT c.customer_name, p.product_name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.product_id;
أفضل الممارسات
1. اختر النوع المناسب من JOIN
- استخدم INNER JOIN عندما تريد فقط السجلات المتطابقة
- استخدم LEFT JOIN عندما تريد جميع السجلات من الجدول الأساسي
- لا تستخدم LEFT JOIN إذا كنت ستصفي بـ WHERE على الجدول الأيمن
2. عالج القيم الفارغة بشكل صحيح
-- استخدم COALESCE أو IFNULL للقيم الافتراضية
SELECT
c.customer_name,
COALESCE(SUM(o.total_amount), 0) AS الإجمالي,
COALESCE(COUNT(o.order_id), 0) AS عدد_الطلبات,
COALESCE(AVG(o.total_amount), 0) AS المتوسط
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
3. استخدم IS NULL للبحث عن السجلات المفقودة
-- نمط شائع ومفيد جداً
SELECT c.*
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
4. انتبه لموضع الشروط
-- شروط الجدول الأيسر: في WHERE
-- شروط الجدول الأيمن: في ON
SELECT c.customer_name, o.order_id
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
AND o.order_date >= '2026-01-01' -- شرط الجدول الأيمن
WHERE c.city = 'الرياض'; -- شرط الجدول الأيسر
5. استخدم الأسماء المستعارة الواضحة
-- واضح وسهل القراءة
SELECT
c.customer_name AS العميل,
COALESCE(o.order_id, 'لا يوجد') AS الطلب,
COALESCE(o.total_amount, 0) AS المبلغ
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
ملخص الدرس
في هذا الدرس، تعلمنا كيفية استخدام LEFT JOIN في SQL:
- LEFT JOIN يُرجع جميع الصفوف من الجدول الأيسر + الصفوف المتطابقة من الجدول الأيمن
- الصفوف غير المتطابقة تحتوي على NULL في أعمدة الجدول الأيمن
- مفيد جداً لإيجاد السجلات بدون علاقات (مثل: عملاء بدون طلبات)
- استخدام WHERE column IS NULL لتصفية السجلات المفقودة
- معالجة NULL باستخدام COALESCE أو IFNULL
- الفرق بين وضع الشروط في ON و WHERE
- استخدام COUNT(column) بدلاً من COUNT(*) مع LEFT JOIN
- يمكن استخدام LEFT JOIN المتعددة للتقارير الشاملة
- الحذر من تحويل LEFT JOIN إلى INNER JOIN بالخطأ
- LEFT JOIN و LEFT OUTER JOIN متطابقان تماماً
في الدرس القادم: سنتعلم عن الربط الأيمن (RIGHT JOIN) في SQL، وكيف يختلف عن LEFT JOIN، ومتى نستخدمه، مع أمثلة عملية توضح العلاقة بين أنواع الربط المختلفة.
الخطوة التالية: الربط الأيمن RIGHT JOIN
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الربط الأيمن RIGHT JOIN وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي