الربط الداخلي INNER JOIN
مقدمة حول INNER JOIN في SQL
الـ INNER JOIN هو أكثر أنواع الربط شيوعاً واستخداماً في SQL. يسمح لك بدمج البيانات من جدولين أو أكثر بناءً على علاقة مشتركة بينهما، ويُرجع فقط الصفوف التي تحتوي على تطابق في كلا الجدولين.
تخيل أن لديك جدول للعملاء وجدول للطلبات. باستخدام INNER JOIN، يمكنك الحصول على قائمة بالعملاء مع طلباتهم، ولكن فقط العملاء الذين لديهم طلبات فعلية.
لماذا INNER JOIN مهم؟
- دمج البيانات من جداول متعددة في استعلام واحد
- الحصول على معلومات كاملة من مصادر مختلفة
- تجنب تكرار البيانات في قاعدة البيانات
- إنشاء تقارير شاملة تجمع بيانات مترابطة
- الأساس لفهم أنواع الربط الأخرى (LEFT, RIGHT, FULL)
الفرق الأساسي: INNER JOIN يُرجع فقط الصفوف المتطابقة في كلا الجدولين. إذا لم يكن هناك تطابق، لن تظهر الصفوف في النتيجة.
الصيغة الأساسية لـ INNER JOIN
الصيغة العامة:
SELECT columns
FROM table1
INNER JOIN table2
ON table1.column = table2.column;
حيث:
- table1 و table2: الجدولان المراد ربطهما
- ON: تحدد شرط الربط (العلاقة بين الجدولين)
- table1.column = table2.column: العمود المشترك بين الجدولين
مثال بسيط - ربط جدولين
إنشاء الجداول:
-- جدول العملاء
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),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- إدراج بيانات العملاء
INSERT INTO customers (customer_name, email, city) VALUES
('أحمد محمد', 'ahmed@example.com', 'الرياض'),
('فاطمة علي', 'fatima@example.com', 'جدة'),
('خالد حسن', 'khaled@example.com', 'الدمام'),
('سارة أحمد', 'sara@example.com', 'مكة'),
('محمد عبدالله', 'mohammed@example.com', 'المدينة');
-- إدراج بيانات الطلبات
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),
(3, '2026-01-25', 450.00),
(3, '2026-01-28', 600.00);
استعلام INNER JOIN الأول:
-- عرض العملاء مع طلباتهم
SELECT
customers.customer_name,
customers.city,
orders.order_id,
orders.order_date,
orders.total_amount
FROM customers
INNER JOIN orders
ON customers.customer_id = orders.customer_id;
| customer_name | city | order_id | order_date | total_amount |
|---|---|---|---|---|
| أحمد محمد | الرياض | 1 | 2026-01-15 | 500.00 |
| أحمد محمد | الرياض | 2 | 2026-01-20 | 750.00 |
| فاطمة علي | جدة | 3 | 2026-01-18 | 1200.00 |
| خالد حسن | الدمام | 4 | 2026-01-22 | 300.00 |
| خالد حسن | الدمام | 5 | 2026-01-25 | 450.00 |
| خالد حسن | الدمام | 6 | 2026-01-28 | 600.00 |
لاحظ: العملاء "سارة أحمد" و "محمد عبدالله" لم يظهروا في النتيجة لأنهم لا يملكون طلبات. هذا هو سلوك INNER JOIN - فقط الصفوف المتطابقة تظهر.
استخدام الأسماء المستعارة (Aliases)
لتبسيط الاستعلامات وجعلها أكثر قابلية للقراءة، يمكننا استخدام أسماء مستعارة قصيرة للجداول.
مثال مع الأسماء المستعارة:
-- استخدام c للعملاء و o للطلبات
SELECT
c.customer_name AS اسم_العميل,
c.email AS البريد_الإلكتروني,
c.city AS المدينة,
o.order_id AS رقم_الطلب,
o.order_date AS تاريخ_الطلب,
o.total_amount AS المبلغ
FROM customers AS c
INNER JOIN orders AS o
ON c.customer_id = o.customer_id
ORDER BY c.customer_name, o.order_date;
INNER JOIN مع شروط WHERE
يمكنك دمج INNER JOIN مع WHERE لتصفية النتائج بشكل أكبر.
مثال 1 - تصفية حسب المبلغ:
-- عرض الطلبات التي تزيد عن 500 ريال
SELECT
c.customer_name,
c.city,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.total_amount > 500
ORDER BY o.total_amount DESC;
مثال 2 - تصفية حسب المدينة:
-- عرض طلبات العملاء من الرياض فقط
SELECT
c.customer_name,
c.city,
o.order_id,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.city = 'الرياض';
مثال 3 - شروط متعددة:
-- طلبات أكثر من 400 ريال من مدينتي الرياض وجدة
SELECT
c.customer_name,
c.city,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.total_amount > 400
AND c.city IN ('الرياض', 'جدة')
ORDER BY o.total_amount DESC;
INNER JOIN مع الدوال التجميعية
يمكن دمج INNER JOIN مع الدوال التجميعية مثل COUNT, SUM, AVG للحصول على إحصائيات مفيدة.
مثال 1 - عدد الطلبات لكل عميل:
SELECT
c.customer_name AS اسم_العميل,
c.city AS المدينة,
COUNT(o.order_id) AS عدد_الطلبات,
SUM(o.total_amount) AS إجمالي_المشتريات,
AVG(o.total_amount) AS متوسط_قيمة_الطلب,
MIN(o.order_date) AS أول_طلب,
MAX(o.order_date) AS آخر_طلب
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name, c.city
ORDER BY إجمالي_المشتريات DESC;
| اسم_العميل | المدينة | عدد_الطلبات | إجمالي_المشتريات | متوسط_قيمة_الطلب | أول_طلب | آخر_طلب |
|---|---|---|---|---|---|---|
| خالد حسن | الدمام | 3 | 1350.00 | 450.00 | 2026-01-22 | 2026-01-28 |
| أحمد محمد | الرياض | 2 | 1250.00 | 625.00 | 2026-01-15 | 2026-01-20 |
| فاطمة علي | جدة | 1 | 1200.00 | 1200.00 | 2026-01-18 | 2026-01-18 |
مثال 2 - تصفية النتائج المجمعة بـ HAVING:
-- عرض العملاء الذين لديهم أكثر من طلب واحد
SELECT
c.customer_name,
COUNT(o.order_id) AS عدد_الطلبات,
SUM(o.total_amount) AS إجمالي_المشتريات
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING COUNT(o.order_id) > 1
ORDER BY عدد_الطلبات DESC;
ربط أكثر من جدولين
يمكنك ربط ثلاثة جداول أو أكثر في استعلام واحد باستخدام INNER 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,
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- إدراج بيانات المنتجات
INSERT INTO products (product_name, price, category) VALUES
('لابتوب Dell', 3500.00, 'إلكترونيات'),
('هاتف iPhone', 4000.00, 'إلكترونيات'),
('سماعات Sony', 450.00, 'إكسسوارات'),
('ماوس Logitech', 80.00, 'إكسسوارات'),
('لوحة مفاتيح', 150.00, 'إكسسوارات');
-- إدراج تفاصيل الطلبات
INSERT INTO order_items (order_id, product_id, quantity) VALUES
(1, 4, 2), -- الطلب 1: 2 ماوس
(1, 5, 1), -- الطلب 1: 1 لوحة مفاتيح
(2, 3, 1), -- الطلب 2: 1 سماعات
(3, 1, 1), -- الطلب 3: 1 لابتوب
(4, 5, 2), -- الطلب 4: 2 لوحة مفاتيح
(5, 3, 1), -- الطلب 5: 1 سماعات
(6, 4, 3); -- الطلب 6: 3 ماوس
ربط ثلاثة جداول:
-- عرض تفاصيل كاملة للطلبات مع المنتجات
SELECT
c.customer_name AS العميل,
o.order_id AS رقم_الطلب,
o.order_date AS التاريخ,
p.product_name AS المنتج,
p.category AS الفئة,
oi.quantity AS الكمية,
p.price AS سعر_الوحدة,
(oi.quantity * p.price) AS الإجمالي
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
ORDER BY o.order_date, o.order_id;
مثال متقدم - تقرير شامل:
-- تقرير مبيعات المنتجات حسب الفئة
SELECT
p.category AS الفئة,
p.product_name AS المنتج,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
SUM(oi.quantity) AS الكمية_المباعة,
p.price AS سعر_الوحدة,
SUM(oi.quantity * p.price) AS إجمالي_المبيعات
FROM products p
INNER JOIN order_items oi ON p.product_id = oi.product_id
INNER JOIN orders o ON oi.order_id = o.order_id
GROUP BY p.category, p.product_id, p.product_name, p.price
ORDER BY p.category, إجمالي_المبيعات DESC;
استخدام USING بدلاً من ON
عندما يكون للعمود المشترك نفس الاسم في كلا الجدولين، يمكنك استخدام USING بدلاً من ON لتبسيط الاستعلام.
الفرق بين ON و USING:
-- استخدام ON (الطريقة التقليدية)
SELECT c.customer_name, o.order_date, o.total_amount
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id;
-- استخدام USING (عندما يكون اسم العمود متطابق)
SELECT c.customer_name, o.order_date, o.total_amount
FROM customers c
INNER JOIN orders o
USING (customer_id);
أمثلة عملية متقدمة
مثال 1: نظام إدارة المبيعات
-- تقرير شامل لأداء المبيعات
SELECT
c.customer_name AS العميل,
c.city AS المدينة,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
COUNT(oi.item_id) AS عدد_المنتجات,
SUM(oi.quantity) AS إجمالي_الكمية,
SUM(oi.quantity * p.price) AS إجمالي_المبيعات,
AVG(oi.quantity * p.price) AS متوسط_قيمة_المنتج,
MIN(o.order_date) AS أول_طلب,
MAX(o.order_date) AS آخر_طلب,
DATEDIFF(MAX(o.order_date), MIN(o.order_date)) AS فترة_التعامل_بالأيام
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
GROUP BY c.customer_id, c.customer_name, c.city
HAVING إجمالي_المبيعات > 0
ORDER BY إجمالي_المبيعات DESC;
مثال 2: تحليل المنتجات الأكثر مبيعاً
-- المنتجات الأكثر مبيعاً مع تفاصيل العملاء
SELECT
p.product_name AS المنتج,
p.category AS الفئة,
p.price AS السعر,
COUNT(DISTINCT c.customer_id) AS عدد_العملاء,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
SUM(oi.quantity) AS الكمية_الكلية,
SUM(oi.quantity * p.price) AS إجمالي_الإيرادات,
ROUND(AVG(oi.quantity), 2) AS متوسط_الكمية_للطلب
FROM products p
INNER JOIN order_items oi ON p.product_id = oi.product_id
INNER JOIN orders o ON oi.order_id = o.order_id
INNER JOIN customers c ON o.customer_id = c.customer_id
GROUP BY p.product_id, p.product_name, p.category, p.price
ORDER BY إجمالي_الإيرادات DESC
LIMIT 5;
مثال 3: تحليل المبيعات حسب المدينة والفئة
-- مبيعات كل فئة منتجات في كل مدينة
SELECT
c.city AS المدينة,
p.category AS فئة_المنتج,
COUNT(DISTINCT o.order_id) AS عدد_الطلبات,
SUM(oi.quantity) AS الكمية_المباعة,
SUM(oi.quantity * p.price) AS إجمالي_المبيعات,
ROUND(AVG(oi.quantity * p.price), 2) AS متوسط_قيمة_المنتج
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
GROUP BY c.city, p.category
ORDER BY c.city, إجمالي_المبيعات DESC;
مثال 4: العملاء النشطون في فترة معينة
-- العملاء الذين قاموا بطلبات في يناير 2026
SELECT
c.customer_name AS العميل,
c.email AS البريد,
c.city AS المدينة,
COUNT(o.order_id) AS عدد_الطلبات,
SUM(o.total_amount) AS إجمالي_المشتريات,
DATE_FORMAT(MIN(o.order_date), '%d/%m/%Y') AS أول_طلب,
DATE_FORMAT(MAX(o.order_date), '%d/%m/%Y') AS آخر_طلب
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date BETWEEN '2026-01-01' AND '2026-01-31'
GROUP BY c.customer_id, c.customer_name, c.email, c.city
ORDER BY إجمالي_المشتريات DESC;
الربط الذاتي (Self Join)
يمكنك ربط جدول مع نفسه باستخدام INNER JOIN. هذا مفيد عند وجود علاقة هرمية في نفس الجدول.
مثال - جدول الموظفين:
-- جدول الموظفين مع المدير
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
employee_name VARCHAR(100),
position VARCHAR(50),
manager_id INT,
salary DECIMAL(10, 2)
);
INSERT INTO employees (employee_name, position, manager_id, salary) VALUES
('أحمد محمد', 'مدير عام', NULL, 15000.00),
('فاطمة علي', 'مدير مبيعات', 1, 10000.00),
('خالد حسن', 'مدير تسويق', 1, 10000.00),
('سارة أحمد', 'موظف مبيعات', 2, 6000.00),
('محمد عبدالله', 'موظف مبيعات', 2, 6000.00),
('نورة خالد', 'موظف تسويق', 3, 5500.00);
-- عرض الموظفين مع أسماء مدرائهم
SELECT
e.employee_name AS الموظف,
e.position AS الوظيفة,
e.salary AS الراتب,
m.employee_name AS المدير_المباشر,
m.position AS وظيفة_المدير
FROM employees e
INNER JOIN employees m ON e.manager_id = m.employee_id
ORDER BY e.employee_id;
| الموظف | الوظيفة | الراتب | المدير_المباشر | وظيفة_المدير |
|---|---|---|---|---|
| فاطمة علي | مدير مبيعات | 10000.00 | أحمد محمد | مدير عام |
| خالد حسن | مدير تسويق | 10000.00 | أحمد محمد | مدير عام |
| سارة أحمد | موظف مبيعات | 6000.00 | فاطمة علي | مدير مبيعات |
| محمد عبدالله | موظف مبيعات | 6000.00 | فاطمة علي | مدير مبيعات |
| نورة خالد | موظف تسويق | 5500.00 | خالد حسن | مدير تسويق |
ملاحظة: المدير العام (أحمد محمد) لم يظهر في النتيجة لأن manager_id الخاص به هو NULL، ولا يوجد تطابق في الربط. لعرض جميع الموظفين بما فيهم من ليس لديهم مدير، استخدم LEFT JOIN بدلاً من INNER JOIN.
أخطاء شائعة وكيفية تجنبها
1. نسيان شرط الربط (ON)
خطأ شائع: عدم تحديد شرط الربط يؤدي إلى Cartesian Product
-- خطأ: سيُرجع كل تركيبة ممكنة (Cartesian Product)
SELECT c.customer_name, o.order_id
FROM customers c
INNER JOIN orders o;
-- إذا كان لديك 5 عملاء و 6 طلبات، ستحصل على 30 صف!
-- الصحيح: تحديد شرط الربط
SELECT c.customer_name, o.order_id
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
2. الخلط بين أسماء الأعمدة
خطأ شائع: عدم تحديد الجدول عند وجود أعمدة بنفس الاسم
-- خطأ: غموض في اسم العمود
SELECT customer_id, customer_name, order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- خطأ: Column 'customer_id' is ambiguous
-- الصحيح: تحديد الجدول
SELECT c.customer_id, c.customer_name, o.order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
3. ترتيب خاطئ للـ JOIN
خطأ شائع: محاولة ربط جداول قبل ربط الجداول المرتبطة بها
-- خطأ: محاولة ربط order_items قبل ربط orders
SELECT c.customer_name, p.product_name
FROM customers c
INNER JOIN order_items oi ON ??? -- لا يوجد علاقة مباشرة!
INNER JOIN products p ON oi.product_id = p.product_id;
-- الصحيح: الترتيب المنطقي
SELECT c.customer_name, p.product_name
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id;
4. استخدام WHERE بدلاً من ON للربط
ملاحظة: يمكن استخدام WHERE للربط، لكن ON أوضح وأفضل
-- يعمل لكن غير موصى به
SELECT c.customer_name, o.order_date
FROM customers c, orders o
WHERE c.customer_id = o.customer_id;
-- الأفضل: استخدام INNER JOIN مع ON
SELECT c.customer_name, o.order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
أفضل الممارسات
1. استخدم أسماء مستعارة واضحة
-- جيد: أسماء مستعارة واضحة
SELECT
cust.customer_name,
ord.order_date
FROM customers AS cust
INNER JOIN orders AS ord ON cust.customer_id = ord.customer_id;
-- أفضل للجداول القصيرة
SELECT
c.customer_name,
o.order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
2. حدد الأعمدة المطلوبة فقط
-- تجنب: استخدام SELECT *
SELECT *
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- الأفضل: تحديد الأعمدة المطلوبة
SELECT
c.customer_name,
c.email,
o.order_id,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
3. استخدم الفهارس على أعمدة الربط
-- إنشاء فهارس لتحسين الأداء
CREATE INDEX idx_customer_id ON orders(customer_id);
CREATE INDEX idx_order_id ON order_items(order_id);
CREATE INDEX idx_product_id ON order_items(product_id);
4. اكتب استعلامات قابلة للقراءة
-- استخدم المسافات والتنسيق المناسب
SELECT
c.customer_name AS اسم_العميل,
c.city AS المدينة,
o.order_date AS تاريخ_الطلب,
o.total_amount AS المبلغ
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.order_date >= '2026-01-01'
ORDER BY o.order_date DESC;
5. استخدم EXPLAIN لتحليل الأداء
-- تحليل خطة تنفيذ الاستعلام
EXPLAIN SELECT
c.customer_name,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.total_amount > 500;
ملخص الدرس
في هذا الدرس، تعلمنا كيفية استخدام INNER JOIN في SQL:
- INNER JOIN يُرجع فقط الصفوف المتطابقة في كلا الجدولين
- الصيغة الأساسية: SELECT ... FROM table1 INNER JOIN table2 ON condition
- يمكن استخدام الأسماء المستعارة لتبسيط الاستعلامات
- دمج INNER JOIN مع WHERE للتصفية المتقدمة
- استخدام الدوال التجميعية مع GROUP BY للإحصائيات
- ربط أكثر من جدولين في استعلام واحد
- استخدام USING بدلاً من ON عند تطابق أسماء الأعمدة
- الربط الذاتي (Self Join) لربط جدول مع نفسه
- أهمية تحديد شرط الربط لتجنب Cartesian Product
- استخدام الفهارس لتحسين أداء الاستعلامات
في الدرس القادم: سنتعلم عن الربط الأيسر (LEFT JOIN) في SQL، وكيف يختلف عن INNER JOIN، ومتى نستخدم كل نوع، مع أمثلة عملية توضح الفرق بينهما.
الخطوة التالية: الربط الأيسر LEFT JOIN
أكمل رحلتك التعليمية وانتقل إلى الدرس التالي لتعلم الربط الأيسر LEFT JOIN وتطوير مهاراتك في قواعد البيانات.
الانتقال إلى الدرس التالي