Skip to content

Ngày 9: Nhóm dữ liệu với GROUP BY và HAVING ​

Tóm tắt: Ngày 8 bạn đã tính một con số cho cả bảng (tổng số đơn, tổng tiền). Hôm nay bạn chia các dòng thành từng nhóm bằng GROUP BY để có một con số cho mỗi nhóm (số đơn của mỗi khách, số đơn mỗi tháng), rồi lọc các nhóm bằng HAVING. Cuối bài là thứ tự thực thi logic đầy đủ của câu SELECT, chìa khóa để hiểu vì sao WHERE và HAVING khác nhau.

Sau bài này bạn có thể:

  • Viết truy vấn GROUP BY theo một cột, nhiều cột hoặc một biểu thức (năm, tháng) để làm báo cáo tổng hợp.
  • Giải thích được quy tắc "cột trong SELECT phải nằm trong GROUP BY hoặc nằm trong hàm tổng hợp" và sửa được lỗi tương ứng.
  • Chọn đúng giữa WHERE (lọc dòng trước khi nhóm) và HAVING (lọc nhóm sau khi nhóm).
  • Sắp xếp được các mệnh đề FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, LIMIT theo thứ tự database xử lý.

Cần biết trước: Ngày 3 (SELECT, WHERE, NULL), Ngày 4 (ORDER BY, LIMIT), Ngày 5 (EXTRACT, DATE_TRUNC, ROUND), Ngày 8 (COUNT, SUM, AVG, MIN, MAX).

Mọi ví dụ chạy trên bộ dữ liệu DSC Shop. Nếu chưa dựng môi trường, xem setup/README.md.

1. Từ một con số đến một con số cho mỗi nhóm ​

Ở Ngày 8, hàm tổng hợp gom cả bảng thành một dòng:

sql
SELECT COUNT(*) AS so_don
FROM orders;
so_don
40

Nhưng người quản lý cửa hàng thường hỏi chi tiết hơn: "trong 40 đơn đó, bao nhiêu đơn đã giao, bao nhiêu đơn bị hủy?". Bạn cần một con số cho mỗi trạng thái. Đó là việc của GROUP BY.

Hình dung thế này: database đọc từng đơn hàng và bỏ vào các "xô" theo giá trị cột status. Đơn delivered vào xô delivered, đơn cancelled vào xô cancelled. Sau đó hàm tổng hợp chạy riêng trên từng xô, và mỗi xô cho ra đúng một dòng kết quả.

sql
SELECT status, COUNT(*) AS so_don
FROM orders
GROUP BY status
ORDER BY so_don DESC;
statusso_don
delivered33
cancelled3
paid2
pending1
shipped1

Cú pháp chung:

text
SELECT cot_nhom, HAM_TONG_HOP(cot_khac)
FROM ten_bang
GROUP BY cot_nhom;

Một ví dụ khác: đếm số đơn của mỗi khách hàng.

sql
SELECT customer_id, COUNT(*) AS so_don
FROM orders
GROUP BY customer_id
ORDER BY so_don DESC, customer_id;
customer_idso_don
15
24
33
43
63
52
72
......

(... tổng 16 dòng)

Có 18 khách nhưng chỉ 16 nhóm: khách 16 và 17 chưa có đơn nào, nên trong bảng orders không có dòng nào để tạo nhóm cho họ. Muốn hiện cả khách chưa mua với số đơn bằng 0, bạn cần ghép bảng customers với orders, sẽ học ở Ngày 10.

2. Quy tắc vàng: cột trong SELECT phải "đại diện" được cho cả nhóm ​

Sau khi nhóm, mỗi nhóm chỉ còn một dòng. Vậy mỗi cột trong SELECT phải cho ra đúng một giá trị cho nhóm đó. Chỉ có hai loại cột làm được việc này:

  1. Cột có trong GROUP BY (cả nhóm có chung một giá trị).
  2. Hàm tổng hợp (COUNT, SUM, ...) gom nhiều giá trị thành một.

Thử chọn thêm order_date khi nhóm theo customer_id:

sql
SELECT customer_id, order_date, COUNT(*)
FROM orders
GROUP BY customer_id;

PostgreSQL báo lỗi:

text
ERROR:  column "orders.order_date" must appear in the GROUP BY clause or be used in an aggregate function

