IT Learning HubIT Learning Hub
THỰC HÀNH 02 · BTHT2

Truy vấn dữ liệu với SQL

5 tiếtDatabaseSQL Server

Mục tiêu: Sử dụng toán tử và hàm để xây dựng điều kiện truy vấn; viết SELECT lọc, sắp xếp, gom nhóm dữ liệu; kết hợp dữ liệu nhiều bảng bằng JOIN; dùng UNION/INTERSECT/EXCEPT; viết truy vấn lồng với IN, ANY, ALL, EXISTS, NOT EXISTS.

01 · TOÁN TỬ TRONG SQL

Toán tử

Toán tử là các ký hiệu hoặc từ khóa dùng để thực hiện phép toán, so sánh hoặc kết hợp điều kiện trong câu truy vấn.

Ví dụ dùng trong các mục dưới đây, giả sử có bảng:

NhanVien

MaNVHoTenLuong
NV01Nguyễn An10000000
NV02Trần Bình15000000
NV03Lê Cường12000000

Toán tử số học

Toán tửÝ nghĩa
+Cộng
-Trừ
*Nhân
/Chia
%Lấy phần dư

Tính lương sau khi tăng 10%:

SELECT HoTen, Luong, Luong * 1.1 AS LuongMoi
FROM NhanVien;
HoTenLuongLuongMoi
Nguyễn An1000000011000000
Trần Bình1500000016500000
Lê Cường1200000013200000

Lưu ý: Toán tử số học có thể dùng trực tiếp trong SELECT để tạo giá trị tính toán mà không cần lưu thêm cột mới vào bảng.

Toán tử so sánh

Toán tửÝ nghĩa
=Bằng
<> hoặc !=Khác
>Lớn hơn
<Nhỏ hơn
>=Lớn hơn hoặc bằng
<=Nhỏ hơn hoặc bằng

Tìm nhân viên có lương trên 10 triệu:

SELECT HoTen, Luong
FROM NhanVien
WHERE Luong > 10000000;

Tìm nhân viên thuộc phòng PB01:

SELECT *
FROM NhanVien
WHERE MaPB = 'PB01';

Toán tử logic

Toán tửÝ nghĩa
ANDTất cả điều kiện phải đúng
ORChỉ cần một điều kiện đúng
NOTPhủ định điều kiện

AND - Nhân viên phải đồng thời thỏa cả hai điều kiện:

SELECT *
FROM NhanVien
WHERE Luong > 5000000
  AND MaPB = 'PB01';

OR - Chỉ cần thỏa một trong hai điều kiện:

SELECT *
FROM NhanVien
WHERE Luong > 15000000
   OR MaPB = 'PB01';

NOT - Lấy nhân viên không thuộc PB01:

SELECT *
FROM NhanVien
WHERE NOT MaPB = 'PB01';

Toán tử đặc biệt

Toán tửÝ nghĩa
LIKESo khớp chuỗi theo mẫu (pattern)
INGiá trị nằm trong một danh sách cho trước
BETWEENGiá trị nằm trong một khoảng
IS NULLGiá trị chưa xác định (rỗng)
IS NOT NULLGiá trị đã có (không rỗng)
ANYĐúng với ít nhất một giá trị trong tập kết quả
ALLĐúng với tất cả giá trị trong tập kết quả

LIKE dùng để tìm theo mẫu (pattern), kết hợp với hai ký tự đại diện (wildcard):

Ký tự đại diệnÝ nghĩa
%Đại diện cho một chuỗi ký tự bất kỳ, kể cả chuỗi rỗng (0, 1 hoặc nhiều ký tự)
_Đại diện cho đúng một ký tự bất kỳ

Bắt đầu bằng "Nguyễn" - Dùng % ở cuối:

SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'Nguyễn%';

Đều có thể được tìm thấy: Nguyễn An, Nguyễn Bình, Nguyễn Văn Nam.

Kết thúc bằng "An" - Dùng % ở đầu:

SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'%An';

Có chứa "Văn" - Dùng % ở cả hai đầu:

SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'%Văn%';

_ chỉ thay thế đúng một ký tự - Ví dụ tìm mã phòng ban dạng PB0 kèm đúng một chữ số theo sau:

SELECT *
FROM NhanVien
WHERE MaPB LIKE 'PB0_';

Khớp PB01, PB02,... nhưng không khớp PB010 vì mẫu PB0_ chỉ chấp nhận đúng 4 ký tự.

BETWEEN:

SELECT *
FROM NhanVien
WHERE Luong BETWEEN 10000000 AND 15000000;

IN:

SELECT *
FROM NhanVien
WHERE MaPB IN ('PB01', 'PB02');

02 · HÀM TRONG SQL

Hàm trong SQL

Hàm giúp xử lý và biến đổi dữ liệu: Hàm xử lý số, hàm xử lý chuỗi, hàm xử lý ngày giờ và hàm tập hợp.
Lưu ý quan trọng: Tài liệu gốc trình bày một số hàm theo cú pháp SQL tổng quát/MySQL (NOW(), CURDATE(), LENGTH(), DATE_ADD(), CEIL()...). Môn thực hành dùng Microsoft SQL Server, nên các hàm dưới đây đã được chuẩn hóa sang T-SQL - Chạy được trực tiếp, không copy nguyên bản MySQL.
MySQL / tổng quátT-SQL (SQL Server)
NOW()GETDATE()
CURDATE()CAST(GETDATE() AS DATE)
LENGTH()LEN()
CEIL()CEILING()
DATE_ADD(date, INTERVAL n unit)DATEADD(unit, n, date)
DATEDIFF(date1, date2) - Số ngàyDATEDIFF(DAY, date1, date2) - Cần chỉ rõ đơn vị
GROUP_CONCAT(x)STRING_AGG(x, ', ') - Cần chỉ rõ dấu phân cách

Hàm xử lý số

HàmCông dụng
ABS()Giá trị tuyệt đối
ROUND()Làm tròn
CEILING()Làm tròn lên
FLOOR()Làm tròn xuống
SQRT()Căn bậc hai
POWER()Lũy thừa
SELECT
    ABS(-10) AS GiaTriTuyetDoi,
    ROUND(3.14159, 2) AS LamTron,
    SQRT(16) AS CanBacHai;

Hàm xử lý chuỗi

HàmCông dụng
CONCAT()Nối chuỗi
LEN()Đếm số ký tự
SUBSTRING()Cắt một đoạn chuỗi
TRIM()Xóa khoảng trắng thừa ở đầu/cuối
UPPER()Chuyển thành chữ hoa
LOWER()Chuyển thành chữ thường
REPLACE()Thay thế một đoạn chuỗi
SELECT
    HoTen,
    UPPER(HoTen) AS HoTenInHoa
FROM NhanVien;

Nối mã nhân viên và họ tên:

SELECT
    CONCAT(MaNV, ' - ', HoTen) AS ThongTinNhanVien
FROM NhanVien;

Hàm xử lý ngày giờ

HàmCông dụng
GETDATE()Lấy ngày giờ hiện tại
YEAR()Lấy năm
MONTH()Lấy tháng
DAY()Lấy ngày
DATEADD()Cộng/trừ một khoảng thời gian vào một ngày
DATEDIFF()Tính khoảng cách giữa hai ngày

Lấy năm của ngày vào làm:

SELECT
    HoTen,
    YEAR(NgVL) AS NamVaoLam
FROM NhanVien;

Tìm số ngày nhân viên đã làm việc (T-SQL DATEDIFF cần chỉ rõ đơn vị DAY):

SELECT
    HoTen,
    DATEDIFF(DAY, NgVL, GETDATE()) AS SoNgayLamViec
FROM NhanVien;

Hàm tổng hợp (Aggregate Functions)

Khác với 3 nhóm hàm trên (tính trên từng dòng), hàm tổng hợp tính toán trên một tập nhiều dòng và trả về một giá trị duy nhất - Thường đi kèm GROUP BY ở mục 04.

