Kết hợp dữ liệu từ nhiều bảng với JOIN là một kỹ năng quan trọng khi làm việc với SQL. Trong thực tế, để trả lời các câu hỏi phân tích dữ liệu như khách hàng đã mua sản phẩm nào, đơn hàng gồm những mặt hàng gì hay doanh thu của từng đơn hàng, chúng ta cần truy vấn thông tin từ nhiều bảng cùng lúc.
Trong bài học này, bạn sẽ tìm hiểu cách sử dụng các loại JOIN để liên kết dữ liệu giữa các bảng trong cơ sở dữ liệu cùng với các ví dụ minh họa để hiểu rõ cách áp dụng trong thực tế.

1 Vì sao cần JOIN?
Trong cơ sở dữ liệu quan hệ, thông tin thường được lưu trong nhiều bảng khác nhau thay vì một bảng duy nhất để tránh lặp dữ liệu và dễ quản lý. Các bảng này liên kết với nhau thông qua khóa chính và khóa ngoại. Vì vậy, để truy vấn thông tin đầy đủ, chúng ta cần kết hợp dữ liệu từ nhiều bảng với JOIN.
JOIN cho phép SQL ghép dữ liệu từ các bảng dựa trên các cột liên quan. Một số loại JOIN phổ biến trong PostgreSQL gồm:
- INNER JOIN – lấy các dòng khớp ở cả hai bảng
- LEFT JOIN – lấy toàn bộ bảng trái
- RIGHT JOIN – lấy toàn bộ bảng phải
- FULL OUTER JOIN – lấy toàn bộ dữ liệu của cả hai bảng
- Ngoài ra còn có SELF JOIN và CROSS JOIN
2 INNER JOIN — “Có thì lấy, không thì bỏ”
INNER JOIN là lựa chọn mặc định, chỉ trả về các dòng có dữ liệu khớp ở cả hai (02) bảng. Cú pháp quen thuộc như sau:
SELECT bang1.cot1, bang2.cot2
FROM bang1
INNER JOIN bang2 ON bang1.cot_chung = bang2.cot_chung;
SELECT b1.cot1, b2.cot2
FROM bang1 b1
INNER JOIN bang2 b2 ON b1.cot_chung = b2.cot_chung;
Trong đó:
- bang1, bang2, …: tên bảng thứ nhất và thứ hai
- b1, b2: bí danh của bang1 và bang2
- cot_chung: cột dùng để liên kết, thường là khóa chính và khóa ngoại
Ví dụ với bãng students và scores
SELECT s.student_id, s.name, sc.subject, sc.score
FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;

Mời xem ví dụ khác : Lấy danh sách khách hàng có đơn hàng
SELECT c.id, c.first_name, c.last_name, o.id, o.order_date
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;

Ví dụ: Liệt kê chi tiết các chi tiết đơn hàng bao gồm tên, email khách hàng, mã đơn hàng, ngày thực hiện, tên sản phẩm, số lượng, giá sản phẩm, thành tiền (trong ví dụ giới hạn số lượng kết quả trả về để tiện theo dõi)
SELECT
c.first_name || ' ' || c.last_name AS "Họ tên",
c.email,
o.id,
o.order_date,
p.product_name,
od.quantity,
p.price,
(od.quantity * p.price) AS total_amount
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
INNER JOIN order_details od ON o.id = od.order_id
INNER JOIN products p ON od.product_id = p.id
ORDER BY c.id, o.order_date
LIMIT 7;

3 LEFT JOIN — “Tôi muốn lấy đủ danh sách bên trái”
LEFT JOIN hay dùng khi bạn muốn: liệt kê đầy đủ một danh sách gốc, rồi “đính kèm” thông tin bên phải nếu có.
Nói cách khác LEFT JOIN trả về tất cả các dòng của bảng bên trái và các dòng trùng khớp từ bảng bên phải. Nếu một dòng ở bảng trái không có bản ghi tương ứng ở bảng phải, thì các cột lấy từ bảng phải sẽ được điền giá trị NULL.
Ví dụ: “Lấy danh sách tất cả khách hàng, kèm theo mã đơn hàng nếu có”
- Khách có đơn hàng → hiện orderID, orderDate
- Khách chưa có đơn hàng → orderID/orderDate = NULL
SELECT b1.cot1, b2.cot2
FROM bang1 b1
LEFT JOIN bang2 b2 ON b1.cot_chung = b2.cot_chung;
Trong đó:
- bang1: bảng trái (LEFT) muốn lấy hết dữ liệu
- bang2: bảng phải (RIGHT), nếu không trùng khớp dữ liệu sẽ hiển thị NULL
- b1, b2: bí danh của bang1 và bang2
- cot_chung: cột dùng để liên kết, thường là khóa chính và khóa ngoại
SELECT s.student_id, s.name, sc.subject, sc.score
FROM students s
LEFT JOIN scores sc ON s.student_id = sc.student_id;

