GROUP BY trong PostgreSQL
Xem thửTình huống mở đầu
Chị quản lý hỏi: "Tháng này bán được bao nhiêu đơn, doanh thu bao nhiêu, và theo từng tháng thì sao?" Bảng orders có 8 đơn. Bạn không thể đọc từng dòng rồi cộng tay, vì tháng sau sẽ là 8 nghìn dòng. Bạn cần một câu lệnh biến nhiều dòng thành vài dòng thống kê.
Hãy đoán trước: câu lệnh nào gom 8 đơn thành 3 dòng theo tháng, và nếu chị hỏi thêm "cột nào chỉ tên khách trong dòng thống kê đó?" thì câu trả lời là gì?
Ôn nhanh bài trước: WHERE và HAVING khác nhau ở điểm nào?
Đáp án: WHERE lọc từng dòng trước khi gom, HAVING lọc nhóm sau khi gom. Bài này dạy chính bước gom đó.
Mục tiêu bài học
Sau bài này bạn có thể:
- Viết
GROUP BYvới một hoặc nhiều khóa, theo cột hoặc theo biểu thức như tháng, kèmcount,sum,avg,min,max. - Giải thích vì sao mọi cột trong
SELECTphải là khóa nhóm hoặc nằm trong hàm tổng hợp. - Dự đoán sự khác nhau giữa
count(*),count(cột)vàcount(DISTINCT cột)khi có NULL. - Nhận ra các lỗi quen thuộc: cột thiếu trong
GROUP BY, hàm tổng hợp trongGROUP BY, vị trí cột sai.
Bức tranh lớn
Xuyên suốt bài, mỗi dòng của bảng là một tấm thẻ, và GROUP BY là việc chia thẻ thành các xấp theo khóa. Mỗi xấp cho ra đúng một dòng kết quả. Dự án xuyên suốt là cửa hàng văn phòng phẩm shop với bảng products và orders.
Hình dưới trả lời một câu hỏi: sáu dòng sản phẩm trở thành ba dòng thống kê qua những chặng nào?
Đang tải sơ đồ…
Cách đọc hình: đi từ trên xuống. Ô cyan là dữ liệu gốc. Ô xanh dương là bước gom theo khóa. Hai ô tím là các nhóm và hàm tổng hợp chạy trong từng nhóm. Ô xanh lá là kết quả: mỗi nhóm một dòng.
Chỗ ẩn dụ không còn đúng: xấp thẻ thật vẫn cho bạn xem từng thẻ. Sau GROUP BY, từng dòng riêng biệt biến mất, chỉ còn số liệu của cả nhóm.
Khái niệm cốt lõi
Bài này có ba khái niệm.
1. Khóa nhóm và hàm tổng hợp
GROUP BY chia dòng thành các nhóm có cùng giá trị ở cột hoặc biểu thức khóa. Hàm tổng hợp như count, sum, avg, min, max tính một con số cho cả nhóm. Kết quả có đúng một dòng cho mỗi nhóm.
2. Cột trong SELECT phải là khóa hoặc được tổng hợp
Mỗi nhóm có nhiều dòng với nhiều giá trị khác nhau ở các cột không phải khóa. PostgreSQL không đoán bạn muốn giá trị nào, nên bắt buộc mọi cột thường trong SELECT phải nằm trong GROUP BY, hoặc nằm trong một hàm tổng hợp.
3. NULL và các cách đếm
count(*) đếm số dòng. count(cột) chỉ đếm dòng có cột khác NULL. count(DISTINCT cột) đếm số giá trị khác nhau. Các hàm sum, avg, min, max bỏ qua NULL. Riêng NULL ở cột khóa vẫn tạo ra một nhóm riêng.
Ví dụ đầu tiên
Ví dụ nhỏ nhất: đếm đơn và cộng doanh thu theo trạng thái.
SELECT status,
count(*) AS orders_count,
sum(total) AS revenue
FROM orders
GROUP BY status
ORDER BY status;
Dự đoán trước khi xem kết quả: có mấy dòng, và đơn nào chiếm nhiều nhất?
Kết quả [Đã chạy: PostgreSQL 16.15]:
status | orders_count | revenue
-----------+--------------+---------
cancelled | 1 | 30.00
paid | 6 | 560.49
pending | 1 | 60.00
(3 rows)
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
SELECT status, count(*), sum(total) | Chọn cột khóa và hai số liệu của nhóm | Cột status hợp lệ vì nó nằm trong GROUP BY |
GROUP BY status | Gom các đơn có cùng trạng thái thành một nhóm | Mỗi trạng thái cho một dòng kết quả |
ORDER BY status | Sắp xếp kết quả theo trạng thái | GROUP BY không hứa thứ tự nào |
Gom theo biểu thức: doanh thu theo tháng. date_trunc('month', ...) đưa mỗi thời điểm về ngày đầu tháng, nên các đơn cùng tháng có cùng khóa.
SELECT date_trunc('month', created_at)::date AS month,
count(*) AS orders_count,
sum(total) AS revenue
FROM orders
GROUP BY 1
ORDER BY 1;
Kết quả [Đã chạy: PostgreSQL 16.15, múi giờ phiên UTC, ranh giới tháng tính theo múi giờ này]:
month | orders_count | revenue
------------+--------------+---------
2026-01-01 | 3 | 230.00
2026-02-01 | 4 | 320.50
2026-03-01 | 1 | 99.99
(3 rows)
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
date_trunc('month', created_at)::date AS month | Cắt thời điểm về đầu tháng rồi đổi sang kiểu ngày | Biểu thức này là khóa nhóm |
GROUP BY 1 | Gom theo cột đầu tiên của danh sách SELECT | Tránh phải lặp lại cả biểu thức dài |
Gom theo hai khóa: mỗi cặp khách và trạng thái là một nhóm riêng.
SELECT user_id, status, count(*) AS orders_count
FROM orders
GROUP BY user_id, status
ORDER BY user_id, status;
user_id | status | orders_count
---------+-----------+--------------
1 | cancelled | 1
1 | paid | 1
2 | paid | 3
3 | paid | 1
3 | pending | 1
5 | paid | 1
(6 rows)
Khách 3 có hai dòng vì họ có đơn paid và đơn pending. Cuối cùng, hãy xem NULL trong cột khóa:
SELECT price, count(*) AS products_count
FROM products
GROUP BY price
ORDER BY price;
price | products_count
-------+----------------
3.00 | 2
12.50 | 2
45.00 | 1
| 1
(4 rows)
Hai sản phẩm giá 12.50 nằm chung một nhóm, còn Desk Lamp có giá NULL tự thành một nhóm riêng. Dòng NULL nằm cuối vì PostgreSQL xếp NULL sau mọi giá trị khi sắp xếp tăng dần (MySQL làm ngược lại). Tiếp theo là count(price) khác count(*):
SELECT category,
count(*) AS rows_count,
count(price) AS priced,
round(avg(price), 2) AS avg_price
FROM products
GROUP BY category
ORDER BY category;
category | rows_count | priced | avg_price
----------+------------+--------+-----------
office | 1 | 0 |
paper | 2 | 2 | 24.00
pen | 3 | 3 | 9.33
(3 rows)
Nhóm office có 1 sản phẩm nhưng 0 sản phẩm có giá, và trung bình là NULL.
Xem từng bước
Animation dưới đây đi qua việc gom sáu sản phẩm thành ba nhóm. Hãy trả lời câu dự đoán trước khi bấm bước đầu.
Đang tải Animation…
Bảng trace bằng chữ [Đã chạy: PostgreSQL 16.15, bảng products]:
| Nhóm | Số dòng | count(price) | avg(price) |
|---|---|---|---|
| paper | 2 | 2 | 24.00 |
| pen | 3 | 3 | 9.33 |
| office | 1 | 0 | NULL |
Bạn vừa làm được: giải thích vì sao một nhóm có dòng mà count(price) vẫn là 0.
Concept Check
Dùng bảng products và orders của bài.
Câu 1. SELECT user_id, status, count(*) FROM orders GROUP BY user_id báo lỗi vì sao?
- A. Vì
count(*)không dùng được cùng vớiGROUP BY. - B. Vì
statuskhông phải khóa nhóm và không được tổng hợp, nên không biết lấy giá trị nào. - C. Vì bảng
orderschưa có chỉ mục cho cộtstatus. - D. Vì
GROUP BYchỉ cho phép gom theo cột đầu tiên.
Câu 2. Nhóm office có một sản phẩm giá NULL. count(*) và count(price) của nhóm này lần lượt là gì?
- A. 1 và 0, vì
count(*)đếm dòng còncount(price)bỏ qua NULL. - B. 0 và 0, vì nhóm chỉ có giá NULL nên không có gì để đếm.
- C. 1 và 1, vì cả hai hàm đều đếm mọi dòng của nhóm.
- D. NULL và NULL, vì giá NULL làm mọi hàm đếm trả về NULL.
Câu 3. SELECT sum(total) FROM orders WHERE status = 'refunded' (không đơn nào refunded) cho kết quả gì?
- A. Không có dòng nào.
- B. Một dòng với giá trị 0.
- C. Một dòng với giá trị NULL.
- D. Báo lỗi vì không có dữ liệu để cộng.
Đáp án và giải thích:
- Câu 1: B. A và D sai quy tắc. C không liên quan. Sau khi gom theo
user_id, mỗi nhóm có thể có nhiều trạng thái. - Câu 2: A. B sai vì
count(*)đếm dòng bất kể giá trị. C sai vìcount(price)bỏ NULL. D sai vì hàm đếm không bao giờ trả về NULL. - Câu 3: C. Không có
GROUP BYthì cả bảng là một nhóm duy nhất, nên luôn có một dòng.sumcủa tập rỗng là NULL, không phải 0. Bài đã chạy: cộtstrống, cộtclà 0.
Làm cùng mình
Kế toán muốn thống kê các đơn paid theo từng khách: số đơn, tổng doanh thu, và đơn lớn nhất.
Bước 1. Viết ra giấy
Từ 6 đơn paid, nhóm bằng tay theo khách rồi cộng và tìm đơn lớn nhất của mỗi nhóm.
Bước 2. Điền chỗ trống
SELECT user_id,
-- TODO 1: đếm số đơn của mỗi khách, đặt tên orders_count
-- TODO 2: tổng doanh thu, đặt tên revenue
-- TODO 3: đơn lớn nhất, đặt tên biggest
FROM orders
WHERE status = 'paid'
GROUP BY user_id
ORDER BY user_id;
Gợi ý: count(*), sum(total), max(total), mỗi cái cần AS để đặt tên.
Chạy xong bạn sẽ thấy [Đã chạy] 4 dòng, mỗi khách một dòng. So với bảng bạn nhóm tay ở Bước 1. Đáp án ở mục Lời giải và đáp án.
Bước 3. Thêm một khóa
Đổi GROUP BY user_id thành nhóm theo cả user_id và status, bỏ điều kiện WHERE. Dự đoán trước: có bao nhiêu dòng?
Bước 4. Vẽ lại ra giấy
Vẽ lại các xấp thẻ cho từng khách, ghi số đơn và tổng tiền của mỗi xấp.
Thử phá nó đi
Bốn lỗi người mới hay gặp [Đã chạy: PostgreSQL 16.15]:
| Code lỗi | Thông báo lỗi | Nguyên nhân | Cách sửa |
|---|---|---|---|
SELECT user_id, status ... GROUP BY user_id | column "orders.status" must appear in the GROUP BY clause or be used in an aggregate function | Cột thường không phải khóa | Thêm vào GROUP BY hoặc bọc hàm tổng hợp |
SELECT user_id, total ... GROUP BY user_id | Cùng loại lỗi với cột orders.total | total có nhiều giá trị trong nhóm | Dùng sum(total) hoặc max(total) |
GROUP BY 2 khi chỉ có 1 cột | GROUP BY position 2 is not in select list | Vị trí đếm theo danh sách SELECT | Dùng đúng vị trí |
GROUP BY count(*) | aggregate functions are not allowed in GROUP BY | Không gom theo kết quả của hàm tổng hợp | Gom theo cột hoặc biểu thức thường |
Kết quả thật của lỗi đầu tiên:
SELECT user_id, status, count(*) FROM orders GROUP BY user_id;
ERROR: column "orders.status" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT user_id, status, count(*) FROM orders GROUP BY user_i...
^
Lỗi thứ hai:
SELECT user_id, total FROM orders GROUP BY user_id;
ERROR: column "orders.total" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT user_id, total FROM orders GROUP BY user_id;
^
Lỗi thứ ba và thứ tư:
SELECT user_id FROM orders GROUP BY 2;
ERROR: GROUP BY position 2 is not in select list
LINE 1: SELECT user_id FROM orders GROUP BY 2;
^
SELECT user_id, count(*) FROM orders GROUP BY count(*);
ERROR: aggregate functions are not allowed in GROUP BY
LINE 1: SELECT user_id, count(*) FROM orders GROUP BY count(*);
^
Một lỗi im lặng đáng nhớ: tổng của tập rỗng là NULL chứ không phải 0:
SELECT sum(total) AS s, count(*) AS c
FROM orders
WHERE status = 'refunded';
s | c
---+---
| 0
(1 row)
Có GROUP BY thì tập rỗng cho ra 0 dòng:
SELECT status, sum(total) AS s
FROM orders
WHERE status = 'refunded'
GROUP BY status;
status | s
--------+---
(0 rows)
Hiểu lầm quan trọng nhất của bài này là nghĩ rằng cột nào cũng chọn được sau khi gom. Hình dưới đối chiếu niềm tin sai với mô hình đúng.
Đang tải sơ đồ…
Cách đọc hình: chuỗi đỏ là niềm tin sai và lỗi nó gây ra. Chuỗi xanh là mô hình đúng: sau khi gom, mỗi cột thường chỉ có hai lựa chọn là làm khóa hoặc được tổng hợp.
Tự tay làm
Dùng bảng products và orders của bài. Mỗi bài nêu đầu vào, kết quả mong đợi và tiêu chí đạt.
Cơ bản
Bài 1. Điền bảng số dòng. Đầu vào: bảng orders 8 đơn của 4 khách và 3 trạng thái. Điền số dòng kết quả:
| Truy vấn | Số dòng |
|---|---|
GROUP BY status | ? |
GROUP BY user_id | ? |
GROUP BY user_id, status | ? |
Tiêu chí đạt: điền đúng cả ba ô rồi chạy kiểm tra. Gợi ý: số dòng bằng số tổ hợp khóa khác nhau.
Vận dụng
Bài 2. Giá thấp nhất và cao nhất theo nhóm hàng. Đầu vào: bảng products. Với mỗi category, trả min(price) đặt tên cheapest và max(price) đặt tên priciest, sắp xếp theo category. Tiêu chí đạt: kết quả có 3 dòng, nhóm office có hai giá trị NULL. Gợi ý: min và max bỏ qua NULL nhưng nhóm toàn NULL cho NULL.
Thử thách (tùy chọn)
Bài 3. Cặp khách và trạng thái có từ hai đơn. Đầu vào: bảng orders. Gom theo user_id và status, chỉ giữ cặp có nhiều hơn 1 đơn, sắp xếp theo user_id. Tiêu chí đạt: kết quả có 1 dòng. Gợi ý: điều kiện trên số đếm đặt ở HAVING.
Tự kiểm tra
Trả lời bằng lời của bạn, không nhìn lại bài.
- (Mục tiêu 1, nhớ lại) Mỗi nhóm của
GROUP BYcho ra bao nhiêu dòng kết quả? - (Mục tiêu 3, dự đoán) Nhóm có 3 dòng, trong đó 1 dòng có
priceNULL:count(*)vàcount(price)là bao nhiêu? - (Mục tiêu 2, dự đoán) Có thể viết
SELECT user_id, total ... GROUP BY user_idkhông? - (Mục tiêu 3, giải thích cho bạn chưa học) Vì sao
sum(total)trên tập rỗng là NULL chứ không phải 0? - (Mục tiêu 4, tìm lỗi) Vì sao
GROUP BY count(*)bị từ chối?
Điều cần nhớ
GROUP BYgom dòng cùng khóa thành nhóm và cho mỗi nhóm một dòng kết quả.- Cột thường trong
SELECTphải nằm trongGROUP BYhoặc trong hàm tổng hợp. count(*)đếm dòng;count(cột)bỏ NULL; các hàm khác cũng bỏ NULL.- NULL ở cột khóa tạo một nhóm riêng; tổng của tập rỗng là NULL.
Cheat sheet:
| Muốn | Viết | Ghi chú |
|---|---|---|
| Đếm đơn theo trạng thái | SELECT status, count(*) ... GROUP BY status | Mỗi trạng thái một dòng |
| Gom theo tháng | GROUP BY date_trunc('month', created_at) | Khóa là biểu thức |
| Gom theo hai cột | GROUP BY user_id, status | Mỗi cặp một nhóm |
| Đếm giá trị khác nhau | count(DISTINCT user_id) | Bỏ NULL |
| Lọc nhóm | HAVING count(*) > 1 | Sau GROUP BY |
Hình tóm tắt của bài là sơ đồ ở mục Bức tranh lớn.
Câu hỏi để dành cho ngày mai: bảng orders có cột user_id, còn tên khách nằm ở bảng users. Muốn thống kê theo tên khách, bạn cần ghép hai bảng bằng thao tác nào?
Đào sâu
Phần này dành cho bạn tò mò.
- Đếm giá trị khác nhau.
count(*)đếm 8 đơn,count(DISTINCT user_id)đếm 4 khách:
SELECT count(*) AS all_rows,
count(DISTINCT user_id) AS buyers
FROM orders;
all_rows | buyers
----------+--------
8 | 4
(1 row)
- Hàm tổng hợp không GROUP BY. Cả bảng là một nhóm duy nhất:
SELECT count(*) AS orders_count,
sum(total) AS revenue,
min(created_at)::date AS first_day
FROM orders;
orders_count | revenue | first_day
--------------+---------+------------
8 | 650.49 | 2026-01-05
(1 row)
- Alias trong GROUP BY. PostgreSQL cho phép dùng alias của danh sách SELECT trong
GROUP BY, khác vớiWHEREvàHAVING:
SELECT status AS order_status, count(*) AS orders_count
FROM orders
GROUP BY order_status
ORDER BY order_status;
order_status | orders_count
--------------+--------------
cancelled | 1
paid | 6
pending | 1
(3 rows)
Đây là mở rộng của PostgreSQL. Nếu alias trùng tên một cột của bảng thì cột của bảng được ưu tiên, nên tránh đặt alias trùng tên cột.
- Vị trí cột.
GROUP BY 1vàORDER BY 2 DESC, 1dùng số thứ tự trong danh sách SELECT:
SELECT status, count(*) AS orders_count
FROM orders
GROUP BY 1
ORDER BY 2 DESC, 1;
status | orders_count
-----------+--------------
paid | 6
cancelled | 1
pending | 1
(3 rows)
- PostgreSQL nới quy tắc khi nhóm theo khóa chính. Nếu bạn gom theo khóa chính của một bảng, mọi cột khác của chính bảng đó được phép xuất hiện trong
SELECT, vì mỗi giá trị khóa chính chỉ ứng với đúng một dòng. Ở đâyusers.idlà khóa chính nênu.emailhợp lệ dù không nằm trongGROUP BY(câu cóLEFT JOIN, bài sau sẽ dạy kỹ):
SELECT u.id, u.email, count(o.id) AS orders_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id
ORDER BY u.id;
id | email | orders_count
----+----------------+--------------
1 | an@mail.com | 2
2 | binh@gmail.com | 3
3 | chi@GMAIL.com | 2
4 | dung@mail.com | 0
5 | eva@gmail.com | 1
(5 rows)
Nếu đổi sang gom theo o.user_id, là cột không phải khóa chính của bảng nào trong câu lệnh này, quy tắc nới không còn áp dụng:
SELECT u.id, u.email, count(o.id) AS orders_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY o.user_id;
ERROR: column "u.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT u.id, u.email, count(o.id) AS orders_count
^
PostgreSQL chỉ nhận ra khóa chính cho quy tắc nới này. Một cột UNIQUE NOT NULL về lý thuyết cũng xác định duy nhất một dòng, nhưng vẫn bị từ chối:
SELECT id, name, count(*) FROM tags GROUP BY id;
ERROR: column "tags.name" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT id, name, count(*) FROM tags GROUP BY id;
^
- FILTER để đếm có điều kiện ngay trong nhóm. Một câu lệnh có thể tính doanh thu đơn paid và doanh thu các đơn còn lại theo từng tháng, không cần hai truy vấn. Tháng không có đơn thuộc loại nào thì cột đó là NULL, vì tổng của tập rỗng là NULL:
SELECT date_trunc('month', created_at)::date AS month,
sum(total) FILTER (WHERE status = 'paid') AS paid_revenue,
sum(total) FILTER (WHERE status <> 'paid') AS other_revenue
FROM orders
GROUP BY 1
ORDER BY 1;
month | paid_revenue | other_revenue
------------+--------------+---------------
2026-01-01 | 200.00 | 30.00
2026-02-01 | 260.50 | 60.00
2026-03-01 | 99.99 |
(3 rows)
- ROLLUP thêm dòng tổng.
GROUP BY ROLLUP (status)cho thêm một dòng tổng cộng vớistatuslà NULL. Hàmgrouping(status)trả 1 ở dòng tổng, giúp phân biệt với một nhóm thật cóstatuslà NULL. Thứ tự sắp xếp mặc định của PostgreSQL đặt NULL cuối khi tăng dần, nên dòng tổng nằm cuối [Đã chạy: PostgreSQL 16.15]:
SELECT status,
grouping(status) AS is_total,
count(*) AS orders_count,
sum(total) AS revenue
FROM orders
GROUP BY ROLLUP (status)
ORDER BY status;
status | is_total | orders_count | revenue
-----------+----------+--------------+---------
cancelled | 0 | 1 | 30.00
paid | 0 | 6 | 560.49
pending | 0 | 1 | 60.00
| 1 | 8 | 650.49
(4 rows)
GROUPING SETS và CUBE là họ hàng của ROLLUP: chúng cho nhiều cách gom trong cùng một câu lệnh [Suy luận: bài này không chạy hai dạng đó].
- Giới hạn của bài. Bài này chưa nói đến hàm cửa sổ, vốn giữ nguyên từng dòng thay vì gộp lại.
- Đọc thêm. PostgreSQL 16 Documentation, mục Aggregate Functions: https://www.postgresql.org/docs/16/functions-aggregate.html
Bài tiếp theo
Bài sau là toán tử LIKE: lọc chuỗi theo mẫu với ký tự đại diện. Mini-dự án nối tiếp: tìm email và tên sản phẩm theo mẫu trong cửa hàng shop.
Lời giải và đáp án
Làm cùng mình, Bước 2. Ba chỗ cần điền:
count(*) AS orders_count,
sum(total) AS revenue,
max(total) AS biggest
SELECT user_id,
count(*) AS orders_count,
sum(total) AS revenue,
max(total) AS biggest
FROM orders
WHERE status = 'paid'
GROUP BY user_id
ORDER BY user_id;
user_id | orders_count | revenue | biggest
---------+--------------+---------+---------
1 | 1 | 120.00 | 120.00
2 | 3 | 225.49 | 99.99
3 | 1 | 200.00 | 200.00
5 | 1 | 15.00 | 15.00
(4 rows)
User 2 có 3 đơn paid (80.00, 45.50, 99.99), tổng 225.49, lớn nhất 99.99.
Làm cùng mình, Bước 3. Không có WHERE, gom theo (user_id, status) cho 6 dòng, đã thấy ở ví dụ thứ ba của bài.
Bài 1. GROUP BY status: 3 dòng. GROUP BY user_id: 4 dòng (khách 1, 2, 3, 5). GROUP BY user_id, status: 6 dòng.
Bài 2.
SELECT category, min(price) AS cheapest, max(price) AS priciest
FROM products
GROUP BY category
ORDER BY category;
category | cheapest | priciest
----------+----------+----------
office | |
paper | 3.00 | 45.00
pen | 3.00 | 12.50
(3 rows)
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
min(price) và max(price) | Lấy giá nhỏ nhất và lớn nhất của nhóm, bỏ qua NULL | Hàm tổng hợp tính trên giá trị không NULL |
GROUP BY category | Một dòng cho mỗi nhóm hàng | Nhóm office toàn NULL nên cả hai cột NULL |
Bài 3.
SELECT user_id, status, count(*) AS orders_count
FROM orders
GROUP BY user_id, status
HAVING count(*) > 1
ORDER BY user_id;
user_id | status | orders_count
---------+--------+--------------
2 | paid | 3
(1 row)
Chỉ cặp (2, paid) có 3 đơn. Điều kiện trên số đếm nên nằm ở HAVING.
Tự kiểm tra.
- Đúng một dòng.
count(*)là 3,count(price)là 2.- Không, vì
totalkhông phải khóa và không được tổng hợp. - Vì không có giá trị nào để cộng, nên kết quả là không biết chứ không phải 0.
- Vì nhóm phải được xác định trước rồi mới tính hàm tổng hợp, nên không gom theo kết quả của chính hàm tổng hợp.