HàmCông dụng
COUNT(*)Đếm tổng số dòng (kể cả dòng có NULL)
COUNT(x)Đếm số dòng có giá trị khác NULL ở cột x
SUM(x)Tính tổng giá trị cột x
AVG(x)Tính giá trị trung bình cột x
MAX(x)Giá trị lớn nhất
MIN(x)Giá trị nhỏ nhất

Đếm số nhân viên và tính tổng/trung bình lương:

SELECT
    COUNT(*) AS SoNhanVien,
    SUM(Luong) AS TongLuong,
    AVG(Luong) AS LuongTrungBinh,
    MAX(Luong) AS LuongCaoNhat,
    MIN(Luong) AS LuongThapNhat
FROM NhanVien;

03 · MỆNH ĐỀ SELECT

SELECT

SELECT là một trong những thành phần quan trọng nhất của SQL, dùng để truy xuất dữ liệu từ một hoặc nhiều bảng.

Cú pháp tổng quát

SELECT [DISTINCT] <DanhSachCot>
FROM <TenBang>
[WHERE <DieuKien>]
[GROUP BY <DanhSachCot>]
[HAVING <DieuKienNhom>]
[ORDER BY <DanhSachCot> [ASC | DESC]];

SELECT cơ bản

Lấy tất cả dữ liệu:

SELECT *
FROM NhanVien;

Chỉ lấy một số cột:

SELECT MaNV, HoTen, Luong
FROM NhanVien;

DISTINCT

Loại bỏ dữ liệu trùng:

SELECT DISTINCT MaPB
FROM NhanVien;

Dữ liệu PB01, PB01, PB02, PB02, PB03 ➜ Kết quả chỉ còn PB01, PB02, PB03.

WHERE

Dùng để lọc các dòng theo điều kiện:

SELECT *
FROM NhanVien
WHERE Luong > 10000000
  AND MaPB = 'PB01';

ORDER BY

SELECT HoTen, Luong
FROM NhanVien
ORDER BY Luong DESC;

ASC - Tăng dần, DESC - Giảm dần.

TOP

Ví dụ lấy nhân viên mới nhất:

SELECT TOP 1 HoTen, NgVL
FROM NhanVien
ORDER BY NgVL DESC;

GROUP BY

Dùng để gom các dòng có cùng giá trị thành nhóm. Ví dụ - Số lượng nhân viên trong từng phòng ban:

SELECT
    MaPB,
    COUNT(*) AS SoLuong
FROM NhanVien
GROUP BY MaPB;

HAVING

WHERE lọc dòng dữ liệu, còn HAVING lọc nhóm dữ liệu sau GROUP BY. Ví dụ - Chỉ hiển thị mức lương có ít nhất 3 nhân viên:

SELECT
    Luong,
    COUNT(*) AS SoLuong
FROM NhanVien
GROUP BY Luong
HAVING COUNT(*) >= 3;
WHERE ↓ lọc từng dòng ↓ GROUP BY ↓ gom nhóm ↓ HAVING ↓ lọc nhóm

KIỂM TRA NHANH

Mệnh đề nào dùng để lọc NHÓM dữ liệu sau GROUP BY?

04 · CÁC PHÉP KẾT JOIN

JOIN

JOIN dùng để kết hợp dữ liệu từ hai hoặc nhiều bảng dựa trên một điều kiện liên quan giữa các bảng.
FK NhanVien MaPB (FK) PhongBan MaPB (PK)

NhanVien.MaPB (FK) ➜ PhongBan.MaPB (PK): Mỗi nhân viên thuộc về đúng 1 phòng ban.

4 PHÉP JOIN CHÍNH - VÙNG XANH LÀ DỮ LIỆU ĐƯỢC GIỮ LẠI

A B INNER JOIN
A B LEFT JOIN
A B RIGHT JOIN
A B FULL OUTER

INNER JOIN

Chỉ lấy những dòng có dữ liệu khớp ở cả hai bảng:

SELECT
    nv.HoTen,
    pb.TenPB