Ví dụ: Lấy danh sách khách hàng, kèm theo mã đơn hàng nếu có
- Bảng LEFT: Customers (lấy toàn bộ)
- Bảng RIGHT: Orders (nếu không có dữ liệu khớp => NULL)
SELECT
c.first_name || ' ' || c.last_name AS "Họ tên",
c.email,
o.id,
o.order_date
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;



LEFT JOIN = “Giữ hết bảng trái; thiếu bên phải thì NULL.”
4 RIGHT JOIN — “Tôi muốn lấy đủ danh sách bên phải”
RIGHT JOIN giống LEFT JOIN nhưng đảo chiều. Cụ thể RIGHT JOIN lấy toàn bộ dữ liệu từ bảng bên phải, dữ liệu từ bảng bên trái chỉ lấy khi có khớp, nếu không khớp thì điền NULL. Cú pháp:
SELECT b1.cot1, b2.cot2
FROM bang1 b1
RIGHT JOIN bang2 b2 ON b1.cot_chung = b2.cot_chung;
Trong đó:
- bang1: bảng trái (LEFT), chỉ lấy khi trùng khớp nếu không sẽ điền NULL
- bang2: bảng phải (RIGHT), lấy toàn bộ dữ liệu
- b1, b2: bí danh của bang1 và bang2
- cot_chung: cột dùng để liên kết, thường là khóa chính và khóa ngoại
SELECT s.student_id, s.name, sc.subject, sc.score
FROM students s
RIGHT JOIN scores sc ON s.student_id = sc.student_id;

Ví dụ: Lấy danh sách sản phẩm và thông tin đơn hàng (nếu có)
SELECT
p.id,
p.product_name,
p.price,
p.stock_quantity,
od.quantity AS sold_quantity
FROM order_details od
RIGHT JOIN products p ON od.product_id = p.id;
- Bảng LEFT: order_details (nếu không tồn tại thì điền NULL)
- Bảng RIGHT: products (lấy toàn bộ dữ liệu)
Do tất cả sản phẩm đã được đặt ít nhất 1 lần nên không xuất hiện dữ liệu NULL


==>

RIGHT JOIN = “Giữ hết bảng phải; thiếu bên trái thì NULL.”
Thực tế, đa số người viết SQL ít dùng RIGHT JOIN vì bạn có thể đổi vị trí hai bảng và dùng LEFT JOIN cho dễ đọc.
5 FULL OUTER JOIN – “Cho tôi thấy cả hai phía, kể cả phần không khớp”
FULL OUTER JOIN trả về tất cả dòng từ cả 2 bảng, điền NULL cho các cột không khớp. FULL OUTER JOIN hữu ích khi:
- bạn muốn đối soát dữ liệu (so sánh có lệch không)
- hoặc muốn “liệt kê tất cả”, kể cả những phần thiếu liên kết
Ví dụ: lấy toàn bộ thông tin khách hàng và đơn hàng; khách không có đơn thì đơn = NULL; đơn nào đó không có khách (trường hợp dữ liệu lỗi) thì khách = NULL.
SELECT b1.cot1, b2.cot2
FROM bang1 b1
FULL OUTER JOIN bang2 b2
ON b1.cot_chung = b2.cot_chung;
Trong đó:
- bang1: bảng trái (LEFT)
- bang2: bảng phải (RIGHT)
- b1, b2: bí danh của bang1 và bang2
- cot_chung: cột dùng để liên kết, thường là khóa chính và khóa ngoại
Ví dụ 1:
SELECT s.student_id, s.name, sc.subject, sc.score
FROM students s
FULL OUTER JOIN scores sc ON s.student_id = sc.student_id;



Ví dụ 2: Lấy toàn bộ thông tin khách hàng và đơn hàng (hoặc: so sánh dữ liệu đơn hàng và khách hàng)
SELECT
c.id,
c.first_name,
c.last_name,
o.id as orderID,
o.order_date
FROM customers c
FULL OUTER JOIN orders o ON c.id = o.customer_id;
- Bảng LEFT: customers (lấy toàn bộ dữ liệu)
- Bảng RIGHT: orders (lấy toàn bộ dữ liệu)
Tất cả customer_id trong orders đều tồn tại trong danh sách khách hàng (bảng customers), khách hàng có customer_id = 10, 9 không có đơn hàng nào

FULL OUTER JOIN = giữ toàn bộ dữ liệu của cả hai bảng; thiếu bên nào thì NULL bên đó.
FULL OUTER JOIN = LEFT JOIN ∪ RIGHT JOIN
6 SELF JOIN — JOIN “chính mình”
SELF JOIN không phải loại JOIN riêng, mà là việc bạn JOIN một bảng với chính nó bằng hai alias khác nhau.
Tình huống điển hình: dữ liệu phân cấp như nhân viên – quản lý. Bảng employees có manager_id trỏ về emp_id trong cùng bảng.
SELECT b1.cot1, b2.cot2
ROM ten_bang b1
[INNER | LEFT | RIGHT | FULL OUTER] JOIN ten_bang b2
ON b1.cot_chung = b2.cot_chung;
Trong đó:
- ten_bang: bảng dữ liệu
- b1, b2: hai (02) bí danh của cùng một ten_bang
Sử dụng khi:
- Dữ liệu có quan hệ phân cấp (nhân viên-quản lý)
- Cần so sánh các dòng trong cùng một bảng
- Tìm các cặp dữ liệu có mối quan hệ với nhau
Ví dụ 1:
SELECT e.id, e.first_name || ' ' || e.last_name AS employee_name,
CONCAT_WS(' ', m.first_name, m.last_name) AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
Bảng employees

Kết quả truy vấn

Thu Dương không có ai quản lý (CEO)
An , Cường chịu sự quản lý của Văn Em
Bình, Ích, Kim chịu sự quản lý của Phượng
Ví dụ 2: Tìm các nhân viên cùng thành phố
SELECT
e1.first_name || ' ' || e1.last_name as nhanvien1,
e2.first_name || ' ' || e2.last_name as nhanvien2,
e1.city
FROM employees e1
JOIN employees e2
ON e1.city = e2.city
WHERE e1.id <e2.id
ORDER BY e1.city

7 CROSS JOIN — Tích Descartes (m × n)
Trong PostgreSQL, CROSS JOIN dùng để tạo tích Descartes giữa hai bảng:
- Mỗi dòng của bảng A sẽ kết hợp với tất cả dòng của bảng B.
- Nếu bảng A có m dòng, bảng B có n dòng → kết quả sẽ có m × n dòng.
- tạo ma trận màu × size
- tạo ma trận khách hàng × sản phẩm để phân tích tiềm năng
SELECT b1.cot1, b2.cot2
FROM bang1 b1
CROSS JOIN bang2 b2
Trong đó:
- bang1, bang2: bảng dữ liệu
- b1, b2: bí danh của bang1, bang2
Sử dụng khi:
- Tạo ma trận dữ liệu
- Tạo báo cáo pivot
- Tạo dữ liệu test
Ví dụ 1:
SELECT c.color_name AS "Màu sắc", s.size_name AS "Kích thước"
FROM colors c
CROSS JOIN sizes s
ORDER BY c.color_name;
Bảng colors

Bảng sizes


Ví dụ 2:Tạo ma trận khách hàng – sản phẩm để phân tích tiềm năng
SELECT
c.id as customID,
c.first_name || ' ' || c.last_name AS "Họ tên",
p.id as productID,
p.product_name,
p.price
FROM customers c
CROSS JOIN products p
ORDER BY c.id DESC, p.id
LIMIT 20;
Khách hàng

Sản phẩm


8 So sánh các loại JOIN
| JOIN | Lấy dữ liệu |
| INNER JOIN | chỉ phần khớp |
| LEFT JOIN | toàn bộ bảng trái |
| RIGHT JOIN | toàn bộ bảng phải |
| FULL JOIN | toàn bộ hai bảng |
| CROSS JOIN | tích Descartes |
KẾT LUẬN
Kết hợp dữ liệu từ nhiều bảng với JOIN là một kỹ năng quan trọng khi làm việc với cơ sở dữ liệu quan hệ. Nhờ JOIN, chúng ta có thể liên kết dữ liệu từ nhiều bảng khác nhau để tạo ra thông tin đầy đủ và có ý nghĩa hơn trong quá trình truy vấn và phân tích dữ liệu.
Trong bài học này, chúng ta đã tìm hiểu các loại JOIN phổ biến như INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, SELF JOIN và CROSS JOIN, cùng với các ví dụ minh họa giúp hiểu rõ cách các bảng dữ liệu được kết nối với nhau trong SQL.


