必须用 SUM(sales) OVER(PARTITION BY category) 计算分类总和,否则无法正确计算每行占比;需用 NULLIF 防除零,乘 1.0 避整数除法截断,并在最终显示时格式化为百分比。

用 SUM() OVER() 算分类总销售额是前提
窗口函数本身不聚合,SUM(sales) OVER(PARTITION BY category) 才能拿到每个 category 内的总和。如果漏掉 PARTITION BY,就会算成全表总和,占比全变成 1 或接近 1。
常见错误:写成 SUM(sales) OVER()(没分区)或 SUM(sales) GROUP BY category(没法和明细行对齐)。
- 必须搭配
PARTITION BY category,否则无法按分类切片 - 不能用
GROUP BY替代,否则原始行数会丢失,没法算每行占本分类多少 - 注意字段名大小写和空格——比如
category实际可能是CategoryName或带引号的"category"
sales / SUM(sales) OVER(...) 直接算占比,但要注意除零
直接写 sales * 1.0 / SUM(sales) OVER(PARTITION BY category) 是最简方式。乘 1.0 是为了防止整数除法截断(尤其在 PostgreSQL、SQL Server 中)。
如果某分类下所有 sales 都是 NULL 或全为 0,分母会是 0 或 NULL,结果变成 NULL 或报错(如 MySQL 8.0 默认开启严格模式时)。
- 加
COALESCE(..., 0)把分母兜底为 0,再配合NULLIF(..., 0)更稳妥:sales / NULLIF(SUM(sales) OVER(PARTITION BY category), 0) - MySQL 用户注意:
sql_mode包含STRICT_TRANS_TABLES时,除零会报错,必须用NULLIF - PostgreSQL 和 SQL Server 对
0/0返回NULL,不报错,但仍建议显式处理
想保留两位小数?别用 ROUND() 糊弄业务逻辑
占比通常要输出百分比格式(如 0.23 → 23.00%),但 ROUND(x, 2) 只是四舍五入,不解决显示问题。真正要的是可读性 + 精度可控。
- 显示用
ROUND(x * 100, 2) || '%'(PostgreSQL)、CONCAT(ROUND(x * 100, 2), '%')(MySQL 8.0+)、或STR(x * 100, 5, 2) + '%'(SQL Server) - 但注意:先
ROUND再乘 100,和先乘 100 再ROUND结果可能不同(浮点误差累积) - 更稳的做法是:先算出精确占比(不 round),仅在最终 SELECT 中格式化显示
ORDER BY 在窗口里加不加,取决于你是否需要累计占比
如果只是静态占比(每个商品占本类多少),OVER(PARTITION BY category) 足够;但如果要“该商品在本类销售额排名前 X%”,就得加 ORDER BY 做累计。
- 例如累计占比:
SUM(sales) OVER(PARTITION BY category ORDER BY sales DESC),再除以分类总和 - 但加了
ORDER BY后,窗口行为从“整组聚合”变成“从第一行累加到当前行”,性能略降,大数据量时需留意 - 排序字段和
PARTITION BY字段无关——比如按时间排序算“截至当天的占比”,PARTITION BY category不变,ORDER BY date单独加
实际执行时,最容易被忽略的是分母是否真的按分类计算、以及除零场景是否覆盖。哪怕数据里暂时没零值,上线后上游清洗异常也可能引入,NULLIF 这一步别省。

