FROM NhanVien nv
INNER JOIN PhongBan pb
    ON nv.MaPB = pb.MaPB;

LEFT JOIN

Lấy tất cả dữ liệu của bảng bên trái, kể cả khi không có dữ liệu tương ứng ở bảng phải:

SELECT
    nv.HoTen,
    pb.TenPB
FROM NhanVien nv
LEFT JOIN PhongBan pb
    ON nv.MaPB = pb.MaPB;
HoTenTenPB
Nguyễn AnPhòng KT
Trần BìnhPhòng KD
Lê CườngNULL

RIGHT JOIN

Ngược lại với LEFT JOIN: Giữ toàn bộ dữ liệu của bảng bên phải:

SELECT
    nv.HoTen,
    pb.TenPB
FROM NhanVien nv
RIGHT JOIN PhongBan pb
    ON nv.MaPB = pb.MaPB;

FULL OUTER JOIN

Giữ dữ liệu từ cả hai bảng, bất kể có khớp hay không:

SELECT
    nv.HoTen,
    pb.TenPB
FROM NhanVien nv
FULL OUTER JOIN PhongBan pb
    ON nv.MaPB = pb.MaPB;

CROSS JOIN

Tạo tích Cartesian - Nếu NhanVien có 5 dòng, PhongBan có 3 dòng thì kết quả là 5 × 3 = 15 dòng:

SELECT
    nv.HoTen,
    pb.TenPB
FROM NhanVien nv
CROSS JOIN PhongBan pb;

SELF JOIN

Một bảng được sử dụng hai lần để thể hiện quan hệ giữa các dòng trong chính bảng đó - Ví dụ nhân viên và người quản lý:

SELECT
    nv.HoTen AS NhanVien,
    ql.HoTen AS QuanLy
FROM NhanVien nv
JOIN NhanVien ql
    ON nv.MaQL = ql.MaNV;

EQUI JOIN & khuyến nghị cú pháp

EQUI JOIN sử dụng phép so sánh bằng = giữa các cột liên quan. Nên ưu tiên cú pháp JOIN ... ON:

FROM NhanVien nv
INNER JOIN PhongBan pb
    ON nv.MaPB = pb.MaPB

thay vì đưa điều kiện kết vào WHERE (vẫn chạy được nhưng không được khuyến khích):

FROM NhanVien nv, PhongBan pb
WHERE nv.MaPB = pb.MaPB;
SQL Server không có NATURAL JOIN: Một số tài liệu tham khảo (MySQL, PostgreSQL, Oracle) còn có thêm NATURAL JOIN - Tự động kết theo các cột trùng tên giữa hai bảng. T-SQL không hỗ trợ cú pháp này - Nếu gặp trong tài liệu khác, hãy viết lại bằng INNER JOIN ... ON với điều kiện kết tường minh như trên.

05 · CÁC PHÉP TOÁN TẬP HỢP

Set Operations

Có 3 phép chính, dùng để kết hợp kết quả của nhiều truy vấn:

A = KẾT QUẢ TRUY VẤN 1 · B = KẾT QUẢ TRUY VẤN 2

A B UNION
A B INTERSECT
A B EXCEPT
UNION INTERSECT EXCEPT

UNION

Lấy tất cả kết quả và loại bỏ dòng trùng. Ví dụ - Nhân viên thực hiện DA01 hoặc DA02:

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'

UNION

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';

INTERSECT

Lấy phần giao nhau. Ví dụ - Nhân viên thực hiện cả DA01 và DA02:

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'

INTERSECT

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';

EXCEPT

Lấy kết quả có trong truy vấn thứ nhất nhưng không có trong truy vấn thứ hai. Ví dụ - Nhân viên thực hiện DA01 nhưng không thực hiện DA02:

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'

EXCEPT

SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';
A UNION B ➜ A hoặc B A INTERSECT B ➜ A và B A EXCEPT B ➜ A nhưng không B

06 · TRUY VẤN LỒNG SUBQUERY

Subquery

