Advanced SQL
SELECT Statement — Truy vấn dữ liệu
Toàn bộ SELECT statement: predicates (WHERE), operators (LIKE, BETWEEN, IN, IS NULL), aliases, concatenation, aggregate functions (COUNT, MAX, MIN, SUM, AVG), GROUP BY, và các loại JOIN (INNER, OUTER, NATURAL, multi-table).
SELECT Statement — Truy vấn dữ liệu
SELECT là lệnh đọc dữ liệu từ database. Cú pháp đầy đủ:
SELECT column1, column2, ... FROM table_name WHERE condition GROUP BY column HAVING group_condition ORDER BY column ASC|DESC;
1. Restriction — Mệnh đề WHERE (Predicate)
Restriction là lọc dòng — chỉ lấy các dòng thỏa điều kiện. WHERE clause dùng predicate (vị từ) để xác định điều kiện.
SELECT * FROM Staff WHERE BranchID = 101;
Các toán tử predicate quan trọng
LIKE — So khớp mẫu chuỗi
%= đại diện cho 0 hoặc nhiều ký tự bất kỳ_= đại diện cho đúng 1 ký tự bất kỳ
SELECT * FROM Staff WHERE StaffName LIKE 'A%'; SELECT * FROM Staff WHERE StaffName LIKE '%son'; SELECT * FROM Staff WHERE StaffName LIKE '_oe%'; SELECT * FROM Staff WHERE StaffName LIKE '%an%';
Giải thích: 'A%' = tên bắt đầu bằng A. '%son' = kết thúc bằng son. '_oe%' = ký tự thứ 2 và 3 là 'oe'. '%an%' = chứa 'an' ở bất kỳ vị trí nào.
BETWEEN — Khoảng giá trị (inclusive)
SELECT * FROM Inspection WHERE InspDate BETWEEN '2025-01-01' AND '2025-03-31'; SELECT * FROM Product WHERE Price BETWEEN 100 AND 500;
BETWEEN bao gồm cả hai đầu mút (inclusive on both ends).
IN và NOT IN — Kiểm tra danh sách
SELECT * FROM Staff WHERE BranchID IN (101, 102, 105); SELECT * FROM Staff WHERE BranchID NOT IN (103, 104);
IS NULL và IS NOT NULL
SELECT * FROM Staff WHERE PhoneNumber IS NULL; SELECT * FROM Staff WHERE Email IS NOT NULL;
KHÔNG ĐƯỢC viết WHERE PhoneNumber = NULL — luôn dùng IS NULL vì NULL không thể so sánh bằng toán tử =.
AND và OR — Kết hợp điều kiện
SELECT * FROM Staff WHERE BranchID = 101 AND Gender = 'F'; SELECT * FROM Staff WHERE BranchID = 101 OR BranchID = 102; SELECT * FROM Staff WHERE Gender = 'F' AND (BranchID = 101 OR BranchID = 102);
AND có độ ưu tiên cao hơn OR — dùng ngoặc đơn để rõ ràng.
2. Projection — Chọn cột cụ thể
Projection là chọn chỉ các cột cần thiết (thay vì SELECT *):
SELECT StaffID, StaffName, BranchID FROM Staff;
Aliases — Đặt tên bí danh
SELECT StaffName AS 'Employee Name',
BranchID AS 'Branch'
FROM Staff;
SELECT s.StaffID, s.StaffName, b.BranchName
FROM Staff s
JOIN Branch b ON s.BranchID = b.BranchID;
Alias cho bảng (s, b) giúp query ngắn gọn hơn khi JOIN nhiều bảng.
Concatenation — Nối chuỗi
SELECT CONCAT(FirstName, ' ', LastName) AS 'Full Name' FROM Person; SELECT FirstName || ' ' || LastName AS 'Full Name' FROM Person;
MySQL dùng CONCAT(). Chuẩn SQL dùng || (Oracle, PostgreSQL).
3. Aggregate Functions — Hàm tổng hợp
| Hàm | Mô tả | Ví dụ |
|---|---|---|
COUNT(*) | Đếm tất cả dòng (kể cả NULL) | SELECT COUNT(*) FROM Staff; |
COUNT(col) | Đếm dòng có giá trị non-NULL trong cột | SELECT COUNT(Email) FROM Staff; |
MAX(col) | Giá trị lớn nhất | SELECT MAX(Score) FROM Result; |
MIN(col) | Giá trị nhỏ nhất | SELECT MIN(Price) FROM Product; |
SUM(col) | Tổng cộng | SELECT SUM(Amount) FROM Order; |
AVG(col) | Trung bình cộng | SELECT AVG(Mark) FROM Enrolment; |
GROUP BY — Nhóm theo cột
GROUP BY kết hợp với aggregate functions để tính toán theo từng nhóm:
SELECT BranchID, COUNT(*) AS StaffCount FROM Staff GROUP BY BranchID;
Kết quả: Số nhân viên theo từng chi nhánh.
SELECT BranchID, Gender, COUNT(*) AS Count FROM Staff GROUP BY BranchID, Gender;
SELECT SubjectID,
AVG(Mark) AS AvgMark,
MAX(Mark) AS TopMark,
MIN(Mark) AS LowestMark,
COUNT(*) AS StudentCount
FROM Enrolment
WHERE Mark IS NOT NULL
GROUP BY SubjectID
HAVING AVG(Mark) > 60
ORDER BY AvgMark DESC;
HAVING lọc kết quả sau GROUP BY (giống WHERE nhưng dành cho group). Ví dụ trên chỉ hiển thị môn học có điểm trung bình trên 60.
4. JOINs — Kết hợp bảng
Lịch sử: Kiểu JOIN cũ (pre-SQL-92)
SELECT s.StudentName, e.SubjectID, e.Mark FROM Student s, Enrolment e WHERE s.StudentID = e.StudentID;
Đây là cú pháp cũ — vẫn hoạt động nhưng không nên dùng vì dễ nhầm lẫn khi nhiều bảng.
INNER JOIN — Chỉ lấy dòng khớp ở cả hai bảng
SELECT s.StudentName, e.SubjectID, e.Mark FROM Student s INNER JOIN Enrolment e ON s.StudentID = e.StudentID;
Ví dụ từ bài giảng: Anna không có enrolment nào → INNER JOIN sẽ loại bỏ Anna khỏi kết quả.
| StudentName | SubjectID | Mark |
|---|---|---|
| John | COS101 | 75 |
| John | COS102 | 82 |
| Mary | COS101 | 88 |
| Anna | (không xuất hiện — không có enrolment) |
LEFT OUTER JOIN / RIGHT OUTER JOIN — Giữ tất cả dòng từ một bảng
SELECT s.StudentName, e.SubjectID, e.Mark FROM Enrolment e RIGHT JOIN Student s ON e.StudentID = s.StudentID;
RIGHT JOIN giữ tất cả dòng từ bảng bên phải (Student). Anna xuất hiện với SubjectID = NULL và Mark = NULL:
| StudentName | SubjectID | Mark |
|---|---|---|
| John | COS101 | 75 |
| John | COS102 | 82 |
| Mary | COS101 | 88 |
| Anna | NULL | NULL |
NATURAL JOIN — Tự động khớp theo cột cùng tên
SELECT StudentName, SubjectID, Mark FROM Student NATURAL JOIN Enrolment;
NATURAL JOIN tự động khớp theo các cột có cùng tên ở cả hai bảng. Ít dùng vì dễ gây lỗi nếu bảng thêm cột mới.
Multi-table JOIN — JOIN nhiều bảng
SELECT s.StaffName, p.PropertyAddress, i.InspDate, i.Comments FROM Staff s INNER JOIN PropertyInspection i ON s.StaffID = i.StaffID INNER JOIN Property p ON i.PropertyID = p.PropertyID WHERE i.InspDate BETWEEN '2025-01-01' AND '2025-06-30' ORDER BY i.InspDate;
JOIN ba bảng: Staff → PropertyInspection → Property. Mỗi JOIN thêm một bảng vào kết quả bằng cách khớp FK.
Chiến lược thực thi Query (Query Execution Strategy)
RDBMS không thực thi query theo thứ tự bạn viết. Nó tối ưu hóa:
- Phân tích mệnh đề WHERE để lọc sớm nhất có thể
- Chọn bảng có ít dòng nhất để bắt đầu
- Dùng index nếu có
- JOIN theo thứ tự tối ưu
Dùng EXPLAIN SELECT ... để xem kế hoạch thực thi của MySQL.