T-SQL Cơ Bản: Truy Vấn Kết Hợp Giữa Các Bảng

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:

  1. Tích Descartes giữa hai bảng
  2. Lọc theo điều kiện trong mệnh đề ON
  3. 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

Thẻ: SQL Server T-SQL JOIN INNER JOIN OUTER JOIN

Đăng vào ngày 20 tháng 7 lúc 15:18