正确做法是先对各表分别聚合再关联,避免join导致行数膨胀;count和sum需作用于各自独立聚合结果,不可在未去重的宽表上混用。

GROUP BY 后怎么同时算 SUM 和 COUNT 且不重复计数?
直接在 GROUP BY 后写多个聚合函数没问题,但关键在于:如果字段之间有 1:N 关系(比如一个订单含多条明细),COUNT 和 SUM 作用在不同列时,容易因隐式笛卡尔或 JOIN 导致重复计算。常见错误是先 JOIN 再聚合,结果把子表行数放大了。
正确做法是「先聚合,再关联」或「用子查询/CTE 隔离聚合层级」:
- 对主表做
GROUP BY,只聚合主表字段(如订单总金额用SUM(order_amount)) - 对子表单独聚合(如用
SELECT order_id, COUNT(*) AS item_cnt FROM items GROUP BY order_id) - 最后用
LEFT JOIN把两个聚合结果按order_id拼起来
避免在同一个 SELECT 中混用 SUM(detail_price) 和 COUNT(detail_id) 却没处理明细表膨胀——那不是语法错,是逻辑错。
用 STRING_AGG 或 GROUP_CONCAT 拼接字符串时怎么去重?
STRING_AGG(PostgreSQL / SQL Server)和 GROUP_CONCAT(MySQL)默认不自动去重,重复值会原样拼进去。比如用户标签字段存成多行,GROUP_CONCAT(tag) 可能输出 "vip,vip,premium"。
去重必须显式处理:
- PostgreSQL:用
STRING_AGG(DISTINCT tag, ',') - MySQL 8.0+:支持
GROUP_CONCAT(DISTINCT tag SEPARATOR ',') - MySQL 5.7 或更老版本:得先套一层
SELECT DISTINCT子查询,再GROUP_CONCAT
注意 DISTINCT 修饰的是整个表达式,不能只写 DISTINCT tag 然后加其他字段——会报错。
想在一个 GROUP BY 里同时取 MAX(id) 和对应 name,为什么不能直接 SELECT name?
因为标准 SQL 不允许在 GROUP BY 查询中,SELECT 非分组字段且未被聚合函数包裹(如 MAX、MIN)。直接写 SELECT MAX(id), name 会报错:column "name" must appear in the GROUP BY clause or be used in an aggregate function。
要拿到「最大 id 对应的 name」,本质是「关联行级数据」,不是纯聚合:
- 用窗口函数最稳:
ROW_NUMBER() OVER (PARTITION BY category ORDER BY id DESC),然后外层过滤rnum = 1 - 用关联子查询:
WHERE id = (SELECT MAX(id) FROM t2 WHERE t2.category = t1.category) - MySQL 8.0+ 支持
FIRST_VALUE(name) OVER (PARTITION BY category ORDER BY id DESC)
别用 GROUP BY category, name 加 HAVING id = MAX(id) —— 这逻辑不通,HAVING 无法引用非聚合字段。
聚合时 NULL 值怎么影响 COUNT、SUM、AVG?
COUNT(*) 统计所有行,包括 NULL;COUNT(col) 只统计 col IS NOT NULL 的行;SUM(col) 和 AVG(col) 自动忽略 NULL,但若整组全是 NULL,SUM 返回 NULL,AVG 也返回 NULL(不是 0)。
实际中容易踩坑的点:
-
COUNT(1)和COUNT(*)行为一致,别误以为COUNT(1)更快——现代优化器都等价 -
AVG(COALESCE(salary, 0))是错的:把 NULL 当 0 算会拉低平均值,应该用AVG(salary)保持语义(空组就是 NULL) - 需要「有值才计数」就用
COUNT(col);需要「行数」就用COUNT(*);别混用还假装自己知道区别
聚合函数对 NULL 的处理是确定的,但业务含义是否合理,得看字段本身能不能为 NULL、以及 NULL 在该场景下代表什么——这点比语法更重要。











