Giới thiệu
Trong chuỗi bài viết về T-SQL cơ bản này, chúng ta sẽ tìm hiểu cách truy vấn dữ liệu từ nhiều bảng khác nhau. Bài viết này tập trung vào các loại phép kết hợp (JOIN) trong SQL Server 2008.
Phần FROM trong câu lệnh SELECT là phần được xử lý đầu tiên về mặt logic. Trong mệnh đề FROM, chúng ta có thể sử dụng các toán tử bảng để thao tác với dữ liệu đầu vào.
SQL Server 2008 hỗ trợ bốn loại toán tử bảng: JOIN, APPLY, PIVOT, UNPIVOT. Bài viết này sẽ tập trung vào các loại JOIN.
1. CROSS JOIN (Phép Kết Hợp Chéo)
1.1 Khái niệm
CROSS JOIN thực hiện phép tích Descartes giữa hai bảng đầu vào. Mỗi dòng trong bảng thứ nhất sẽ được kết hợp với tất cả các dòng trong bảng thứ hai.
Nếu bảng thứ nhất có m dòng và bảng thứ hai có n dòng, kết quả sẽ là m × n dòng.
1.2 Cú pháp
Giả sử chúng ta có hai bảng: SanPham (4 dòng) và KhachHang (5 dòng).
Cú pháp ANSI SQL-92
SELECT SP.ma_sp, KH.ma_kh
FROM SanPham
CROSS JOIN KhachHang
Kết quả sẽ trả về 4 × 5 = 20 dòng.
Cú pháp ANSI SQL-89
SELECT SP.ma_sp, KH.ma_kh
FROM SanPham, KhachHang
Cả hai cú pháp đều tương đương về mặt logic và hiệu năng.
1.3 Self CROSS JOIN (Tự kết chéo)
Self CROSS JOIN là phép kết hợp chéo một bảng với chính nó. Khi sử dụng self-join, bắt buộc phải đặt bí danh (alias) cho bảng để tránh sự mơ hồ về tên cột.
SELECT A1.ma_sp, A2.ma_sp
FROM SanPham AS A1
CROSS JOIN SanPham AS A2
Nếu bảng SanPham có 4 dòng, kết quả sẽ có 4 × 4 = 16 dòng.
2. INNER JOIN (Phép Kết Hợp Trong)
2.1 Khái niệm
INNER JOIN thực hiện phép tích Descartes giữa hai bảng, sau đó lọc các dòng kết quả dựa trên điều kiện kết hợp được chỉ định.
2.2 Cú pháp
Cú pháp ANSI SQL-92
Điều kiện kết hợp được chỉ định trong mệnh đề ON. Từ khóa INNER là tùy chọn (mặc định là INNER JOIN).
Ví dụ: Kết hợp bảng DonHang và ChiTietDon theo điều kiện DonHang.ma_dh = ChiTietDon.ma_dh:
SELECT DH.ma_dh ,
DH.ngay_dat ,
CT.ma_sp ,
CT.so_luong
FROM dbo.DonHang AS DH
INNER JOIN ChiTietDon AS CT ON CT.ma_dh = DH.ma_dh
Cách hiểu INNER JOIN: Mỗi dòng trong bảng DonHang được so sánh với tất cả các dòng trong bảng ChiTietDon. Nếu giá trị ma_dh bằng nhau, dòng đó được giữ lại trong kết quả.
Về mặt toán học quan hệ, phép kết hợp trong thực hiện: tích Descartes (4 × 5 = 20 dòng) → lọc theo điều kiện → kết quả cuối cùng.
Lưu ý: Mệnh đề ON chỉ trả về các dòng thỏa mãn điều kiện là TRUE, không trả về FALSE hoặc UNKNOWN.
Cú pháp ANSI SQL-89
SELECT DH.ma_dh ,
DH.ngay_dat ,
CT.ma_sp ,
CT.so_luong
FROM dbo.DonHang AS DH ,
dbo.ChiTietDon AS CT
WHERE DH.ma_dh = CT.ma_dh
Khuyến nghị sử dụng ANSI SQL-92 vì:
- Nếu quên điều kiện trong mệnh đề ON, trình phân tích cú pháp sẽ báo lỗi
- Với cú pháp ANSI SQL-89, nếu quên điều kiện trong WHERE, trình phân tích vẫn chạy nhưng kết quả là CROSS JOIN - không phải INNER JOIN như mong đợi
2.3 Các trường hợp đặc biệt
Composite JOIN (Kết hợp phức hợp)
Khi điều kiện kết hợp cần nhiều hơn một cột. Ví dụ bảng ChiTietDon có khóa ngoại phức (ma_dh, ma_sp) tham chiếu đến bảng DonHang:
SELECT *
FROM ChiTietDon CT
INNER JOIN DonHang DH ON DH.ma_dh = CT.ma_dh
AND DH.ma_sp = CT.ma_sp
Non-Equi JOIN (Kết hợp không bằng)
Khi điều kiện sử dụng các toán tử khác ngoài dấu bằng (=):
SELECT *
FROM SanPham SP
INNER JOIN KhachHang KH ON SP.gia_ban < KH.gioi_han_tien
Ví dụ này trả về các cặp sản phẩm và khách hàng mà giá sản phẩm nhỏ hơn giới hạn tiền của khách hàng.
Multi-table JOIN (Kết hợp nhiều bảng)
Khi có nhiều toán tử JOIN trong mệnh đề FROM, chúng được xử lý từ trái sang phải. Kết quả của phép kết hợp thứ nhất trở thành đầu vào cho phép kết hợp thứ hai.
3. OUTER JOIN (Phép Kết Hợp Ngoài)
3.1 Khái niệm
OUTER JOIN thực hiện ba bước:
- Tích Descartes giữa hai bảng
- Lọc theo điều kiện trong mệnh đề ON
- Thêm các dòng bên ngoài (outer rows) từ bảng được bảo tồn
3.2 Cú pháp
Chỉ hỗ trợ cú pháp ANSI SQL-92:
-- LEFT OUTER JOIN: bảo tồn dòng bên trái
-- RIGHT OUTER JOIN: bảo tồn dòng bên phải
-- FULL OUTER JOIN: bảo tồn dòng cả hai bên
3.3 Nguyên tắc hoạt động
- LEFT JOIN: giữ lại tất cả dòng từ bảng bên trái
- RIGHT JOIN: giữ lại tất cả dòng từ bảng bên phải
- FULL JOIN: giữ lại tất cả dòng từ cả hai bảng
Các cột từ bảng không được bảo tồn sẽ có giá trị NULL trong các dòng bên ngoài.
3.4 Ví dụ
Liệt kê tất cả khách hàng cùng với đơn hàng của họ (bao gồm cả khách hàng chưa có đơn hàng nào):
SELECT KH.ma_kh ,
DH.ma_dh
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
Tìm các khách hàng chưa có đơn hàng nào:
SELECT KH.ma_kh ,
DH.ma_dh ,
DH.ngay_dat
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
WHERE DH.ma_kh IS NULL
3.5 Phân biệt ON và WHERE trong OUTER JOIN
- ON: xác định điều kiện để khớp dữ liệu giữa hai bảng
- WHERE: lọc kết quả cuối cùng sau khi đã thêm các dòng bên ngoài
Ví dụ: Trả về tất cả khách hàng và đơn hàng ngày 15/01/2008 (bao gồm cả khách hàng không có đơn hàng ngày đó):
SELECT KH.ma_kh ,
KH.ten_kh ,
DH.ma_dh ,
DH.ngay_dat
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
AND DH.ngay_dat = '20080115'
3.6 Lưu ý khi sử dụng với nhiều bảng
Thứ tự các phép kết hợp ảnh hưởng đến kết quả. Nếu một OUTER JOIN được theo sau bởi một INNER JOIN hoặc RIGHT JOIN, các dòng bên ngoài có thể bị mất nếu điều kiện JOIN so sánh giá trị NULL từ bảng bên trái với giá trị từ bảng bên phải.
4. Bài tập thực hành
4.1 Bài tập 1
Yêu cầu: Trả về các khách hàng đến từ Việt Nam, với mỗi khách hàng hiển thị tổng số đơn hàng và tổng số lượng sản phẩm đã mua.
SELECT KH.ma_kh ,
COUNT(DISTINCT DH.ma_dh) AS so_don_hang ,
SUM(CASE WHEN CT.so_luong IS NULL THEN 0 ELSE CT.so_luong END) AS tong_so_luong
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
LEFT OUTER JOIN Sales.ChiTietDon AS CT ON CT.ma_dh = DH.ma_dh
WHERE quoc_gia = N'Việt Nam'
GROUP BY KH.ma_kh
4.2 Bài tập 2
Yêu cầu: Trả về thông tin khách hàng và đơn hàng của họ, bao gồm cả khách hàng chưa đặt hàng lần nào.
SELECT KH.ma_kh ,
KH.ten_cong_ty ,
DH.ma_dh ,
DH.ngay_dat
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
4.3 Bài tập 3
Yêu cầu: Trả về khách hàng đã đặt hàng vào ngày 12/02/2008 và cả khách hàng chưa đặt hàng ngày đó.
Phân tích: Điều kiện lọc ngày đặt hàng phải đặt trong mệnh đề ON chứ không phải WHERE, vì WHERE sẽ lọc sau khi đã thêm các dòng bên ngoài.
SELECT KH.ma_kh ,
KH.ten_cong_ty ,
DH.ma_dh ,
DH.ngay_dat
FROM Sales.KhachHang AS KH
LEFT OUTER JOIN Sales.DonHang AS DH ON DH.ma_kh = KH.ma_kh
AND DH.ngay_dat = '20080212'
Tài liệu tham khảo
Microsoft SQL Server 2008 Internals - T-SQL Fundamentals