Truy vấn dữ liệu với SQL
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ử
Ví dụ dùng trong các mục dưới đây, giả sử có bảng:
NhanVien
| MaNV | HoTen | Luong |
|---|---|---|
| NV01 | Nguyễn An | 10000000 |
| NV02 | Trần Bình | 15000000 |
| NV03 | Lê Cường | 12000000 |
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;| HoTen | Luong | LuongMoi |
|---|---|---|
| Nguyễn An | 10000000 | 11000000 |
| Trần Bình | 15000000 | 16500000 |
| Lê Cường | 12000000 | 13200000 |
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 |
|---|---|
AND | Tất cả điều kiện phải đúng |
OR | Chỉ cần một điều kiện đúng |
NOT | Phủ đị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 |
|---|---|
LIKE | So khớp chuỗi theo mẫu (pattern) |
IN | Giá trị nằm trong một danh sách cho trước |
BETWEEN | Giá trị nằm trong một khoảng |
IS NULL | Giá trị chưa xác định (rỗng) |
IS NOT NULL | Giá 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
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át | T-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ày | DATEDIFF(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àm | Cô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àm | Cô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àm | Cô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àm | Cô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;KIỂM TRA NHANH
Mệnh đề nào dùng để lọc NHÓM dữ liệu sau GROUP BY?
GROUP BY, còn WHERE lọc từng dòng trước khi gom nhóm.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.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
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;| HoTen | TenPB |
|---|---|
| Nguyễn An | Phòng KT |
| Trần Bình | Phòng KD |
| Lê Cường | NULL |
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.MaPBthay 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;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
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';06 · TRUY VẤN LỒNG SUBQUERY
Subquery
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'
);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án | Công cụ |
|---|---|
| Lọc dữ liệu | WHERE |
| Nhiều điều kiện | AND, OR, NOT |
| Tìm theo mẫu | LIKE |
| Tìm trong danh sách | IN |
| Sắp xếp | ORDER BY |
| Loại trùng | DISTINCT |
| Gom nhóm | GROUP BY |
| Lọc nhóm | HAVING |
| Kết hợp bảng | JOIN |
| Kết hợp kết quả truy vấn | UNION |
| Phần chung | INTERSECT |
| Có A nhưng không B | EXCEPT |
| So sánh với tập kết quả | ANY, ALL |
| Kiểm tra có dữ liệu | EXISTS |
| Kiểm tra không có dữ liệu | NOT EXISTS |
08 · BÀI TẬP THỰC HÀNH
Thực hành tổng hợp
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.
