DHDataHọc
TheoryLesson 19/306 min read+10 XP

AVG, MIN, MAX

Bạn sẽ học được

  • Tính trung bình bằng AVG và hiểu nó bỏ qua NULL.
  • Tìm giá trị nhỏ nhất, lớn nhất bằng MIN và MAX trên số, ngày, chuỗi.
  • Biết vì sao AVG khác "tổng chia số dòng" khi có NULL.
  • Dùng MIN/MAX để trả lời câu hỏi "sớm nhất", "muộn nhất", "đắt nhất".
  • Kết hợp ba hàm với WHERE và GROUP BY.

Khái niệm cốt lõi

Ba hàm này thường đi cùng nhau trong báo cáo mô tả dữ liệu:

HàmTrả vềVí dụ
AVG(cot)Trung bình cộngGiá trung bình một sản phẩm
MIN(cot)Giá trị nhỏ nhấtSản phẩm rẻ nhất
MAX(cot)Giá trị lớn nhấtĐơn hàng mới nhất

Cả ba đều bỏ qua NULL, giống SUM. Đây là điểm khiến AVG khác phép chia thủ công:

code
SELECT AVG(price) AS gia_trung_binh,
       SUM(price) / COUNT(*) AS chia_cho_moi_dong,
       SUM(price) / COUNT(price) AS chia_cho_dong_co_gia
FROM products;

Trong bảng products không có giá NULL, nên cả ba bằng nhau: 634.444,44. Nhưng nếu thêm một sản phẩm chưa có giá, COUNT(*) đếm cả dòng đó còn AVG thì không. Khi phân tích dữ liệu thiếu, hãy tự hỏi: "trung bình trên những dòng có dữ liệu, hay trên toàn bộ dòng?" — và viết truy vấn đúng theo câu trả lời.

AVG trên cột số nguyên trả về số thập phân. Cột stock có các giá trị 25, 10, 5, 0, 40, 30, 18, 12, 7, tổng 147:

code
SELECT AVG(stock) AS ton_kho_trung_binh FROM products;

Kết quả: 16,3333.... PostgreSQL trả numeric với nhiều chữ số thập phân. Muốn gọn, dùng ROUND(AVG(stock), 1) → 16,3. Đừng nhầm ROUND là bắt buộc — nó chỉ để trình bày.

MIN và MAX không chỉ dành cho số. Với ngày, chúng cho ngày sớm nhất và muộn nhất:

code
SELECT MIN(order_date) AS don_som_nhat,
       MAX(order_date) AS don_muon_nhat
FROM orders;

Kết quả: 2025-01-05 và 2025-11-11. Đây là cách nhanh nhất để biết phạm vi thời gian của dữ liệu trước khi vẽ biểu đồ. Với chuỗi, MIN/MAX theo collation — hữu ích cho mã, kém tin cậy cho tên người có dấu.

Một điểm dễ nhầm: MIN/MAX không đi kèm dòng của chúng. Truy vấn SELECT name, MAX(price) FROM products không trả tên sản phẩm đắt nhất — nó báo lỗi trong PostgreSQL vì name không nằm trong GROUP BY và không phải hàm tổng hợp. (Các hệ quản trị khác như MySQL cũ có thể trả một giá trị tùy ý; PostgreSQL chặt chẽ hơn.) Muốn biết sản phẩm nào, dùng ORDER BY price DESC LIMIT 1 hoặc truy vấn con:

code
SELECT name, price
FROM products
WHERE price = (SELECT MAX(price) FROM products);

Ở chương này, hãy ghi nhớ: MAX trả con số, không trả cả dòng.

AVG, MIN, MAX đều trả NULL khi không có dòng nào thỏa điều kiện — khác COUNT trả 0. Vì vậy bảng báo cáo có thể có ô trống ở nhóm không có dữ liệu. Đó là hành vi đúng; chỉ dùng COALESCE khi bạn chắc rằng 0 là câu trả lời có nghĩa.

Về hiệu năng, cả ba đều phải quét dữ liệu nếu không có chỉ mục. MIN/MAX trên cột có chỉ mục B-tree rất nhanh vì PostgreSQL đọc hai đầu chỉ mục. AVG luôn phải đọc mọi giá trị. Với bảng lớn, câu hỏi "giá cao nhất là bao nhiêu" rẻ hơn nhiều so với "giá trung bình là bao nhiêu".

Ví dụ chạy được

Mô tả giá sản phẩm trong một dòng:

code
SELECT MIN(price) AS gia_nho_nhat,
       MAX(price) AS gia_lon_nhat,
       AVG(price) AS gia_trung_binh,
       COUNT(*) AS so_san_pham
FROM products;

Kết quả: 180000 | 1690000 | 634444.44 | 9. Câu này là mẫu "tóm tắt một cột số" mà bạn sẽ dùng lại nhiều lần. Nó trả lời cùng lúc: khoảng giá, giá trị điển hình, và quy mô dữ liệu.

Giá trung bình chỉ tính nhóm Điện tử:

code
SELECT AVG(price) AS gia_tb_dien_tu
FROM products
WHERE category = 'Điện tử';

Kết quả: 863.333,33. Ba sản phẩm: 890.000, 1.250.000, 450.000 — tổng 2.590.000 chia 3. So với trung bình toàn bảng 634.444,44, nhóm Điện tử đắt hơn rõ rệt. Đây là cách so sánh nhóm bằng một con số, trước khi học GROUP BY.

Trung bình giá trị một đơn hàng:

code
SELECT AVG(quantity * unit_price) AS gia_tri_tb_mot_dong
FROM order_items;

Kết quả: 1.076.896,55. Đây là trung bình của từng dòng hàng, không phải từng đơn. Muốn trung bình theo đơn phải gom nhóm trước — nội dung bài sau. Sự khác biệt này quan trọng: một đơn có ba món góp ba giá trị vào phép trung bình, không phải một.

Ngày đăng ký sớm nhất và muộn nhất:

code
SELECT MIN(signup_date) AS khach_som_nhat,
       MAX(signup_date) AS khach_muon_nhat
FROM customers;

Kết quả: 2023-03-12 (An Nguyễn) và 2025-06-30 (Nga Lý). Dữ liệu trải hơn hai năm. Khi làm báo cáo "khách mới trong tháng", bạn sẽ dùng MAX để kiểm tra ngày gần nhất có dữ liệu hay không.

MIN/MAX theo nhóm:

code
SELECT category,
       MIN(price) AS re_nhat,
       MAX(price) AS dat_nhat
FROM products
GROUP BY category
ORDER BY category;

Bốn nhóm: Điện tử 450.000–1.250.000; Gia dụng 290.000–1.690.000; Sách 180.000–180.000; Thời trang 250.000–250.000. Nhóm có một sản phẩm thì MIN bằng MAX — dấu hiệu nhóm quá nhỏ để so sánh, đáng chú ý khi đọc báo cáo.

Kết hợp AVG với điều kiện số:

code
SELECT AVG(quantity) AS so_luong_tb
FROM order_items
WHERE unit_price > 1000000;

Chỉ còn các dòng có đơn giá trên một triệu: Bàn phím (1.250.000) và Nồi chiên (1.690.000). Các dòng tương ứng có số lượng 1, 2, 1, 1, 1, 1, 2, 1, 1... — kết quả khoảng 1,3. Điều đáng chú ý không phải con số mà là phạm vi: WHERE lọc dòng trước, AVG tính trên phần còn lại.

Thực hành

  1. Tìm giá thấp nhất, cao nhất và trung bình của sản phẩm còn hàng (stock > 0). So với kết quả toàn bảng, giải thích chênh lệch.
  2. Tính trung bình số lượng trên mỗi dòng hàng (AVG(quantity)). Kết quả khoảng 1,86 — nghĩa là phần lớn dòng đặt một hoặc hai sản phẩm.
  3. Tìm ngày đặt hàng đầu tiên và cuối cùng của khách 2 (Bình Trần). Kết quả: 18/01/2025 và 04/07/2025.
  4. Viết truy vấn trả về cả MIN(price), MAX(price), AVG(price) và độ lệch MAX(price) - MIN(price). Khoảng giá là 1.510.000.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
column "name" must appear in the GROUP BY clauseChọn cột không tổng hợp cùng MAXDùng ORDER BY ... LIMIT 1 hoặc truy vấn con
AVG khác "tổng chia số dòng"Có NULL trong cộtChọn đúng mẫu số, hoặc COALESCE dữ liệu nguồn
Kết quả NULLKhông có dòng thỏa WHEREKiểm tra điều kiện, dùng COALESCE nếu cần
Trung bình bị lệch bởi một giá trị lớnPhân bố lệch, có ngoại laiDùng thêm MIN/MAX hoặc trung vị để đọc đúng
MIN/MAX trên chuỗi ra kết quả lạCollation tiếng ViệtKiểm tra collation hoặc chuẩn hóa dữ liệu

Lỗi đầu tiên gặp rất thường khi mới học: muốn "sản phẩm đắt nhất" nên viết SELECT name, MAX(price). Hãy đọc lại: MAX trả một con số cho toàn nhóm; tên sản phẩm là thông tin của một dòng cụ thể. Muốn cả hai, phải chỉ rõ dòng đó bằng ORDER BY + LIMIT hoặc so sánh với MAX.

Tóm tắt

  • AVG, MIN, MAX bỏ qua NULL và trả NULL khi không có dòng nào.
  • AVG = tổng chia số giá trị khác NULL, không phải chia số dòng.
  • MIN/MAX dùng được cho số, ngày và chuỗi; trả giá trị, không trả cả dòng.
  • Kèm WHERE để tính trong một phạm vi; kèm GROUP BY để tính theo nhóm.
  • AVG nhạy với ngoại lai — luôn đọc kèm MIN/MAX khi mô tả dữ liệu.

Use ← → to move between lessons