用 COUNT() OVER() 计算全表总数作分母最稳妥,因其天然同步、只扫描一次表,避免过滤不一致或空分类导致偏差;需配合 100.0 和 ROUND(,2) 防整数除法截断;NULL 分类需按业务明确是否纳入;销售额占比须确保分子分母过滤条件严格一致。

用 COUNT(*) OVER() 算全表总数作分母最稳
直接在 GROUP BY 里除以子查询或硬写总数,容易因过滤条件不一致、空分类、重复扫描导致结果偏移。窗口函数 COUNT(*) OVER()(不带 PARTITION BY)能把全表总行数“广播”到每组,天然同步、只扫一次表。
-
COUNT(*) OVER()返回的是原始表总行数,包含NULL分类行(除非你提前WHERE category IS NOT NULL) - 别写成
COUNT(*) OVER(PARTITION BY category)当分母——那算出来全是 100% - MySQL 8.0+、PostgreSQL、SQL Server 2012+ 都支持;SQLite 3.25+ 才有,旧版会报
no such function: COUNT或syntax error near "OVER"
* 100.0 和 ROUND(, 2) 必须一起用
整数除法在 PostgreSQL、SQL Server、MySQL(严格模式下)默认截断小数,COUNT(*) / COUNT(*) OVER() 可能返回 0 而不是 0.33。光靠 ROUND() 不够,得先让除法变成浮点运算。
- 写成
COUNT(*) * 100.0 / COUNT(*) OVER(),比CAST(... AS DECIMAL)更简洁通用 -
ROUND(..., 2)控制显示精度,但多个四舍五入值加起来可能 ≠ 100 —— 这是浮点舍入必然结果,不是 bug - 别用
FLOAT类型存中间结果,优先用DECIMAL(5,2)或保留小数字段类型
分类字段含 NULL 时占比逻辑必须明确
GROUP BY category 会把 NULL 单独成组,但 COUNT(*) 默认计入所有行(含 NULL 行),所以分母仍含它。是否合理,取决于业务定义。
- 若想把
NULL当作合法分类(如“未归类”),保持原写法,且确保数据库允许GROUP BY中的NULL(标准 SQL 支持) - 若想排除
NULL再算占比,必须在外层加WHERE category IS NOT NULL,否则分母含NULL行而分子不含,比例失真 - PostgreSQL 可用
COUNT(*) FILTER (WHERE category IS NOT NULL) OVER();MySQL/SQL Server 得改用SUM(CASE WHEN category IS NOT NULL THEN 1 ELSE 0 END) OVER()
算销售额占比不能只换函数名
从“订单数占比”切换到“销售额占比”,不只是把 COUNT(*) 换成 SUM(sales) —— 分子分母的过滤范围必须严格一致,否则分母混入未付款订单,占比就崩了。
- 分子:用
SUM(sales),分母:用SUM(sales) OVER()(全表)或SUM(sales) OVER(PARTITION BY category)(本分类) - 如果加了
WHERE status = 'paid',窗口函数里的SUM()也必须受同一条件约束;否则分母仍是全量,分子是过滤后,结果无意义 - 防除零:用
NULLIF(SUM(sales) OVER(), 0)替代直接除,尤其 MySQL 开启STRICT_TRANS_TABLES时,0 除会报错
真正麻烦的是多层嵌套场景:比如“每个地区内各品类销售额占该地区总额的比”,这时 PARTITION BY region, category 和 PARTITION BY region 要配对使用,且 WHERE 条件必须在最外层统一生效——稍一错位,分母就不是你认为的那个“地区总额”。

















