聚合函数能计算整个 Table 的总数和平均值,但真实问题经常要求“每一类分别统计”:每个商品类别有多少商品、每个用户下了多少订单、每位顾客买了多少件商品。SQL 使用 GROUP BY 把相同字段值的 Row 放在一组,再对每组运行聚合函数。
| id | name | price | category |
|---|---|---|---|
| 1 | Keyboard | 80 | Electronics |
| 2 | Mouse | 40 | Electronics |
| 3 | Coffee Mug | 15 | Home |
| 4 | Backpack | 60 | Fashion |
| 5 | Webcam | 55 | Electronics |
从一个总数变成每组一个结果
SELECT COUNT(*) FROM products 只能告诉我们一共有 5 个商品。加入 GROUP BY category 后,相同 category 的 Row 先聚到一起,COUNT(*) 再分别计算:Electronics 3、Fashion 1、Home 1。
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category;每组可以计算多个统计值
GROUP BY 不只搭配 COUNT。每个 category 可以同时计算商品数、平均价格、最高价格和最低价格。一条结果 Row 代表一个组,而不再代表一个原始商品。
为什么普通 Column 要出现在 GROUP BY?
category 是每组的标签,所以可放在 SELECT 中。若只按 category 分组却同时 SELECT name,Electronics 组里有 Keyboard、Mouse、Webcam,数据库不知道这一条统计结果该显示哪个 name;严格 SQL 模式会拒绝这种查询。
SELECT category,
COUNT(*) AS product_count,
AVG(price) AS average_price,
MAX(price) AS highest_price,
MIN(price) AS lowest_price
FROM products
GROUP BY category;GROUP BY 与 JOIN
要统计每位用户的订单数,先 LEFT JOIN users 和 orders,再按用户分组。LEFT JOIN 让无订单的 Diana 也出现。这里使用 COUNT(o.id) 而不是 COUNT(*):Diana 的连接结果仍有一条 Row,但 o.id 为 NULL,COUNT(o.id) 忽略它,因此订单数正确地显示 0。
SELECT u.name, COUNT(o.id) AS order_count
FROM users AS u
LEFT JOIN orders AS o ON u.id = o.user_id
GROUP BY u.id, u.name;WHERE 筛 Row,HAVING 筛组
WHERE 发生在分组前,用于筛选原始 Row;HAVING 发生在分组统计后,用于筛选组结果。找“至少有两个商品的类别”要写 HAVING COUNT(*) >= 2,不能写 WHERE COUNT(*)。两者可以同用:先保留 price 至少 40 的商品,再按 category 分组,最后只保留仍有至少两个商品的组。
拆解完整统计查询
“显示至少有 2 个订单的用户,并按订单数降序”可以逐步思考:连接 users 与 orders;按用户 GROUP BY;COUNT 订单;HAVING 留下 count 至少 2;ORDER BY 降序。SQL 变长时,不要一次吞下整句,逐段说明 FROM、JOIN、WHERE、GROUP BY、HAVING 和 ORDER BY 各自做什么。
看见 GROUP BY 的“中间步骤”
| category | count | average_price |
|---|---|---|
| Electronics | 3 | 58.33 |
| Home | 1 | 15.00 |
| Fashion | 1 | 60.00 |
| Stationery | 1 | 8.00 |
原始 6 行先按 category 分组,再为每组计算统计。知识测验
知识测验
每个类别的商品概览
商品经理需要每个 category 的商品数量和平均价格,并按平均价格从高到低排序。结果应只有 category、product_count、average_price 三列。
高价值客户统计
业务要找 2026 年已完成订单中,消费总额至少 100 的用户。连接 users 与 orders;只保留 status 为 completed 且 order_date 不早于 2026-01-01 的 Row;按用户分组;返回 user_name、order_count、total_spent;用 HAVING 筛总额,并按 total_spent 降序。