SQL Joins (Các Phép Nối SQL)
Bạn sẽ Học được gì
Kết thúc trang này, bạn sẽ có thể:
- Giải thích tại sao phép nối (JOIN) cần thiết trong cơ sở dữ liệu quan hệ (Relational Database)
- Viết các truy vấn INNER JOIN, LEFT JOIN, RIGHT JOIN và FULL OUTER JOIN
- Hiểu sự khác biệt giữa từng loại nối bằng sơ đồ Venn (Venn Diagrams) và bảng kết quả
- Sử dụng CROSS JOIN và SELF JOIN cho các trường hợp đặc biệt
- Tránh các cạm bẫy hiệu năng phổ biến khi viết truy vấn nối
- Chọn loại nối phù hợp cho từng câu hỏi kinh doanh
Vấn đề: Dữ liệu Nằm ở Nhiều Bảng
Cơ sở dữ liệu quan hệ chia dữ liệu thành nhiều bảng để tránh trùng lặp và duy trì tính toàn vẹn (Chuẩn hóa - Normalization). Nhưng để trả lời các câu hỏi thực tế, bạn thường cần kết hợp dữ liệu từ nhiều bảng.
employees table: departments table:
+----+--------+--------+ +--------+------------------+
| id | name | dept_id| | dept_id| dept_name |
+----+--------+--------+ +--------+------------------+
| 1 | Alice | 10 | | 10 | Engineering |
| 2 | Bob | 20 | | 20 | Marketing |
| 3 | Carol | 30 | | 30 | Sales |
| 4 | Dave | NULL | | 40 | HR |
+----+--------+--------+ +--------+------------------+
Các câu hỏi bạn không thể trả lời chỉ từ một bảng:
- "Alice làm ở phòng ban nào?" — cần cả hai bảng
- "Phòng ban nào không có nhân viên?" — cần cả hai bảng
- "Ai không có phòng ban?" — cần cả hai bảng
Phép nối (JOIN) là cơ chế SQL kết hợp các dòng từ hai bảng dựa trên cột liên kết giữa chúng.
Các Bảng Mẫu
Tất cả ví dụ sử dụng hai bảng sau:
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50)
);
INSERT INTO departments VALUES
(10, 'Engineering'),
(20, 'Marketing'),
(30, 'Sales'),
(40, 'HR');
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT REFERENCES departments(dept_id)
);
INSERT INTO employees VALUES
(1, 'Alice', 10),
(2, 'Bob', 20),
(3, 'Carol', 30),
(4, 'Dave', NULL),
(5, 'Eve', 10);
Điểm quan trọng cần lưu ý:
- Phòng ban 40 (HR) không có nhân viên
- Nhân viên 4 (Dave) không có phòng ban (
dept_idlà NULL) - Sự bất đối xứng này làm nổi bật sự khác biệt giữa các loại nối
Tổng quan Các loại Nối
INNER JOIN
Trả về chỉ các dòng có sự khớp ở cả hai bảng. Các dòng không khớp từ bất kỳ bảng nào đều bị loại trừ.
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
Kết quả:
| name | dept_name |
|---|---|
| Alice | Engineering |
| Bob | Marketing |
| Carol | Sales |
| Eve | Engineering |
- 4 dòng được trả về — chỉ nhân viên có phòng ban hợp lệ
- Dave bị loại trừ (không có phòng ban)
- HR bị loại trừ (không có nhân viên)
INNER JOIN là mặc định — viết chỉ JOIN tương đương với INNER JOIN.
LEFT JOIN (Left Outer Join)
Trả về tất cả dòng từ bảng bên trái và các dòng khớp từ bảng bên phải. Nếu không có sự khớp, các cột bên phải được điền NULL.
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
Kết quả:
| name | dept_name |
|---|---|
| Alice | Engineering |
| Bob | Marketing |
| Carol | Sales |
| Dave | NULL |
| Eve | Engineering |
- 5 dòng được trả về — tất cả nhân viên đều được bao gồm
dept_namecủa Dave làNULLvì anh ấy không có phòng ban- HR vẫn bị loại trừ (không có nhân viên)
Tìm các dòng không có sự khớp
Một mẫu phổ biến là sử dụng LEFT JOIN ... WHERE right.id IS NULL để tìm các dòng tồn tại ở bảng trái nhưng không có sự khớp ở bảng phải:
-- Tìm nhân viên không có phòng ban
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
WHERE d.dept_id IS NULL;
Kết quả:
| name |
|---|
| Dave |
RIGHT JOIN (Right Outer Join)
Trả về tất cả dòng từ bảng bên phải và các dòng khớp từ bảng bên trái. Nếu không có sự khớp, các cột bên trái được điền NULL.
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
Kết quả:
| name | dept_name |
|---|---|
| Alice | Engineering |
| Bob | Marketing |
| Carol | Sales |
| NULL | HR |
| Eve | Engineering |
- 5 dòng được trả về — tất cả phòng ban đều được bao gồm
- HR có
NULLchonamevì không có nhân viên - Dave lúc này bị loại trừ (không có phòng ban)
RIGHT JOIN về mặt chức năng tương đương với LEFT JOIN khi đổi thứ tự bảng. Trong thực tế, hầu hết lập trình viên thích LEFT JOIN hơn vì dễ đọc — giữ bảng "chính" ở trước.
FULL OUTER JOIN
Trả về tất cả dòng từ cả hai bảng. Các dòng khớp được kết hợp; các dòng không khớp từ bất kỳ bên nào nhận NULL cho các cột của bảng kia.
SELECT e.name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.dept_id;
Kết quả:
| name | dept_name |
|---|---|
| Alice | Engineering |
| Bob | Marketing |
| Carol | Sales |
| Dave | NULL |
| Eve | Engineering |
| NULL | HR |
- 6 dòng được trả về — mọi nhân viên và mọi phòng ban
- Dave có
NULLcho phòng ban (không có phòng ban) - HR có
NULLcho tên (không có nhân viên)
Tìm các dòng không khớp từ cả hai bên
-- Tìm nhân viên không có phòng ban VÀ phòng ban không có nhân viên
SELECT e.name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.dept_id
WHERE e.dept_id IS NULL OR d.dept_id IS NULL;
Kết quả:
| name | dept_name |
|---|---|
| Dave | NULL |
| NULL | HR |
CROSS JOIN
Trả về tích Descartes (Cartesian Product) — mọi dòng từ bảng trái kết hợp với mọi dòng từ bảng phải. Không cần điều kiện ON.
SELECT e.name, d.dept_name
FROM employees e
CROSS JOIN departments d;
Kết quả: 5 nhân viên x 4 phòng ban = 20 dòng
| name | dept_name |
|---|---|
| Alice | Engineering |
| Alice | Marketing |
| Alice | Sales |
| Alice | HR |
| Bob | Engineering |
| ... | ... |
CROSS JOIN có thể tạo tập kết quả rất lớn. Cross join hai bảng mỗi bảng 10,000 dòng sẽ tạo ra 100,000,000 dòng. Hãy sử dụng có chủ đích, không phải vô tình.
Khi nào sử dụng CROSS JOIN
- Tạo tất cả tổ hợp (ví dụ: tất cả kích thước x tất cả màu sắc)
- Tạo ma trận hoặc lưới giá trị
- Xây dựng kỳ báo cáo bằng cách kết hợp phạm vi ngày với các thực thể
SELF JOIN
Bảng tự nối với chính nó. Hữu ích khi các dòng trong cùng bảng có mối quan hệ phân cấp (Hierarchical Relationship) hoặc quan hệ ngang hàng.