Subquery là một câu truy vấn được đặt bên trong một câu truy vấn khác. Truy vấn bên trong được thực hiện để cung cấp kết quả cho truy vấn bên ngoài.
SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
    SELECT MaSP
    FROM HoaDon
);

Non-Correlated Subquery

Truy vấn con không phụ thuộc vào truy vấn cha. Ví dụ - Tìm sản phẩm đã bán trong tháng 1/2024:

SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
    SELECT MaSP
    FROM HoaDon
    WHERE MONTH(NgayBan) = 1
      AND YEAR(NgayBan) = 2024
);

IN / NOT IN

IN - Kiểm tra một giá trị có nằm trong tập kết quả của truy vấn con hay không:

SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
    SELECT MaSP
    FROM HoaDon
);

NOT IN - Tìm sản phẩm chưa từng xuất hiện trong hóa đơn:

SELECT TenSP
FROM SanPham
WHERE MaSP NOT IN
(
    SELECT MaSP
    FROM HoaDon
);

ANY / ALL

ANY so sánh với ít nhất một giá trị trong kết quả truy vấn con. Ví dụ - Lương lớn hơn bất kỳ nhân viên nào thuộc PB01, PB02 hoặc PB03:

SELECT HoTen, Luong
FROM NhanVien
WHERE Luong > ANY
(
    SELECT Luong
    FROM NhanVien
    WHERE MaPB IN ('PB01', 'PB02', 'PB03')
);

ALL yêu cầu điều kiện đúng với tất cả giá trị trong truy vấn con. Ví dụ - Sản phẩm có giá cao hơn tất cả sản phẩm thuộc danh mục Laptop:

SELECT MaSP, TenSP, GiaBan
FROM SanPham
WHERE DanhMuc = 'Phu Kien'
  AND GiaBan > ALL
(
    SELECT GiaBan
    FROM SanPham
    WHERE DanhMuc = 'Laptop'
);
> ANY ➜ Lớn hơn ít nhất một > ALL ➜ Lớn hơn tất cả

EXISTS / NOT EXISTS

EXISTS kiểm tra truy vấn con có trả về ít nhất một dòng hay không. Ví dụ - Nhân viên có ít nhất một đơn hàng:

SELECT HoTen
FROM NhanVien nv
WHERE EXISTS
(
    SELECT *
    FROM DonHang dh
    WHERE dh.MaNV = nv.MaNV
);

Truy vấn con ở đây tham chiếu đến dòng hiện tại của truy vấn cha (nv.MaNV) - Đây gọi là Correlated Subquery.

NOT EXISTS - Tìm nhân viên không có bất kỳ đơn hàng nào:

SELECT HoTen
FROM NhanVien nv
WHERE NOT EXISTS
(
    SELECT *
    FROM DonHang dh
    WHERE dh.MaNV = nv.MaNV
);

07 · TỔNG HỢP - CHỌN CÔNG CỤ NÀO?

Chọn công cụ nào?

Bảng tra nhanh: Biết cú pháp là một việc, biết khi nào dùng cái gì mới là quan trọng.

Bài toánCông cụ
Lọc dữ liệuWHERE
Nhiều điều kiệnAND, OR, NOT
Tìm theo mẫuLIKE
Tìm trong danh sáchIN
Sắp xếpORDER BY
Loại trùngDISTINCT
Gom nhómGROUP BY
Lọc nhómHAVING
Kết hợp bảngJOIN
Kết hợp kết quả truy vấnUNION
Phần chungINTERSECT
Có A nhưng không BEXCEPT
So sánh với tập kết quảANY, ALL
Kiểm tra có dữ liệuEXISTS
Kiểm tra không có dữ liệuNOT EXISTS

08 · BÀI TẬP THỰC HÀNH

Thực hành tổng hợp

BÀI TẬP CHÍNH THỨC · BTTH2

Quản lý bán hàng - Câu 1 ➜ 25

Xem đề bài đầy đủ, file đính kèm và đáp án chi tiết (cần mã truy cập) của BTTH2.

Xem bài tập & đáp án ›