SQL: الربط والعلاقات
فهم علاقات الجداول
في قواعد البيانات العلائقية، غالباً ما يتم توزيع البيانات عبر جداول متعددة. JOINs تسمح لك بدمج الصفوف من جدولين أو أكثر بناءً على الأعمدة المرتبطة.
أنواع العلاقات
1. واحد لكثير (الأكثر شيوعاً)
سجل واحد في الجدول A يرتبط بعدة سجلات في الجدول B.
مثال: مستخدم واحد يمكن أن يكون لديه العديد من الطلبات
- جدول
users: مستخدم واحد - جدول
orders: العديد من الطلبات مع مفتاح خارجيuser_id
2. كثير لكثير
عدة سجلات في الجدول A ترتبط بعدة سجلات في الجدول B.
مثال: الطلاب والدورات (طالب واحد يأخذ العديد من الدورات، دورة واحدة لها العديد من الطلاب)
- يتطلب جدول وصل/محوري:
student_courses - يحتوي على
student_idوcourse_id
3. واحد لواحد (نادر)
سجل واحد في الجدول A يرتبط بسجل واحد بالضبط في الجدول B.
مثال: المستخدم وملف تعريف المستخدم (معلومات المستخدم الموسعة)
مخطط قاعدة البيانات النموذجية
سنستخدم مخطط التجارة الإلكترونية هذا للأمثلة:
users (id, username, email)
|
+-- orders (id, user_id, total_amount, created_at)
|
+-- order_items (id, order_id, product_id, quantity, price)
|
+-- products (id, name, category_id, price)
|
+-- categories (id, name)
INNER JOIN
INNER JOIN يرجع فقط الصفوف التي لها قيم مطابقة في كلا الجدولين.
الصيغة
SELECT columns FROM table1 INNER JOIN table2 ON table1.column = table2.column;
مثال: المستخدمون وطلباتهم
SELECT
users.username,
users.email,
orders.id AS order_id,
orders.total_amount,
orders.created_at
FROM users
INNER JOIN orders ON users.id = orders.user_id;
النتيجة: يتم عرض المستخدمين الذين قدموا طلبات فقط.
استخدام أسماء مستعارة للجداول
SELECT
u.username,
o.id AS order_id,
o.total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.total_amount > 100
ORDER BY o.created_at DESC;
أفضل ممارسة: استخدم أسماء مستعارة للجداول (u, o) لجعل الاستعلامات أقصر وأكثر قابلية للقراءة، خاصة مع الربط المتعدد.
LEFT JOIN (LEFT OUTER JOIN)
LEFT JOIN يرجع جميع الصفوف من الجدول الأيسر والصفوف المطابقة من الجدول الأيمن. إذا لم يكن هناك تطابق، يتم إرجاع قيم NULL لأعمدة الجدول الأيمن.
مثال: جميع المستخدمين، بما في ذلك أولئك الذين ليس لديهم طلبات
SELECT
u.username,
u.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username, u.email
ORDER BY total_spent DESC;
النتيجة: يتم عرض جميع المستخدمين، حتى أولئك الذين لديهم 0 طلبات (order_count = 0).
دالة COALESCE
COALESCE(value1, value2, ...) تُرجع أول قيمة غير NULL.
مفيدة مع LEFT JOIN لاستبدال NULL بقيمة افتراضية:
COALESCE(SUM(o.total_amount), 0) -- يُرجع 0 إذا كان SUM هو NULL
البحث عن سجلات بدون تطابق
-- العثور على المستخدمين الذين لم يقدموا طلباً أبداً
SELECT
u.username,
u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
RIGHT JOIN (RIGHT OUTER JOIN)
RIGHT JOIN يرجع جميع الصفوف من الجدول الأيمن والصفوف المطابقة من الجدول الأيسر.
-- جميع الطلبات مع معلومات المستخدم (حتى الطلبات اليتيمة)
SELECT
o.id AS order_id,
o.total_amount,
u.username
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
ملاحظة: نادراً ما يُستخدم RIGHT JOIN. يمكنك دائماً إعادة كتابته كـ LEFT JOIN عن طريق تبديل ترتيب الجدول:
-- هذه متكافئة: SELECT * FROM users u RIGHT JOIN orders o ON u.id = o.user_id; SELECT * FROM orders o LEFT JOIN users u ON u.id = o.user_id;
الربط المتعدد
يمكنك ربط أكثر من جدولين في استعلام واحد.
مثال: الطلبات مع تفاصيل المستخدم والمنتج
SELECT
u.username,
o.id AS order_id,
o.created_at,
p.name AS product_name,
oi.quantity,
oi.price AS item_price,
(oi.quantity * oi.price) AS item_total
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE u.id = 10
ORDER BY o.created_at DESC;
مثال: المنتجات بأسماء الفئات
SELECT
p.name AS product_name,
p.price,
c.name AS category_name
FROM products p
INNER JOIN categories c ON p.category_id = c.id
WHERE p.price > 50
ORDER BY c.name, p.name;
الربط الذاتي
جدول مرتبط بنفسه، مفيد للبيانات الهرمية.
مثال: علاقة الموظف والمدير
-- الجدول: employees (id, name, manager_id)
SELECT
e.name AS employee_name,
m.name AS manager_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
مثال: المنتجات ذات الصلة
-- البحث عن المنتجات في نفس الفئة
SELECT
p1.name AS product,
p2.name AS related_product,
p1.category_id
FROM products p1
INNER JOIN products p2
ON p1.category_id = p2.category_id
AND p1.id != p2.id -- لا تطابق المنتج مع نفسه
WHERE p1.id = 5
LIMIT 5;
CROSS JOIN (الضرب الديكارتي)
يرجع جميع المجموعات الممكنة من الصفوف من كلا الجدولين. نادراً ما يُستخدم.
-- كل مجموعة من الحجم واللون
SELECT
sizes.name AS size,
colors.name AS color
FROM sizes
CROSS JOIN colors;
علاقات كثير لكثير
يتطلب جدول وصل لربط جدولين.
مثال: المنتجات والوسوم
-- الجداول:
-- products (id, name)
-- tags (id, name)
-- product_tags (product_id, tag_id)
-- الحصول على جميع الوسوم لمنتج
SELECT
p.name AS product_name,
t.name AS tag_name
FROM products p
INNER JOIN product_tags pt ON p.id = pt.product_id
INNER JOIN tags t ON pt.tag_id = t.id
WHERE p.id = 15;
مثال: الطلاب والدورات
-- الحصول على جميع الدورات لطالب
SELECT
s.name AS student_name,
c.name AS course_name,
sc.grade
FROM students s
INNER JOIN student_courses sc ON s.id = sc.student_id
INNER JOIN courses c ON sc.course_id = c.id
WHERE s.id = 25;
تقنيات الربط المتقدمة
الربط على شروط متعددة
SELECT
u.username,
o.id AS order_id,
o.status
FROM users u
INNER JOIN orders o
ON u.id = o.user_id
AND o.status = 'completed'
AND o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY);
استخدام الاستعلامات الفرعية في الربط
-- الربط مع جدول مشتق
SELECT
u.username,
order_stats.order_count,
order_stats.total_spent
FROM users u
INNER JOIN (
SELECT
user_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM orders
GROUP BY user_id
) AS order_stats ON u.id = order_stats.user_id
WHERE order_stats.order_count > 5;
أمثلة عملية
مثال 1: تفاصيل الطلب الكاملة
SELECT
o.id AS order_id,
o.created_at AS order_date,
u.username,
u.email,
p.name AS product_name,
oi.quantity,
oi.price AS unit_price,
(oi.quantity * oi.price) AS line_total,
o.total_amount AS order_total
FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.id = 100;
مثال 2: تقرير مبيعات المنتجات
SELECT
p.name AS product_name,
c.name AS category,
COUNT(oi.id) AS times_ordered,
SUM(oi.quantity) AS total_quantity_sold,
SUM(oi.quantity * oi.price) AS total_revenue
FROM products p
INNER JOIN categories c ON p.category_id = c.id
LEFT JOIN order_items oi ON p.id = oi.product_id
GROUP BY p.id, p.name, c.name
ORDER BY total_revenue DESC
LIMIT 20;
مثال 3: تقرير نشاط العملاء
SELECT
u.username,
u.email,
u.created_at AS member_since,
COUNT(DISTINCT o.id) AS total_orders,
COUNT(DISTINCT oi.product_id) AS unique_products_bought,
COALESCE(SUM(o.total_amount), 0) AS lifetime_value,
MAX(o.created_at) AS last_order_date,
DATEDIFF(NOW(), MAX(o.created_at)) AS days_since_last_order
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY u.id, u.username, u.email, u.created_at
HAVING total_orders > 0
ORDER BY lifetime_value DESC;
مثال 4: أداء الفئات
SELECT
c.name AS category,
COUNT(DISTINCT p.id) AS product_count,
COUNT(DISTINCT o.id) AS order_count,
SUM(oi.quantity) AS items_sold,
SUM(oi.quantity * oi.price) AS total_revenue,
AVG(oi.price) AS avg_item_price
FROM categories c
INNER JOIN products p ON c.id = p.category_id
LEFT JOIN order_items oi ON p.id = oi.product_id
LEFT JOIN orders o ON oi.order_id = o.id
GROUP BY c.id, c.name
ORDER BY total_revenue DESC;
تمرين: ممارسة الربط
باستخدام مخطط التجارة الإلكترونية، اكتب استعلامات لـ:
- سرد جميع المنتجات بأسماء فئاتها، وإظهار المنتجات بدون فئات على أنها "غير مصنف"
- العثور على المستخدمين الذين طلبوا منتجات من فئة "الإلكترونيات"
- إظهار أفضل 5 منتجات الأكثر مبيعاً (حسب إجمالي الكمية المباعة) مع فئتها
- سرد جميع الطلبات المقدمة في آخر 30 يوماً مع اسم مستخدم العميل وإجمالي الطلب
- ابحث عن المنتجات التي لم يتم طلبها أبداً
- احسب متوسط قيمة الطلب لكل مستخدم قدم أكثر من 3 طلبات
- إظهار المنتجات وعناصرها "التي يتم شراؤها معاً بشكل متكرر" (المنتجات المطلوبة في نفس الطلبات)
- إنشاء تقرير يوضح الإيرادات الشهرية لكل فئة للأشهر الستة الماضية
نصائح أداء الربط
تحسين الربط
- فهرس المفاتيح الخارجية: قم دائماً بإضافة فهارس على الأعمدة المستخدمة في شروط JOIN
- استخدم INNER JOIN عندما يكون ممكناً: أسرع من OUTER JOINs
- قم بالتصفية مبكراً: استخدم WHERE قبل JOIN عندما يكون ممكناً
- تجنب SELECT *: حدد الأعمدة المطلوبة فقط
- استخدم EXPLAIN: تحليل خطة تنفيذ الاستعلام
- ترتيب الربط مهم: ابدأ بأصغر جدول
-- تحقق من أداء الربط EXPLAIN SELECT u.username, o.total_amount FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.created_at >= '2024-01-01';
أنماط الربط الشائعة
النمط 1: عد السجلات المرتبطة
-- عد الطلبات لكل مستخدم
SELECT
u.username,
COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
النمط 2: آخر/أول سجل
-- الحصول على أحدث طلب لكل مستخدم
SELECT
u.username,
o.id AS last_order_id,
o.created_at AS last_order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.created_at = (
SELECT MAX(o2.created_at)
FROM orders o2
WHERE o2.user_id = u.id
);
النمط 3: التجميع عبر الربط
-- إجمالي الإنفاق لكل فئة من قبل كل مستخدم
SELECT
u.username,
c.name AS category,
SUM(oi.quantity * oi.price) AS category_spending
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
INNER JOIN categories c ON p.category_id = c.id
GROUP BY u.id, u.username, c.id, c.name
ORDER BY u.username, category_spending DESC;
استكشاف أخطاء الربط
مشاكل الربط الشائعة
- صفوف مكررة: التطابقات المتعددة تنشئ صفوف إضافية. استخدم DISTINCT أو اضبط شروط JOIN
- نوع JOIN خاطئ: INNER JOIN يستبعد الصفوف غير المطابقة، استخدم LEFT JOIN إذا لزم الأمر
- فهارس المفاتيح الخارجية المفقودة: أداء بطيء على الجداول الكبيرة
- أسماء أعمدة غامضة: قم دائماً ببادئة الأعمدة باسم الجدول المستعار
- ضرب ديكارتي: نسيان شرط JOIN ينشئ جميع التركيبات
ملخص أنواع JOIN
| نوع JOIN | يرجع | حالة الاستخدام |
|---|---|---|
INNER JOIN |
الصفوف المطابقة فقط | عندما تحتاج إلى بيانات مرتبطة |
LEFT JOIN |
جميع اليسار + اليمين المطابق | تضمين الكل من الجدول الأيسر |
RIGHT JOIN |
جميع اليمين + اليسار المطابق | نادراً ما يُستخدم (استخدم LEFT بدلاً) |
CROSS JOIN |
جميع التركيبات | توليد بيانات الاختبار |
SELF JOIN |
جدول مرتبط بنفسه | بيانات هرمية |
ما التالي؟
تهانينا! لقد أكملت الوحدة 7: أساسيات MySQL. أنت الآن تفهم:
- مفاهيم قاعدة البيانات وأساسيات MySQL
- إنشاء وإدارة قواعد البيانات والجداول
- إدراج واختيار وتحديث وحذف البيانات
- الاستعلامات المتقدمة مع GROUP BY و HAVING والاستعلامات الفرعية
- ربط جداول متعددة وإدارة العلاقات
في الوحدة التالية، ستتعلم كيفية ربط PHP بقواعد بيانات MySQL، وتنفيذ الاستعلامات من كود PHP، ومعالجة النتائج، ومنع حقن SQL، وبناء تطبيقات ويب كاملة مدفوعة بقواعد البيانات!
نصيحة للممارسة: أفضل طريقة لإتقان SQL هي من خلال الممارسة. أنشئ قواعد بيانات عينة، اكتب استعلامات، جرب أنواع JOIN المختلفة، وحلل سيناريوهات البيانات الواقعية. حاول بناء قاعدة بيانات تجارة إلكترونية كاملة مع المنتجات والمستخدمين والطلبات والفئات، ثم اكتب استعلامات معقدة لاستخراج رؤى ذات معنى!