Lý do: khách 1 có 5 đơn với 5 ngày khác nhau. Database không biết bạn muốn ngày nào. Nếu bạn muốn ngày đơn đầu tiên và đơn gần nhất, hãy nói rõ bằng hàm tổng hợp:

sql
SELECT customer_id,
       COUNT(*)               AS so_don,
       MIN(order_date)::date  AS don_dau_tien,
       MAX(order_date)::date  AS don_gan_nhat
FROM orders
GROUP BY customer_id
ORDER BY customer_id
LIMIT 3;
customer_idso_dondon_dau_tiendon_gan_nhat
152025-01-122026-09-02
242025-01-202026-06-15
332025-02-032026-10-14

Ghi chú dialect: MySQL khi tắt chế độ ONLY_FULL_GROUP_BY (mặc định đang bật từ MySQL 5.7) và SQLite không báo lỗi câu trên mà trả về một order_date bất kỳ trong nhóm. Kết quả đó không đáng tin, đừng dựa vào nó. PostgreSQL và SQL Server luôn báo lỗi.

3. GROUP BY nhiều cột ​

Liệt kê nhiều cột trong GROUP BY thì mỗi tổ hợp giá trị khác nhau là một nhóm. Ví dụ: số đơn theo kênh bán và trạng thái.

sql
SELECT channel, status, COUNT(*) AS so_don
FROM orders
GROUP BY channel, status
ORDER BY channel, status;
channelstatusso_don
offlinedelivered15
onlinecancelled3
onlinedelivered18
onlinepaid2
onlinepending1
onlineshipped1

Chỉ những tổ hợp thật sự xuất hiện trong dữ liệu mới thành nhóm. Không có đơn offline nào bị hủy nên không có dòng offline | cancelled.

4. GROUP BY theo biểu thức ​

Bạn không bắt buộc nhóm theo một cột có sẵn. Nhóm theo một biểu thức cũng được, rất hay dùng với ngày tháng (đã học EXTRACT, DATE_TRUNC ở Ngày 5).

Số đơn mỗi năm:

sql
SELECT EXTRACT(YEAR FROM order_date) AS nam,
       COUNT(*) AS so_don
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date)
ORDER BY nam;
namso_don
202525
202615

Tổng tiền của từng đơn hàng: bảng orders không có cột tổng tiền, tổng tiền luôn tính từ order_items bằng quantity * unit_price * (1 - discount). Nhóm theo order_id là ra tổng tiền mỗi đơn.

sql
SELECT order_id,
       COUNT(*) AS so_dong_hang,
       ROUND(SUM(quantity * unit_price * (1 - discount))) AS tong_tien
FROM order_items
GROUP BY order_id
ORDER BY order_id
LIMIT 5;
order_idso_dong_hangtong_tien
12699000
2126990000
325780000
427881000
5132990000

ROUND ở đây để bỏ phần .00 sinh ra do discount có 2 chữ số thập phân.

5. NULL tạo thành một nhóm riêng ​

Ở Ngày 3 bạn biết NULL = NULL không cho ra TRUE. Riêng với GROUP BY, mọi giá trị NULL được gom chung vào một nhóm. Xem số khách theo thành phố:

sql
SELECT city,
       COUNT(*)     AS so_khach,
       COUNT(phone) AS co_sdt
FROM customers
GROUP BY city
ORDER BY so_khach DESC, city;
cityso_khachco_sdt
Hà Nội42
TP. Hồ Chí Minh33
Cần Thơ22
Đà Nẵng21
NULL21
Biên Hòa11
đà nẵng11
Hải Phòng11
Hồ Chí Minh11
TP.HCM11

Hai điều rút ra:

  • Hai khách không có city nằm chung nhóm NULL.
  • GROUP BY so sánh chính xác từng ký tự. TP. Hồ Chí Minh, Hồ Chí Minh, TP.HCM là ba nhóm khác nhau, Đà Nẵng và đà nẵng cũng vậy. Dữ liệu bẩn làm báo cáo sai, nên nhóm theo biểu thức đã làm sạch (bạn đã luyện ở Ngày 5) trước khi tin vào con số.

Cột co_sdt nhắc lại bài Ngày 8: COUNT(*) đếm mọi dòng của nhóm, còn COUNT(phone) bỏ qua dòng có phone là NULL.

6. HAVING: lọc nhóm sau khi nhóm ​

Câu hỏi: "khách nào đã đặt từ 3 đơn trở lên?". Điều kiện COUNT(*) >= 3 nói về cả nhóm, không nói về một dòng đơn lẻ. Thử viết vào WHERE:

sql
SELECT customer_id, COUNT(*) AS so_don
FROM orders
WHERE COUNT(*) >= 3
GROUP BY customer_id;
text
ERROR:  aggregate functions are not allowed in WHERE

Điều kiện trên nhóm phải đặt trong HAVING, viết sau GROUP BY:

sql
SELECT customer_id, COUNT(*) AS so_don
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 3
ORDER BY so_don DESC, customer_id;
customer_idso_don
15
24
33
43
63

HAVING cũng dùng được với các hàm tổng hợp khác. Các đơn có tổng tiền trên 30 triệu:

sql
SELECT order_id,
       ROUND(SUM(quantity * unit_price * (1 - discount))) AS tong_tien
FROM order_items
GROUP BY order_id
HAVING SUM(quantity * unit_price * (1 - discount)) > 30000000
ORDER BY tong_tien DESC;
order_idtong_tien
3939980000
532990000
1231880000
2731340500

7. WHERE và HAVING dùng cùng nhau ​

WHERE và HAVING không loại trừ nhau. Một câu truy vấn thường có cả hai: WHERE bỏ các dòng không cần trước khi nhóm, HAVING bỏ các nhóm không đạt sau khi nhóm.

Câu hỏi: "khách nào có từ 3 đơn trở lên, không tính đơn đã hủy?"

sql
SELECT customer_id, COUNT(*) AS so_don
FROM orders
WHERE status <> 'cancelled'
GROUP BY customer_id
HAVING COUNT(*) >= 3
ORDER BY so_don DESC, customer_id;
customer_idso_don
15
24
33
63

Khách 4 biến mất: khách này có 3 đơn nhưng đơn 5 bị hủy. WHERE bỏ đơn 5 trước khi đếm (40 đơn còn 37), nên nhóm của khách 4 chỉ còn 2 đơn và bị HAVING loại.

WHEREHAVING
Lọc cái gìTừng dòngTừng nhóm
Chạy lúc nàoTrước khi nhómSau khi nhóm
Dùng được hàm tổng hợp (COUNT, SUM, ...)KhôngCó
Dùng chung với GROUP BYCóCó
Không có GROUP BY thì saoLọc dòng như bình thườngCả bảng được coi là một nhóm

Quy tắc thực hành: điều kiện trên cột thường (status, order_date, channel) đặt ở WHERE. Chỉ điều kiện trên hàm tổng hợp mới đặt ở HAVING. Viết HAVING status <> 'cancelled' sẽ lỗi nếu status không nằm trong GROUP BY; và kể cả khi chạy được, lọc sớm bằng WHERE giúp database phải nhóm ít dòng hơn.

8. Thứ tự thực thi logic đầy đủ ​

Ở Ngày 3 bạn đã biết database xử lý FROM → WHERE → SELECT. Giờ có thêm nhóm, thứ tự đầy đủ là:

BướcMệnh đềLàm gì
1FROM (và JOIN, Ngày 10)Lấy bảng nguồn
2WHERELọc từng dòng
3GROUP BYGom dòng thành nhóm
4HAVINGLọc từng nhóm
5SELECTTính các cột, đặt tên (alias)
6DISTINCTBỏ dòng trùng
7ORDER BYSắp xếp
8LIMIT / OFFSETCắt lấy một phần

Đây là thứ tự logic, tức là kết quả luôn giống như khi chạy theo thứ tự này. Bên trong, database có thể làm khác đi để chạy nhanh hơn (bạn sẽ thấy ở Ngày 20).

Sơ đồ dưới đây đi theo truy vấn ở mục 7:

