SQL GROUP BY 用法示例

GROUP BY 把行收进一个个桶里,每个桶只出一行结果。聚合函数(COUNT, SUM, AVG, MAX, MIN)负责汇总每个桶。这一页讲两件事:你会反复用到的那几个套路,还有你一定会撞上的那个报错。

🎙️ 发布并录制于: ·

GROUP BY 的几个核心套路

分组就是把一张很长的流水表变成一份报表。先想清楚哪个字段决定一个桶,再对每个桶算计数、总额、平均值或者最大值。一次算好几个汇总也行,数据库照样每个桶只返回一行。

-- how many orders per customer?
SELECT user_id, COUNT(*) AS orders
FROM orders
GROUP BY user_id;

-- revenue per customer, biggest first, top 10
SELECT user_id, SUM(amount) AS revenue
FROM orders
GROUP BY user_id
ORDER BY revenue DESC
LIMIT 10;

-- several aggregates at once (the report query)
SELECT user_id,
       COUNT(*) AS orders, SUM(amount) AS revenue,
       AVG(amount) AS avg_order, MAX(amount) AS biggest
FROM orders GROUP BY user_id;

-- group by a computed value: orders per month (SQLite)
SELECT strftime('%Y-%m', created_at) AS month, COUNT(*)
FROM orders GROUP BY month;

每个 GROUP BY 查询都会撞上的报错

行被收拢之后,你选出来的每个值,在每个桶里都必须只有一个明确的答案。如果五十行源数据能给出五十个不同的名字,数据库不会替你猜。名字属于这个桶的身份,就把它加进分组条件里;只有当你真的确认这些值等价,才用聚合函数把它收起来。

SELECT user_id, name, SUM(amount)   -- ← name is the problem
FROM orders JOIN users ON ...
GROUP BY user_id;

ERROR: column "users.name" must appear in the GROUP BY
clause or be used in an aggregate function

-- concrete fix when one name belongs to each user:
GROUP BY user_id, name
别拿聚合函数盖住脏数据
MAX(name) 能让报错消失,但它同样可能把一个用户桶里两个不同的名字藏起来。我的建议是写 GROUP BY user_id, name。只有在确认这个值真的恒定之后,才用聚合函数当解法。

HAVING、查重复,还有两个不声不响的计数坑

分组之前用行过滤,分组之后用桶过滤。如果你的条件依赖总额或者计数,它就该放在分组这一步之后。查重复靠的也是这个区别:先按邮箱分出一个个桶,再只留下行数超过一的桶。

-- customers who spent 100+ in 2026
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE created_at >= '2026-01-01'    -- rows before grouping
GROUP BY user_id
HAVING SUM(amount) >= 100;          -- buckets after grouping
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- every duplicated email, with its count. works for any column.
两个不声不响的坑
COUNT(*) 和 COUNT(列):星号数的是行数;COUNT(col) 会跳过那一列里的 NULL。报表里的计数莫名偏小,先看它用的是哪种写法。NULL 自己算一组:缺失的键会被收进同一个 NULL 桶;报表里那行来历不明的数据是缺值,不是数据库出毛病。
📚 本页是 SQL 系列中的一篇。完整速成课讲 JOIN、NULL 的坑和 UPDATE 惨案,全部配有音频:10 分钟学会 SQL →

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.