Chào mừng bạn đến với bài giảng SQL mới! Hôm nay tụi mình sẽ làm quen với những toán tử tưởng như "vô hình" nhưng cực kỳ mạnh mẽ — EXISTS và NOT EXISTS. Hãy tưởng tượng bạn là một điệp viên, không để lại dấu vết nhưng luôn báo ngay: "Ừ, có đối tượng này" hoặc "Không, ở đây trống trơn". Hai toán tử này không trả về dữ liệu trực tiếp, mà giúp bạn thực hiện kiểm tra logic cực kỳ chính xác trong truy vấn.
Bắt đầu từ cơ bản nhé. EXISTS — là toán tử kiểm tra sự tồn tại của bản ghi trong kết quả của subquery. Nếu subquery trả về ít nhất một dòng, điều kiện EXISTS sẽ trả về TRUE, ngược lại là FALSE.
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
Như bạn thấy, mình không quan tâm dữ liệu trong subquery, chỉ cần biết có dòng nào thỏa điều kiện không thôi. Nếu có ít nhất một bản ghi phù hợp, truy vấn trả về 1.
Cú pháp EXISTS
Cú pháp EXISTS khá đơn giản:
SELECT cột
FROM bảng
WHERE EXISTS (
SELECT 1
FROM bảng_khác
WHERE điều_kiện
);
Giải thích:
- Subquery lồng trong
EXISTScó thể là bất kỳ truy vấn nào. - Kết quả của subquery quyết định trả về
TRUEhayFALSE.
Ví dụ: Có sinh viên nào điểm trên 4 không?
Giả sử có bảng students:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
Giả sử bạn muốn kiểm tra có sinh viên nào điểm trên 4 không. Dùng truy vấn sau:
SELECT 'Có sinh viên điểm cao!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
Kết quả:
Có sinh viên điểm cao!
Tại sao EXISTS chạy nhanh hơn IN?
Lợi thế lớn nhất của EXISTS là nó dừng thực thi subquery ngay khi tìm thấy kết quả đầu tiên. Nghĩa là nếu bạn chỉ cần kiểm tra sự tồn tại, EXISTS cực kỳ hiệu quả.
Ví dụ, bảng students có hàng triệu dòng, nhưng bạn chỉ cần biết có ai điểm > 4 không. Ngay khi SQL tìm thấy dòng đầu tiên thỏa điều kiện, truy vấn kết thúc luôn.
Sử dụng NOT EXISTS
Giờ nói về NOT EXISTS nhé. Toán tử này là "phản đề" của EXISTS. Nó trả về TRUE nếu subquery không trả về dòng nào.
Ví dụ: tìm sinh viên chưa có điểm (NULL)
Giả sử bảng có sinh viên chưa có điểm:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
Bạn muốn chọn tất cả sinh viên chưa có điểm. Dùng NOT EXISTS:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
Kết quả:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
So sánh EXISTS và IN
Đôi khi bạn nghĩ EXISTS và IN giống nhau. Nhìn sơ thì đúng, nhưng có chi tiết nhỏ. Đặc biệt là khi có NULL xuất hiện. Lúc đó IN có thể hành xử rất "kỳ lạ", còn EXISTS lại cứu nguy.
Xem ví dụ nhé.
Bảng courses (các khóa học):
| course_id | name |
|---|---|
| 1 | Toán học |
| 2 | Lịch sử |
Và đây là sinh viên:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
Bảng enrollments (ai đăng ký khóa nào):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
Bạn muốn chọn tên các khóa học có ai đó đăng ký. Nghe đơn giản nhỉ.
Dùng IN:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
Nhìn qua tưởng sẽ chạy ngon. Nhưng nếu trong enrollments có NULL ở courseid, như Maria Chi, IN có thể trả về... chẳng gì cả! Vì NULL làm subquery "không xác định", SQL bối rối: biết đâu NULL lại chính là courseid mình cần?
Dùng EXISTS:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
Còn EXISTS chỉ kiểm tra: "Có dòng nào course_id trùng không?" — thế là xong. Nó không ngại NULL, vì chỉ tìm đúng giá trị trùng thôi, không cần danh sách giá trị.
Kết luận: nếu subquery có thể có NULL, nên dùng EXISTS để tránh bất ngờ.
Ví dụ thực tế
Bảng students:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
Bảng enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
Ví dụ 1. Sinh viên đã đăng ký khóa học
Tìm những ai đã từng đăng ký khóa học (dù hơi lạ như Maria Chi):
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
Kết quả:
Alex Lin
Anna Song
Maria Chi
Chỉ cần sinh viên xuất hiện trong enrollments — là lọt vào kết quả, kể cả course_id của họ là NULL.
Ví dụ 2. Sinh viên chưa đăng ký khóa học
Giờ tìm những ai chỉ "tồn tại" trong hệ thống — chưa đăng ký gì cả:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
Kết quả:
Dan Seth
Shadow Moon
Có vẻ hai bạn này chưa tìm được khóa học ưng ý. Hoặc quên đăng ký rồi :)
Ví dụ 3. Chọn khóa học có hơn 5 sinh viên đăng ký
Bảng courses:
| course_id | name |
|---|---|
| 1 | Toán học |
| 2 | Lịch sử |
| 3 | Sinh học |
| 4 | Triết học |
Bảng enrollments:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
Bạn muốn tìm các khóa học có hơn năm sinh viên đăng ký. Ở đây EXISTS như hỏi: "Có nhóm nào của khóa này có hơn 5 sinh viên không?"
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
Kết quả:
Toán học
Chỉ có khóa "Toán học" (course_id = 1) có sáu sinh viên đăng ký. Các khóa khác chưa hot lắm.
Lỗi thường gặp khi dùng EXISTS và NOT EXISTS
- Hiểu sai cú pháp subquery. Luôn kiểm tra subquery có tham chiếu đúng bảng ngoài không nhé.
- Bỏ sót kiểm tra
NULL. Dù dùngEXISTS, đôi khi bạn vẫn cần xử lýNULLrõ ràng. - Không có index trên các trường subquery. Điều này có thể làm truy vấn chạy chậm hẳn.
Vậy là hết cho hôm nay! Giờ bạn đã biết cách dùng EXISTS và NOT EXISTS để kiểm tra sự tồn tại dữ liệu, cũng như sự khác biệt giữa chúng với IN. Bài sau tụi mình sẽ tiếp tục khám phá subquery, xem cách dùng trong SELECT để làm việc với dữ liệu tổng hợp nhé.
GO TO FULL VERSION