Thứ tự thực thi logic của câu SELECT có GROUP BY, minh họa bằng truy vấn đếm đơn mỗi khách trên bảng orders. Câu lệnh viết theo thứ tự SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, nhưng database xử lý: 1 FROM lấy bảng orders, 40 dòng; 2 WHERE status khác 'cancelled' lọc từng dòng, còn 37 dòng; hai bước này chạy trước khi nhóm. Sau đó 3 GROUP BY customer_id gom thành 16 nhóm; 4 HAVING COUNT() >= 3 lọc từng nhóm, còn 4 nhóm; 5 SELECT tính COUNT() và đặt tên so_don; 6 ORDER BY so_don DESC sắp xếp. WHERE chạy khi chưa có nhóm nên không lọc được SUM(), COUNT(); HAVING chạy sau GROUP BY nên lọc được theo hàm tổng hợp.Thứ tự thực thi logic của câu SELECT có GROUP BY, minh họa bằng truy vấn đếm đơn mỗi khách trên bảng orders. Câu lệnh viết theo thứ tự SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, nhưng database xử lý: 1 FROM lấy bảng orders, 40 dòng; 2 WHERE status khác 'cancelled' lọc từng dòng, còn 37 dòng; hai bước này chạy trước khi nhóm. Sau đó 3 GROUP BY customer_id gom thành 16 nhóm; 4 HAVING COUNT() >= 3 lọc từng nhóm, còn 4 nhóm; 5 SELECT tính COUNT() và đặt tên so_don; 6 ORDER BY so_don DESC sắp xếp. WHERE chạy khi chưa có nhóm nên không lọc được SUM(), COUNT(); HAVING chạy sau GROUP BY nên lọc được theo hàm tổng hợp.

File gốc: nền sáng · nền tối

Thứ tự này giải thích luôn chuyện dùng alias (tên đặt bằng AS) ở đâu được:

  • ORDER BY so_don chạy được, vì ORDER BY (bước 7) chạy sau SELECT (bước 5), lúc đó tên so_don đã có.
  • HAVING so_don >= 3 thì lỗi, vì HAVING (bước 4) chạy trước khi SELECT đặt tên:
sql
SELECT customer_id, COUNT(*) AS so_don
FROM orders
GROUP BY customer_id
HAVING so_don >= 3;
text
ERROR:  column "so_don" does not exist

Cách đúng là lặp lại biểu thức: HAVING COUNT(*) >= 3.

Ghi chú dialect: PostgreSQL và MySQL cho phép viết alias trong GROUP BY (ví dụ GROUP BY nam thay cho GROUP BY EXTRACT(YEAR FROM order_date)), đây là phần mở rộng ngoài chuẩn SQL. SQL Server không cho, bạn phải lặp lại biểu thức. Lặp lại biểu thức là cách chạy được ở mọi nơi. MySQL còn cho dùng alias trong HAVING, PostgreSQL và SQL Server thì không.

9. Nhắc qua: dòng tổng cộng với ROLLUP ​

Báo cáo thường cần thêm một dòng "tổng cộng" ở cuối. GROUP BY ROLLUP (...) làm việc đó:

sql
SELECT channel, COUNT(*) AS so_don
FROM orders
GROUP BY ROLLUP (channel)
ORDER BY channel;
channelso_don
offline15
online25
NULL40

Dòng có channel là NULL chính là dòng tổng của mọi nhóm. Bạn chỉ cần biết ROLLUP tồn tại; các báo cáo nhiều cấp (ROLLUP, CUBE, GROUPING SETS) nằm ngoài phạm vi khóa này.

Lỗi thường gặp ​

1. Chọn cột không nằm trong GROUP BY. Đã gặp ở mục 2. Hỏi lại bản thân: "mỗi nhóm có đúng một giá trị cho cột này không?". Nếu không, đưa cột đó vào GROUP BY hoặc bọc trong hàm tổng hợp.

2. Dùng hàm tổng hợp trong WHERE. WHERE COUNT(*) >= 3 lỗi aggregate functions are not allowed in WHERE. Chuyển điều kiện sang HAVING.

3. Nhóm theo tên thay vì theo khóa. DSC Shop có hai khách khác nhau cùng tên Nguyễn Văn An (id 1 và 15):

sql
SELECT full_name, COUNT(*) AS so_khach
FROM customers
GROUP BY full_name
HAVING COUNT(*) > 1;
full_nameso_khach
Nguyễn Văn An2

Nếu bạn nhóm đơn hàng theo tên khách, đơn của hai người này bị gộp làm một. Luôn nhóm theo khóa chính (customer_id), tên chỉ để hiển thị.

