CodeGym /Các khóa học /SQL SELF /Ví dụ về truy vấn lồng phức tạp: kết hợp EXISTS, IN, HAVI...

Ví dụ về truy vấn lồng phức tạp: kết hợp EXISTS, IN, HAVING

SQL SELF
Mức độ , Bài học
Có sẵn

Chúc mừng nha, tụi mình đã đến đoạn bắt đầu thú vị thật sự rồi! Hôm nay tụi mình sẽ xem cách kết hợp nhiều loại subquery khác nhau để giải quyết các bài toán khó nhằn. EXISTS, IN, HAVING — bộ ba này sẽ giúp bạn cảm thấy mình như phù thủy database luôn đó. Tụi mình sẽ lấy dữ liệu từ một bảng, lọc theo dữ liệu bảng khác, nhóm lại, rồi lại lọc tiếp trên nhóm. Và bonus — sẽ bàn luôn mấy mẹo giúp truy vấn chạy nhanh hơn.

Bắt đầu bằng việc đặt ra một bài toán chung, tụi mình sẽ giải từng bước xuyên suốt bài giảng này nha.

Đặt bài toán

Giả sử tụi mình có database trường đại học với ba bảng:

Bảng students

id name group_id
1 Otto 101
2 Maria 101
3 Alex 102
4 Anna 103

Bảng courses

id name
1 Toán học
2 Lập trình
3 Triết học

Bảng enrollments

student_id course_id grade
1 1 90
1 2 NULL
2 1 85
3 3 70

Cần chọn tất cả sinh viên mà:

  1. Đã đăng ký ít nhất một môn học EXISTS.
  2. Chưa có điểm ở ít nhất một môn đã đăng ký IN.
  3. Thuộc nhóm mà điểm trung bình lớn hơn 80 HAVING.

Giải bằng EXISTSIN

Bước 1: Kiểm tra sinh viên đã đăng ký (EXISTS). Bắt đầu với điều kiện đơn giản nhất. Tụi mình cần biết ai trong số sinh viên đã đăng ký ít nhất một môn. Dùng EXISTS là chuẩn bài.

SELECT name
FROM students s
WHERE EXISTS (
  SELECT 1
  FROM enrollments e
  WHERE e.student_id = s.id
);
  1. Query ngoài chọn tên từ bảng students.
  2. Trong subquery, tụi mình kiểm tra có bản ghi nào trong bảng enrollments khớp với sinh viên ngoài không (WHERE e.student_id = s.id).
  3. SELECT 1 chỉ để báo là có bản ghi là được, không quan tâm nội dung.

Kết quả:

name
Otto
Maria
Alex

Giờ tụi mình đã biết ai đăng ký môn học rồi. Nhưng muốn nhiều hơn nữa. Muốn lọc theo việc chưa có điểm.

Bước 2: Kiểm tra chưa có điểm (IN + NULL). Giờ thêm điều kiện: chỉ lấy sinh viên mà có ít nhất một môn chưa có điểm. Ở đây IN và kiến thức về NULL sẽ giúp tụi mình.

SELECT name
FROM students s
WHERE id IN (
  SELECT e.student_id
  FROM enrollments e
  WHERE e.grade IS NULL
);
  1. Query ngoài chọn tên sinh viên.
  2. Subquery tạo list student_id từ bảng enrollments nơi grade IS NULL.

Kết quả:

name
Otto

Vậy là Otto — sinh viên duy nhất có môn chưa có điểm. Hơi drama nhỉ! Nhưng chưa xong đâu: chỉ lấy nhóm mà điểm trung bình lớn hơn 80 thôi.

Giải bằng HAVING

Bước 3: Nhóm và lọc với HAVING.

Giờ là lúc kết hợp tất cả lại. Tụi mình cần:

  1. Tính điểm trung bình cho từng nhóm.
  2. Lọc nhóm có điểm trung bình lớn hơn 80.
  3. In ra sinh viên thuộc các nhóm này, kèm điều kiện trước đó.
SELECT name
FROM students s
WHERE s.group_id IN (
  SELECT group_id
  FROM students
  JOIN enrollments ON students.id = enrollments.student_id
  WHERE grade IS NOT NULL
  GROUP BY group_id
  HAVING AVG(grade) > 80
)
AND id IN (
  SELECT e.student_id
  FROM enrollments e
  WHERE e.grade IS NULL
);
  1. Query ngoài chọn tên sinh viên thỏa mãn tất cả điều kiện.
  2. Subquery đầu trong WHERE trả về list group_id cho nhóm có điểm trung bình lớn hơn 80.
    • Tụi mình join students với enrollments để lấy điểm.
    • Lọc bản ghi có grade IS NOT NULL.
    • Group theo group_id.
    • Dùng HAVING để lọc nhóm.
  3. Subquery thứ hai trong WHERE kiểm tra sinh viên có ít nhất một môn chưa có điểm.
  4. Hai điều kiện nối với nhau bằng AND.

Kết quả:

name
Otto

Vậy là tụi mình phát hiện Otto — không chỉ là sinh viên duy nhất chưa có điểm, mà còn thuộc nhóm học bá luôn.

So sánh: EXISTS vs IN

EXISTS mạnh nhất khi bạn chỉ muốn kiểm tra có tồn tại bản ghi không. Nó hiệu quả vì dừng ngay khi tìm thấy bản ghi đầu tiên. Đặc biệt quan trọng với bảng lớn.

Trong khi đó IN hữu ích khi bạn cần tập trung vào nội dung dữ liệu. Ví dụ, khi lấy list id để lọc tiếp. Nhưng nhớ là IN có thể chậm nếu subquery trả về nhiều giá trị.

Khi nào dùng HAVING

Với dữ liệu đã tổng hợp, cần lọc theo kết quả tính toán thì HAVING là lựa chọn số 1. Nhưng nếu có thể chuyển điều kiện vào WHERE (ví dụ lọc theo cột), thì query sẽ đơn giản và chạy nhanh hơn.

Ví dụ đầy đủ

Để chắc kèo, tụi mình làm thêm ví dụ nữa: chọn nhóm mà có ít nhất một sinh viên điểm dưới 75, nhưng không đăng ký môn "Triết học".

Nhắc lại, các bảng của tụi mình:

Bảng students

id name group_id
1 Otto 101
2 Maria 101
3 Alex 102
4 Anna 103

Bảng courses

id name
1 Toán học
2 Lập trình
3 Triết học

Bảng enrollments

student_id course_id grade
1 1 90
1 2 NULL
2 1 85
3 3 70
SELECT DISTINCT group_id
FROM students s
WHERE group_id IN (
  SELECT s.group_id
  FROM students s
  JOIN enrollments e ON s.id = e.student_id
  WHERE e.grade < 75
)
AND group_id NOT IN (
  SELECT s.group_id                                 -- subquery cấp 1
  FROM students s
  JOIN enrollments e ON s.id = e.student_id
  WHERE e.course_id = (
    SELECT id FROM courses WHERE name = 'Triết học' -- subquery cấp 2 :P
  )
);
  1. Subquery đầu chọn nhóm có sinh viên điểm dưới 75.
  2. Subquery thứ hai loại nhóm liên quan đến môn "Triết học".
  3. Kết hợp điều kiện qua INNOT IN để ra kết quả cuối.

Kết quả:

group_id
101

Cái này hữu ích cỡ nào?

Trong thực tế, mấy cách này cứu cánh khi phải phân tích mối quan hệ dữ liệu phức tạp. Ví dụ:

  • Trong phân tích để tìm nhóm khách hàng "đặc biệt" (VIP, vấn đề, v.v.).
  • Khi làm hệ thống gợi ý, cần lọc user theo nhiều tiêu chí.
  • Khi phỏng vấn, bị hỏi tối ưu hóa truy vấn SQL phức tạp.

Luyện tập đi nhé! Đó là con đường lên trình của bạn đó.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION