Bài 5. Hàm tổng hợp, GROUP BY và HAVING

Báo cáo thống kê (điểm trung bình theo lớp, số sinh viên mỗi tỉnh) dựa trên hàm tổng hợp và GROUP BY. Bài này cũng giới thiệu các hàm chuỗi, số, ngày thường dùng trong SELECT.

45 phútTrung cấp
Bạn sẽ học được
  • Dùng COUNT, SUM, AVG, MIN, MAX
  • Nhóm dữ liệu với GROUP BY, lọc nhóm với HAVING
  • Phân biệt WHERE và HAVING
  • Dùng hàm chuỗi, số, ngày trong truy vấn

Khái niệm và lý thuyết

Hàm tổng hợp gộp nhiều dòng thành một giá trị. Không có GROUP BY, cả bảng là một nhóm. Với GROUP BY, mỗi giá trị khác nhau của cột nhóm là một dòng kết quả; mọi cột trong SELECT phải là cột nhóm hoặc nằm trong hàm tổng hợp. WHERE lọc dòng trước khi nhóm, HAVING lọc nhóm sau khi tổng hợp.

Hàm tổng hợp

SELECT COUNT(*) AS so_sv FROM sinhvien;
SELECT COUNT(email) FROM sinhvien;                -- chỉ đếm dòng có email (bỏ NULL)
SELECT COUNT(DISTINCT que_quan) FROM sinhvien;    -- số tỉnh khác nhau
SELECT AVG(diem), MIN(diem), MAX(diem), SUM(diem) FROM diem;
SELECT ROUND(AVG(diem), 2) AS diem_tb FROM diem WHERE monhoc_id = 1;

GROUP BY và HAVING

-- Số sinh viên theo quê quán
SELECT que_quan, COUNT(*) AS so_luong FROM sinhvien GROUP BY que_quan ORDER BY so_luong DESC;
-- Điểm trung bình theo sinh viên
SELECT sinhvien_id, ROUND(AVG(diem), 2) AS diem_tb, COUNT(*) AS so_mon
FROM diem GROUP BY sinhvien_id;
-- Nhóm theo nhiều cột
SELECT lop_id, gioi_tinh, COUNT(*) FROM sinhvien GROUP BY lop_id, gioi_tinh;
-- HAVING: chỉ sinh viên có điểm TB >= 8
SELECT sinhvien_id, AVG(diem) AS diem_tb FROM diem
GROUP BY sinhvien_id HAVING diem_tb >= 8;
-- WHERE lọc dòng trước, HAVING lọc nhóm sau
SELECT monhoc_id, COUNT(*) AS so_sv_dat FROM diem
WHERE diem >= 5 GROUP BY monhoc_id HAVING so_sv_dat >= 2;
-- Đếm theo xếp loại (CASE + GROUP BY)
SELECT CASE WHEN diem >= 8.5 THEN 'Giỏi' WHEN diem >= 7 THEN 'Khá' WHEN diem >= 5 THEN 'TB' ELSE 'Yếu' END AS xl,
       COUNT(*) FROM diem WHERE diem IS NOT NULL GROUP BY xl;

Hàm thường dùng

NhómHàmVí dụ
ChuỗiCONCAT, CONCAT_WS, UPPER, LOWER, LENGTH, CHAR_LENGTH, SUBSTRING, TRIM, REPLACE, LEFT, RIGHTCONCAT(ma_sv, ' - ', ho_ten)
SốROUND, FLOOR, CEIL, ABS, MOD, TRUNCATEROUND(diem, 1)
NgàyNOW, CURDATE, YEAR, MONTH, DAY, DATE_FORMAT, DATEDIFF, DATE_ADD, TIMESTAMPDIFFTIMESTAMPDIFF(YEAR, ngay_sinh, CURDATE())
Điều kiệnIF, IFNULL, COALESCE, NULLIF, CASEIF(diem >= 5, 'Đạt', 'Rớt')
SELECT CONCAT_WS(' ', ma_sv, ho_ten) AS sv, UPPER(que_quan), CHAR_LENGTH(ho_ten) AS so_ky_tu FROM sinhvien;
SELECT ho_ten, DATE_FORMAT(ngay_sinh, '%d/%m/%Y') AS ns, TIMESTAMPDIFF(YEAR, ngay_sinh, CURDATE()) AS tuoi FROM sinhvien;
SELECT SUBSTRING_INDEX(ho_ten, ' ', -1) AS ten, SUBSTRING_INDEX(ho_ten, ' ', 1) AS ho FROM sinhvien;
SELECT ho_ten, IF(gioi_tinh = 'Nữ', 'Chị', 'Anh') AS xung_ho FROM sinhvien;
Mẹo hay

Bí danh đặt ở SELECT dùng được trong GROUP BY, HAVING, ORDER BY của MySQL, giúp truy vấn ngắn hơn.

Lưu ý

Lỗi “is not in GROUP BY clause” xuất hiện khi SELECT cột không thuộc nhóm; thêm cột đó vào GROUP BY hoặc bọc bằng hàm tổng hợp (MAX, ANY_VALUE).

Thực hành
  1. Tính số sinh viên và tỉ lệ nữ của mỗi lớp.
  2. Tìm môn có điểm trung bình thấp nhất.
  3. Liệt kê các tỉnh có từ 2 sinh viên trở lên.
  4. Thống kê số sinh viên sinh theo từng tháng.
Tóm tắt bài học
  • COUNT, SUM, AVG, MIN, MAX bỏ qua NULL.
  • GROUP BY tạo nhóm; cột SELECT phải thuộc nhóm hoặc tổng hợp.
  • WHERE trước nhóm, HAVING sau nhóm.
  • Hàm chuỗi, số, ngày làm giàu kết quả truy vấn.