4. Tưởng GROUP BY tự sắp xếp. Có lúc kết quả nhìn như đã sắp xếp, nhưng đó là tình cờ. Chạy câu này nhiều lần hoặc trên máy khác, thứ tự có thể khác:

sql
SELECT status, COUNT(*) AS so_don
FROM orders
GROUP BY status;
statusso_don
delivered33
cancelled3
pending1
paid2
shipped1

Muốn có thứ tự, luôn viết ORDER BY.

5. Dùng alias trong HAVING. Đã gặp ở mục 8: HAVING so_don >= 3 lỗi trong PostgreSQL. Viết lại biểu thức HAVING COUNT(*) >= 3.

6. Đặt điều kiện trên cột thường vào HAVING. Lọc status, order_date ở WHERE để database nhóm ít dòng hơn và câu lệnh dễ đọc hơn. HAVING chỉ dành cho điều kiện trên nhóm.

Bài tập ​

Cơ bản ​

Bài 1. Đếm số sản phẩm trong mỗi danh mục (category_id), kèm giá thấp nhất và cao nhất của danh mục đó. Sắp xếp theo category_id.

Đáp án
sql
SELECT category_id,
       COUNT(*)   AS so_san_pham,
       MIN(price) AS gia_thap_nhat,
       MAX(price) AS gia_cao_nhat
FROM products
GROUP BY category_id
ORDER BY category_id;

Kết quả:

category_idso_san_phamgia_thap_nhatgia_cao_nhat
332199000032990000
431699000024990000
5342900009990000
7469000019990000
82949000016990000
1042500002490000
11326900007990000
122150000890000

Danh mục 1, 2, 6, 9 là danh mục cha, 13 chưa có sản phẩm, nên không xuất hiện.

Bài 2. Đếm số đơn theo từng năm và từng kênh bán (channel).

Đáp án
sql
SELECT EXTRACT(YEAR FROM order_date) AS nam,
       channel,
       COUNT(*) AS so_don
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date), channel
ORDER BY nam, channel;

Kết quả:

namchannelso_don
2025offline10
2025online15
2026offline5
2026online10

Bài 3. Với mỗi phương thức thanh toán (payments.method), cho biết số lần thanh toán và tổng tiền. Sắp xếp theo tổng tiền giảm dần.

Đáp án
sql
SELECT method,
       COUNT(*)    AS so_lan,
       SUM(amount) AS tong_tien
FROM payments
GROUP BY method
ORDER BY tong_tien DESC;

Kết quả:

methodso_lantong_tien
bank_transfer8166691500
momo14157951000
card7112317500
cash899331000

Vận dụng ​

Bài 4. Tìm 5 sản phẩm (product_id) có giá trị đặt hàng cao nhất trong order_items (tổng quantity * unit_price * (1 - discount)), kèm tổng số lượng đã đặt. Làm tròn đến đồng.

Lưu ý: đây chưa phải doanh thu. Trong khóa này, doanh thu chỉ tính các đơn có status NOT IN ('pending', 'cancelled') (đơn đã thanh toán, khớp với bảng payments). Muốn lọc theo status phải ghép order_items với orders, việc của Ngày 10. Bài này tính mọi đơn, kể cả đơn chờ xử lý và đơn hủy.

Đáp án
sql
SELECT product_id,
       SUM(quantity) AS so_luong,
       ROUND(SUM(quantity * unit_price * (1 - discount))) AS gia_tri_dat
FROM order_items
GROUP BY product_id
ORDER BY gia_tri_dat DESC
LIMIT 5;

Kết quả:

product_idso_luonggia_tri_dat
10478960500
1264330500
2259980000
5250730500
3243830500

Bài 5. Chỉ giữ các phương thức thanh toán có tổng tiền trên 100 triệu.

Đáp án
sql
SELECT method,
       COUNT(*)    AS so_lan,
       SUM(amount) AS tong_tien
FROM payments
GROUP BY method
HAVING SUM(amount) > 100000000
ORDER BY tong_tien DESC;

Kết quả:

methodso_lantong_tien
bank_transfer8166691500
momo14157951000
card7112317500

cash có tổng 99.331.000 nên bị loại.

Bài 6. Với các đơn offline, mỗi nhân viên (employee_id) bán được bao nhiêu đơn? Sắp xếp giảm dần theo số đơn.

Đáp án
sql
SELECT employee_id, COUNT(*) AS so_don
FROM orders
WHERE channel = 'offline'
GROUP BY employee_id
ORDER BY so_don DESC, employee_id;

Kết quả:

employee_idso_don
45
55
64
81

channel = 'offline' là điều kiện trên từng dòng nên đặt ở WHERE.

Thử thách ​

Bài 7. Trong năm 2025, tháng nào có từ 3 đơn trở lên (không tính đơn đã hủy)? Hiển thị tháng dạng ngày đầu tháng (ví dụ 2025-09-01).

Đáp án
sql
SELECT DATE_TRUNC('month', order_date)::date AS thang,
       COUNT(*) AS so_don
FROM orders
WHERE status <> 'cancelled'
  AND order_date >= '2025-01-01'
  AND order_date <  '2026-01-01'
GROUP BY DATE_TRUNC('month', order_date)
HAVING COUNT(*) >= 3
ORDER BY thang;

Kết quả:

thangso_don
2025-09-013
2025-11-013

Lọc năm bằng khoảng >= '2025-01-01' AND < '2026-01-01' thay vì EXTRACT(YEAR ...) = 2025: kết quả như nhau, nhưng cách so sánh trực tiếp trên cột cho phép database dùng index (Ngày 15, 28).

Bài 8. Tìm các sản phẩm xuất hiện trong ít nhất 3 đơn hàng khác nhau. Hiển thị product_id, số đơn và tổng số lượng đã bán, sắp xếp theo số đơn giảm dần.

Đáp án
sql
SELECT product_id,
       COUNT(DISTINCT order_id) AS so_don,
       SUM(quantity)            AS so_luong
FROM order_items
GROUP BY product_id
HAVING COUNT(DISTINCT order_id) >= 3
ORDER BY so_don DESC, product_id;

Kết quả:

product_idso_donso_luong
23613
1656
1245
1744
2246
1034
1833
1933
2033

Khóa chính của order_items là (order_id, product_id), nên trong một nhóm product_id mỗi order_id chỉ xuất hiện một lần, COUNT(*) cũng cho cùng kết quả. Viết COUNT(DISTINCT order_id) thì ý nghĩa "số đơn khác nhau" rõ ràng hơn và vẫn đúng nếu sau này schema thay đổi.

Bài 9. "Có bao nhiêu khách đã đặt từ 3 đơn trở lên?" Câu trả lời là một con số. Thử nghĩ xem vì sao chỉ dùng GROUP BY và HAVING thì không ra được một con số.

Đáp án

GROUP BY customer_id HAVING COUNT(*) >= 3 cho ra 5 dòng (mỗi khách một dòng, xem mục 6). Muốn đếm số dòng đó, bạn phải tổng hợp thêm một lần nữa trên kết quả của truy vấn trước. Việc này cần truy vấn con trong FROM, học ở Ngày 11:

sql
SELECT COUNT(*) AS so_khach
FROM (
    SELECT customer_id
    FROM orders
    GROUP BY customer_id
    HAVING COUNT(*) >= 3
) AS khach_than_thiet;

Kết quả:

so_khach
5

Tóm tắt ​

  • GROUP BY chia các dòng thành nhóm theo giá trị của một hoặc nhiều cột (hoặc biểu thức); hàm tổng hợp chạy riêng trên từng nhóm, mỗi nhóm ra một dòng.
  • Cột trong SELECT phải nằm trong GROUP BY hoặc nằm trong hàm tổng hợp.
  • NULL gom chung thành một nhóm; giá trị viết khác nhau (Đà Nẵng, đà nẵng) là các nhóm khác nhau.
  • WHERE lọc dòng trước khi nhóm, HAVING lọc nhóm sau khi nhóm. Hai mệnh đề thường đi cùng nhau.
  • Thứ tự logic: FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT. Vì vậy alias dùng được trong ORDER BY nhưng không dùng được trong WHERE, HAVING.
  • GROUP BY không đảm bảo thứ tự; cần thứ tự thì viết ORDER BY.
  • Nhóm theo khóa chính, không nhóm theo tên.

Đọc thêm ​


⬅️ Ngày 8: Hàm tổng hợp (COUNT, SUM, AVG, MIN, MAX) | 📚 Mục lục | Ngày 10: Kết hợp nhiều bảng với JOIN ➡️

Phát hành theo giấy phép MIT.