الربط الكامل FULL JOIN
مقدمة حول FULL OUTER JOIN في SQL
الـ FULL OUTER JOIN (أو FULL JOIN) هو نوع من أنواع الربط في SQL يُرجع جميع الصفوف من كلا الجدولين، سواء كان هناك تطابق أم لا. يمكن اعتباره دمجاً بين LEFT JOIN و RIGHT JOIN.
عندما يوجد تطابق، تُعرض البيانات من كلا الجدولين. وعندما لا يوجد تطابق، تُملأ الأعمدة المفقودة بقيم NULL.
تنبيه مهم لمستخدمي MySQL: MySQL لا يدعم FULL OUTER JOIN بشكل مباشر! لكن يمكن محاكاته باستخدام UNION بين LEFT JOIN و RIGHT JOIN. سنشرح الطريقتين في هذا الدرس.
كيف يعمل FULL OUTER JOIN؟
- يُرجع جميع الصفوف من الجدول الأيسر (حتى بدون تطابق)
- يُرجع جميع الصفوف من الجدول الأيمن (حتى بدون تطابق)
- يُرجع الصفوف المتطابقة من كلا الجدولين
- يملأ القيم المفقودة بـ NULL
متى نستخدم FULL OUTER JOIN؟
- عند الحاجة لرؤية جميع البيانات من كلا الجدولين
- لإيجاد الاختلافات والتطابقات بين جدولين
- لتسوية البيانات (Data Reconciliation)
- لإنشاء تقارير شاملة تتضمن جميع السجلات
- لاكتشاف البيانات المفقودة في أي من الجدولين
الصيغة الأساسية لـ FULL OUTER JOIN
الصيغة القياسية (PostgreSQL, SQL Server, Oracle):
SELECT columns
FROM table1
FULL OUTER JOIN table2
ON table1.column = table2.column;
أو بشكل مختصر:
SELECT columns
FROM table1
FULL JOIN table2
ON table1.column = table2.column;
الصيغة البديلة لـ MySQL (باستخدام UNION):
-- محاكاة FULL OUTER JOIN في MySQL
SELECT columns
FROM table1
LEFT JOIN table2 ON table1.column = table2.column
UNION
SELECT columns
FROM table1
RIGHT JOIN table2 ON table1.column = table2.column;
شرح الطريقة البديلة:
- LEFT JOIN: يُرجع جميع صفوف table1 + المتطابقات من table2
- RIGHT JOIN: يُرجع جميع صفوف table2 + المتطابقات من table1
- UNION: يدمج النتيجتين ويزيل التكرار
- النتيجة النهائية = جميع الصفوف من كلا الجدولين
مقارنة بصرية: أنواع JOIN المختلفة
إنشاء جداول للمثال:
-- جدول الموظفين
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100),
department VARCHAR(50)
);
-- جدول المشاريع
CREATE TABLE projects (
project_id INT PRIMARY KEY,
emp_id INT,
project_name VARCHAR(100),
budget DECIMAL(10, 2)
);
-- إدراج بيانات الموظفين
INSERT INTO employees (emp_id, emp_name, department) VALUES
(1, 'أحمد محمد', 'تطوير'),
(2, 'فاطمة علي', 'تصميم'),
(3, 'خالد حسن', 'تسويق'),
(4, 'سارة أحمد', 'موارد بشرية');
-- إدراج بيانات المشاريع
INSERT INTO projects (project_id, emp_id, project_name, budget) VALUES
(101, 1, 'تطبيق الجوال', 50000.00),
(102, 2, 'موقع الشركة', 30000.00),
(103, 99, 'مشروع يتيم', 20000.00), -- موظف غير موجود
(104, 99, 'مشروع آخر', 15000.00); -- موظف غير موجود
مقارنة النتائج:
-- INNER JOIN: فقط المتطابقات (2 صفوف)
SELECT e.emp_name, p.project_name
FROM employees e
INNER JOIN projects p ON e.emp_id = p.emp_id;
-- LEFT JOIN: جميع الموظفين (4 صفوف)
SELECT e.emp_name, p.project_name
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id;
-- RIGHT JOIN: جميع المشاريع (4 صفوف)
SELECT e.emp_name, p.project_name
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id;
-- FULL OUTER JOIN: الكل (6 صفوف)
-- في MySQL:
SELECT e.emp_name, p.project_name
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id
UNION
SELECT e.emp_name, p.project_name
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id;
| emp_name | project_name |
|---|---|
| أحمد محمد | تطبيق الجوال |
| فاطمة علي | موقع الشركة |
| خالد حسن | NULL |
| سارة أحمد | NULL |
| NULL | مشروع يتيم |
| NULL | مشروع آخر |
تحليل النتيجة:
- الصفوف 1-2: موظفون لديهم مشاريع (تطابق)
- الصفوف 3-4: موظفون بدون مشاريع (من LEFT JOIN)
- الصفوف 5-6: مشاريع بدون موظفين (من RIGHT JOIN)
تطبيق FULL OUTER JOIN في MySQL
نظراً لأن MySQL لا يدعم FULL OUTER JOIN مباشرة، إليك الطريقة الصحيحة لمحاكاته:
الطريقة الأساسية (UNION):
-- استعلام كامل مع جميع الأعمدة
SELECT
e.emp_id,
e.emp_name,
e.department,
p.project_id,
p.project_name,
p.budget
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id
UNION
SELECT
e.emp_id,
e.emp_name,
e.department,
p.project_id,
p.project_name,
p.budget
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id;
ملاحظة مهمة: يجب أن يكون عدد الأعمدة ونوعها متطابقاً في كلا الاستعلامين قبل وبعد UNION.
طريقة بديلة (UNION ALL + DISTINCT):
-- إذا كنت تريد التحكم الكامل في إزالة التكرار
SELECT DISTINCT
e.emp_id,
e.emp_name,
p.project_name
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id
UNION ALL
SELECT DISTINCT
e.emp_id,
e.emp_name,
p.project_name
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id
WHERE e.emp_id IS NULL; -- فقط الصفوف غير الموجودة في LEFT JOIN
الفرق بين UNION و UNION ALL:
- UNION: يزيل الصفوف المكررة تلقائياً (أبطأ قليلاً)
- UNION ALL: يحتفظ بجميع الصفوف بما في ذلك المكررة (أسرع)
- في الطريقة الثانية، نستخدم WHERE e.emp_id IS NULL لتجنب التكرار
أمثلة عملية متقدمة
مثال 1: تسوية البيانات بين نظامين
-- لنفترض أن لدينا جدولين من نظامين مختلفين
CREATE TABLE system_a_sales (
sale_id INT PRIMARY KEY,
product_name VARCHAR(100),
amount DECIMAL(10, 2),
sale_date DATE
);
CREATE TABLE system_b_sales (
sale_id INT PRIMARY KEY,
product_name VARCHAR(100),
amount DECIMAL(10, 2),
sale_date DATE
);
-- إدراج بيانات مختلفة
INSERT INTO system_a_sales VALUES
(1, 'لابتوب', 3500.00, '2026-01-15'),
(2, 'هاتف', 2200.00, '2026-01-16'),
(3, 'تابلت', 1500.00, '2026-01-17');
INSERT INTO system_b_sales VALUES
(2, 'هاتف', 2200.00, '2026-01-16'),
(3, 'تابلت', 1500.00, '2026-01-17'),
(4, 'سماعات', 450.00, '2026-01-18');
-- مقارنة البيانات بين النظامين
SELECT
COALESCE(a.sale_id, b.sale_id) AS sale_id,
a.product_name AS نظام_A,
b.product_name AS نظام_B,
a.amount AS مبلغ_A,
b.amount AS مبلغ_B,
CASE
WHEN a.sale_id IS NULL THEN 'موجود في B فقط'
WHEN b.sale_id IS NULL THEN 'موجود في A فقط'
WHEN a.amount = b.amount THEN 'متطابق'
ELSE 'اختلاف في المبلغ'
END AS الحالة
FROM system_a_sales a
LEFT JOIN system_b_sales b ON a.sale_id = b.sale_id
UNION
SELECT
COALESCE(a.sale_id, b.sale_id) AS sale_id,
a.product_name AS نظام_A,
b.product_name AS نظام_B,
a.amount AS مبلغ_A,
b.amount AS مبلغ_B,
CASE
WHEN a.sale_id IS NULL THEN 'موجود في B فقط'
WHEN b.sale_id IS NULL THEN 'موجود في A فقط'
WHEN a.amount = b.amount THEN 'متطابق'
ELSE 'اختلاف في المبلغ'
END AS الحالة
FROM system_a_sales a
RIGHT JOIN system_b_sales b ON a.sale_id = b.sale_id
ORDER BY sale_id;
| sale_id | نظام_A | نظام_B | مبلغ_A | مبلغ_B | الحالة |
|---|---|---|---|---|---|
| 1 | لابتوب | NULL | 3500.00 | NULL | موجود في A فقط |
| 2 | هاتف | هاتف | 2200.00 | 2200.00 | متطابق |
| 3 | تابلت | تابلت | 1500.00 | 1500.00 | متطابق |
| 4 | NULL | سماعات | NULL | 450.00 | موجود في B فقط |
مثال 2: تقرير شامل للموظفين والمشاريع
-- تقرير يوضح جميع الموظفين والمشاريع مع حالة كل منهم
SELECT
COALESCE(e.emp_id, p.emp_id) AS معرف_الموظف,
COALESCE(e.emp_name, 'موظف محذوف') AS الموظف,
COALESCE(e.department, 'غير محدد') AS القسم,
COALESCE(p.project_name, 'لا يوجد مشروع') AS المشروع,
COALESCE(p.budget, 0) AS الميزانية,
CASE
WHEN e.emp_id IS NULL THEN 'تحذير: مشروع بدون موظف'
WHEN p.project_id IS NULL THEN 'موظف متاح للعمل'
ELSE 'موظف نشط'
END AS الحالة
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id
UNION
SELECT
COALESCE(e.emp_id, p.emp_id) AS معرف_الموظف,
COALESCE(e.emp_name, 'موظف محذوف') AS الموظف,
COALESCE(e.department, 'غير محدد') AS القسم,
COALESCE(p.project_name, 'لا يوجد مشروع') AS المشروع,
COALESCE(p.budget, 0) AS الميزانية,
CASE
WHEN e.emp_id IS NULL THEN 'تحذير: مشروع بدون موظف'
WHEN p.project_id IS NULL THEN 'موظف متاح للعمل'
ELSE 'موظف نشط'
END AS الحالة
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id
ORDER BY
CASE
WHEN الحالة LIKE 'تحذير%' THEN 1
WHEN الحالة = 'موظف متاح للعمل' THEN 2
ELSE 3
END,
معرف_الموظف;
مثال 3: إيجاد جميع الاختلافات
-- البحث عن السجلات غير المتطابقة فقط
SELECT
COALESCE(e.emp_id, p.emp_id) AS معرف,
e.emp_name AS الموظف,
p.project_name AS المشروع,
CASE
WHEN e.emp_id IS NULL THEN 'مشروع يتيم - يحتاج تعيين موظف'
WHEN p.project_id IS NULL THEN 'موظف بدون مشروع - يحتاج تكليف'
END AS المشكلة
FROM employees e
LEFT JOIN projects p ON e.emp_id = p.emp_id
WHERE p.project_id IS NULL
UNION
SELECT
COALESCE(e.emp_id, p.emp_id) AS معرف,
e.emp_name AS الموظف,
p.project_name AS المشروع,
CASE
WHEN e.emp_id IS NULL THEN 'مشروع يتيم - يحتاج تعيين موظف'
WHEN p.project_id IS NULL THEN 'موظف بدون مشروع - يحتاج تكليف'
END AS المشكلة
FROM employees e
RIGHT JOIN projects p ON e.emp_id = p.emp_id
WHERE e.emp_id IS NULL;
حالات استخدام متقدمة
مثال 4: مقارنة المخزون بين مستودعين
CREATE TABLE warehouse_a (
product_id INT,
product_name VARCHAR(100),
quantity INT
);
CREATE TABLE warehouse_b (
product_id INT,
product_name VARCHAR(100),
quantity INT
);
INSERT INTO warehouse_a VALUES
(1, 'لابتوب', 50),
(2, 'هاتف', 100),
(3, 'تابلت', 30);
INSERT INTO warehouse_b VALUES
(2, 'هاتف', 80),
(3, 'تابلت', 30),
(4, 'سماعات', 200);
-- تقرير مقارنة شامل
SELECT
COALESCE(a.product_id, b.product_id) AS product_id,
COALESCE(a.product_name, b.product_name) AS المنتج,
COALESCE(a.quantity, 0) AS مستودع_A,
COALESCE(b.quantity, 0) AS مستودع_B,
COALESCE(a.quantity, 0) + COALESCE(b.quantity, 0) AS الإجمالي,
CASE
WHEN a.product_id IS NULL THEN 'موجود في B فقط'
WHEN b.product_id IS NULL THEN 'موجود في A فقط'
WHEN a.quantity > b.quantity THEN 'A أكثر'
WHEN b.quantity > a.quantity THEN 'B أكثر'
ELSE 'متساوي'
END AS المقارنة
FROM warehouse_a a
LEFT JOIN warehouse_b b ON a.product_id = b.product_id
UNION
SELECT
COALESCE(a.product_id, b.product_id) AS product_id,
COALESCE(a.product_name, b.product_name) AS المنتج,
COALESCE(a.quantity, 0) AS مستودع_A,
COALESCE(b.quantity, 0) AS مستودع_B,
COALESCE(a.quantity, 0) + COALESCE(b.quantity, 0) AS الإجمالي,
CASE
WHEN a.product_id IS NULL THEN 'موجود في B فقط'
WHEN b.product_id IS NULL THEN 'موجود في A فقط'
WHEN a.quantity > b.quantity THEN 'A أكثر'
WHEN b.quantity > a.quantity THEN 'B أكثر'
ELSE 'متساوي'
END AS المقارنة
FROM warehouse_a a
RIGHT JOIN warehouse_b b ON a.product_id = b.product_id
ORDER BY product_id;
مثال 5: تحليل الحضور والغياب
-- جدول الموظفين المسجلين
CREATE TABLE registered_employees (
emp_id INT,
emp_name VARCHAR(100)
);
-- جدول الحضور اليومي
CREATE TABLE daily_attendance (
emp_id INT,
attendance_date DATE,
status VARCHAR(20)
);
-- تقرير شامل
SELECT
COALESCE(r.emp_id, a.emp_id) AS emp_id,
COALESCE(r.emp_name, 'موظف غير مسجل') AS الموظف,
a.attendance_date AS التاريخ,
COALESCE(a.status, 'لم يسجل حضور') AS الحالة,
CASE
WHEN r.emp_id IS NULL THEN 'خطأ: حضور لموظف غير مسجل'
WHEN a.emp_id IS NULL THEN 'موظف لم يسجل حضور'
WHEN a.status = 'present' THEN 'حاضر'
WHEN a.status = 'absent' THEN 'غائب'
ELSE a.status
END AS التقييم
FROM registered_employees r
LEFT JOIN daily_attendance a ON r.emp_id = a.emp_id
UNION
SELECT
COALESCE(r.emp_id, a.emp_id) AS emp_id,
COALESCE(r.emp_name, 'موظف غير مسجل') AS الموظف,
a.attendance_date AS التاريخ,
COALESCE(a.status, 'لم يسجل حضور') AS الحالة,
CASE
WHEN r.emp_id IS NULL THEN 'خطأ: حضور لموظف غير مسجل'
WHEN a.emp_id IS NULL THEN 'موظف لم يسجل حضور'
WHEN a.status = 'present' THEN 'حاضر'
WHEN a.status = 'absent' THEN 'غائب'
ELSE a.status
END AS التقييم
FROM registered_employees r
RIGHT JOIN daily_attendance a ON r.emp_id = a.emp_id;
أخطاء شائعة وكيفية تجنبها
1. نسيان أن MySQL لا يدعم FULL OUTER JOIN
خطأ شائع: محاولة استخدام FULL OUTER JOIN مباشرة في MySQL
-- خطأ في MySQL!
SELECT * FROM table1
FULL OUTER JOIN table2 ON table1.id = table2.id;
-- Error: You have an error in your SQL syntax
-- الصحيح: استخدم UNION
SELECT * FROM table1
LEFT JOIN table2 ON table1.id = table2.id
UNION
SELECT * FROM table1
RIGHT JOIN table2 ON table1.id = table2.id;
2. عدم تطابق الأعمدة في UNION
خطأ شائع: أعمدة مختلفة في الاستعلامين
-- خطأ: عدد الأعمدة مختلف
SELECT id, name FROM table1
LEFT JOIN table2 ON table1.id = table2.id
UNION
SELECT id, name, email FROM table1 -- عمود إضافي!
RIGHT JOIN table2 ON table1.id = table2.id;
-- الصحيح: نفس الأعمدة
SELECT id, name, email FROM table1
LEFT JOIN table2 ON table1.id = table2.id
UNION
SELECT id, name, email FROM table1
RIGHT JOIN table2 ON table1.id = table2.id;
3. عدم استخدام COALESCE للقيم NULL
خطأ شائع: عدم معالجة NULL في الأعمدة المشتركة
-- مشكلة: قد تحصل على NULL في الأعمدة المهمة
SELECT a.id, a.name, b.value
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
UNION
SELECT a.id, a.name, b.value
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id;
-- الأفضل: استخدم COALESCE
SELECT
COALESCE(a.id, b.id) AS id,
COALESCE(a.name, 'غير معروف') AS name,
COALESCE(b.value, 0) AS value
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
UNION
SELECT
COALESCE(a.id, b.id) AS id,
COALESCE(a.name, 'غير معروف') AS name,
COALESCE(b.value, 0) AS value
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id;
4. استخدام FULL OUTER JOIN عندما لا تحتاجه
خطأ شائع: استخدام FULL OUTER JOIN بدلاً من LEFT أو INNER JOIN
-- غير ضروري: إذا كنت تريد فقط العملاء
-- لا تستخدم FULL OUTER JOIN
SELECT * FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
UNION
SELECT * FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id;
-- الأفضل: استخدم LEFT JOIN فقط
SELECT * FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;
أفضل الممارسات
1. استخدم FULL OUTER JOIN فقط عند الحاجة الحقيقية
FULL OUTER JOIN مفيد في حالات محددة:
- تسوية البيانات بين نظامين
- إيجاد جميع الاختلافات والتطابقات
- تقارير شاملة تتطلب جميع السجلات من كلا الجدولين
2. استخدم COALESCE للأعمدة المشتركة
-- دائماً استخدم COALESCE للمفاتيح المشتركة
SELECT
COALESCE(a.id, b.id) AS id, -- ليس a.id فقط
a.name,
b.value
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
UNION
SELECT
COALESCE(a.id, b.id) AS id,
a.name,
b.value
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id;
3. أضف شروط تصفية بعد UNION إذا لزم الأمر
-- يمكنك إضافة WHERE بعد UNION
SELECT * FROM (
SELECT a.id, a.name, b.value
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
UNION
SELECT a.id, a.name, b.value
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id
) AS full_join_result
WHERE value > 1000 -- تصفية بعد الدمج
ORDER BY id;
4. استخدم الأسماء المستعارة الواضحة
-- واضح وسهل الفهم
SELECT
COALESCE(old.id, new.id) AS product_id,
old.price AS السعر_القديم,
new.price AS السعر_الجديد,
new.price - old.price AS الفرق
FROM old_prices old
LEFT JOIN new_prices new ON old.id = new.id
UNION
SELECT
COALESCE(old.id, new.id) AS product_id,
old.price AS السعر_القديم,
new.price AS السعر_الجديد,
new.price - old.price AS الفرق
FROM old_prices old
RIGHT JOIN new_prices new ON old.id = new.id;
5. فكر في الأداء
FULL OUTER JOIN (خاصة مع UNION) يمكن أن يكون بطيئاً على الجداول الكبيرة:
- تأكد من وجود فهارس (Indexes) على أعمدة الربط
- استخدم WHERE لتقليل عدد الصفوف قبل UNION إذا أمكن
- فكر في استخدام جداول مؤقتة للاستعلامات المعقدة
ملخص الدرس
في هذا الدرس، تعلمنا عن FULL OUTER JOIN في SQL:
- FULL OUTER JOIN يُرجع جميع الصفوف من كلا الجدولين
- هو دمج بين LEFT JOIN و RIGHT JOIN
- MySQL لا يدعم FULL OUTER JOIN مباشرة
- يمكن محاكاته في MySQL باستخدام UNION
- مفيد جداً لتسوية البيانات بين نظامين
- يساعد في إيجاد جميع الاختلافات والتطابقات
- استخدم COALESCE للأعمدة المشتركة
- تأكد من تطابق الأعمدة في كلا جزئي UNION
- استخدمه فقط عند الحاجة الحقيقية لجميع البيانات
- انتبه للأداء على الجداول الكبيرة
نصيحة مهمة: FULL OUTER JOIN أقل استخداماً من INNER و LEFT JOIN. استخدمه فقط عندما تحتاج فعلاً لرؤية جميع السجلات من كلا الجدولين، بغض النظر عن التطابق.
في الدرس القادم: سنتعلم عن الربط الذاتي (SELF JOIN) في SQL، وهو تقنية قوية لربط الجدول بنفسه، مع أمثلة عملية مثل العلاقات الهرمية (الموظفين والمدراء) والمقارنات داخل نفس الجدول.
الخطوة التالية: الربط الذاتي SELF JOIN
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الربط الذاتي SELF JOIN وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي