JOIN - ربط الجداول — SQL الأساسيات
JOIN — ربط الجداول
البيانات موزعة على جداول (درس التصميم). JOIN يجمعها في نتيجة واحدة عبر المفاتيح.
INNER JOIN: المتطابق فقط
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total REAL NOT NULL CHECK (total >= 0),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO users (id, name, email, city) VALUES
(1, 'سارة', 'sara@example.com', 'بغداد'),
(2, 'عمر', 'omar@example.com', 'البصرة'),
(3, 'ليلى', 'layla@example.com', 'بغداد'),
(4, 'زيد', 'zaid@example.com', NULL);
INSERT INTO orders (user_id, total, status) VALUES
(1, 120.0, 'paid'), (1, 45.5, 'paid'), (2, 300.0, 'pending'),
(3, 80.0, 'paid'), (3, 15.0, 'cancelled'), (1, 60.0, 'pending');
SELECT u.name, o.total, o.status
FROM orders o
INNER JOIN users u ON u.id = o.user_id
ORDER BY u.name;- o و u أسماء مستعارة تختصر الكتابة وتزيل الغموض (كلا الجدولين فيه id).
- شرط ON يحدد كيف ترتبط الصفوف.
- المستخدم بلا طلبات لا يظهر.
LEFT JOIN: كل صفوف اليسار
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total REAL NOT NULL CHECK (total >= 0),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO users (id, name, email, city) VALUES
(1, 'سارة', 'sara@example.com', 'بغداد'),
(2, 'عمر', 'omar@example.com', 'البصرة'),
(3, 'ليلى', 'layla@example.com', 'بغداد'),
(4, 'زيد', 'zaid@example.com', NULL);
INSERT INTO orders (user_id, total, status) VALUES
(1, 120.0, 'paid'), (1, 45.5, 'paid'), (2, 300.0, 'pending'),
(3, 80.0, 'paid'), (3, 15.0, 'cancelled'), (1, 60.0, 'pending');
SELECT u.name, COUNT(o.id) AS orders_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;كل المستخدمين يظهرون، ومن لا طلبات له تكون أعمدة الطلب NULL. لاحظ COUNT(o.id) لا COUNT(*): الأولى لا تعد صفوف NULL فتعطي 0 صحيحاً.
إيجاد "من ليس له"
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total REAL NOT NULL CHECK (total >= 0),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO users (id, name, email, city) VALUES
(1, 'سارة', 'sara@example.com', 'بغداد'),
(2, 'عمر', 'omar@example.com', 'البصرة'),
(3, 'ليلى', 'layla@example.com', 'بغداد'),
(4, 'زيد', 'zaid@example.com', NULL);
INSERT INTO orders (user_id, total, status) VALUES
(1, 120.0, 'paid'), (1, 45.5, 'paid'), (2, 300.0, 'pending'),
(3, 80.0, 'paid'), (3, 15.0, 'cancelled'), (1, 60.0, 'pending');
SELECT u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL; -- مستخدمون بلا أي طلبأنواع JOIN
| النوع | النتيجة |
|---|---|
| INNER JOIN | المتطابق في الجدولين فقط |
| LEFT JOIN | كل اليسار + المطابق من اليمين (أو NULL) |
| RIGHT JOIN | العكس (نادر؛ اقلب ترتيب الجداول واستخدم LEFT) |
| FULL OUTER JOIN | الكل من الطرفين |
| CROSS JOIN | كل التركيبات (حذار: 1000 × 1000 = مليون صف) |
⚠️ شرط الفلترة في ON أم WHERE مع LEFT JOIN؟
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total REAL NOT NULL CHECK (total >= 0),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO users (id, name, email, city) VALUES
(1, 'سارة', 'sara@example.com', 'بغداد'),
(2, 'عمر', 'omar@example.com', 'البصرة'),
(3, 'ليلى', 'layla@example.com', 'بغداد'),
(4, 'زيد', 'zaid@example.com', NULL);
INSERT INTO orders (user_id, total, status) VALUES
(1, 120.0, 'paid'), (1, 45.5, 'paid'), (2, 300.0, 'pending'),
(3, 80.0, 'paid'), (3, 15.0, 'cancelled'), (1, 60.0, 'pending');
-- كل المستخدمين مع طلباتهم المدفوعة فقط (من بلا طلب مدفوع يبقى ظاهراً)
SELECT u.name, o.total FROM users u LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid';
-- هذا يحوّلها عملياً لـ INNER JOIN: يحذف من ليس له طلب مدفوع
SELECT u.name, o.total FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE o.status = 'paid';الربط الذاتي
جدول يُربط بنفسه، مثل موظف ومديره في جدول employees واحد: FROM employees e LEFT JOIN employees m ON m.id = e.manager_id.
عدة جداول
الربط متسلسل: FROM loans l JOIN books b ON ... JOIN members m ON ... — لكل JOIN شرطه.
تمرين
على جداول المثال: اعرض لكل مدينة عدد المستخدمين ومجموع مبيعاتهم المدفوعة (المدن بلا مبيعات تظهر بـ 0)، ثم اعرض أسماء المستخدمين الذين لديهم طلب ملغى.
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total REAL NOT NULL CHECK (total >= 0),
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO users (id, name, email, city) VALUES
(1, 'سارة', 'sara@example.com', 'بغداد'),
(2, 'عمر', 'omar@example.com', 'البصرة'),
(3, 'ليلى', 'layla@example.com', 'بغداد'),
(4, 'زيد', 'zaid@example.com', NULL);
INSERT INTO orders (user_id, total, status) VALUES
(1, 120.0, 'paid'), (1, 45.5, 'paid'), (2, 300.0, 'pending'),
(3, 80.0, 'paid'), (3, 15.0, 'cancelled'), (1, 60.0, 'pending');
-- INNER: الطلبات مع أسماء أصحابها
SELECT u.name, o.total, o.status
FROM orders o
JOIN users u ON u.id = o.user_id
ORDER BY o.total DESC;
-- LEFT: كل المستخدمين ومجموع مدفوعاتهم (0 لمن لا يملك)
SELECT u.name, COALESCE(SUM(o.total), 0) AS paid_total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid'
GROUP BY u.id, u.name
ORDER BY paid_total DESC;
-- من لا طلب له
SELECT u.name FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE o.id IS NULL;اختبر فهمك