毛利率计算需用NULLIF处理分母为0,再用COALESCE补0或保留NULL;排名应优先用DENSE_RANK()体现并列;GROUP BY后不可直接引用列别名,窗口函数ORDER BY须用原始表达式;MySQL 5.7需变量模拟排名但不可靠。

用 AVG() 和 SUM() 算毛利率时别直接除零
毛利率 = (SUM(revenue) − SUM(cost)) / SUM(revenue),但实际数据里常有 revenue = 0 或全为 NULL 的产品线。直接写 SUM(revenue - cost) / SUM(revenue) 会返回 NULL(不是报错),但更危险的是某些数据库(如 MySQL 默认模式)可能静默转成 0,导致排名失真。
正确做法是显式处理分母:
- 用
NULLIF(SUM(revenue), 0)把分母为 0 的情况转成NULL,让整个表达式结果也为NULL - 再用
COALESCE(..., 0)统一补 0(如果业务允许“零收入=毛利率0”),或保留NULL并在排序时明确NULLS LAST - 注意:PostgreSQL 支持
NULLS LAST,MySQL 8.0+ 需用ORDER BY ... IS NULL, ... DESC
用 ROW_NUMBER() 还是 RANK() 排名?看是否要跳名次
产品线毛利率可能相同(比如两条线都是 42.5%)。这时:
-
ROW_NUMBER()强制给唯一序号,相同毛利率也会被拆开排(如 1, 2, 3),不适合体现“并列”关系 -
RANK()对相同值赋予相同名次,后续跳过位数(如 42.5% 占第1、2名,则下一个名次是 4) -
DENSE_RANK()更常用:相同值同名次,后续不跳(1, 2, 2, 3)——符合多数业务对“排名”的直觉
示例片段:
SELECT product_line, COALESCE(ROUND((SUM(revenue) - SUM(cost)) * 100.0 / NULLIF(SUM(revenue), 0), 2), 0) AS gross_margin_pct, DENSE_RANK() OVER (ORDER BY (SUM(revenue) - SUM(cost)) * 1.0 / NULLIF(SUM(revenue), 0) DESC NULLS LAST) AS rank FROM sales GROUP BY product_line;
GROUP BY 后不能直接引用原表字段,小心列别名作用域
写完 SELECT ... AS gross_margin_pct,别在 ORDER BY 或窗口函数里直接写 gross_margin_pct —— 多数数据库(如 PostgreSQL、SQL Server)不允许在 ORDER BY 外引用列别名,MySQL 8.0 虽支持但易混淆逻辑顺序。
- 窗口函数的
ORDER BY必须用原始表达式,不能用别名 - 聚合后字段若需多次使用(比如既要显示又要排序),建议用子查询或 CTE 封装,避免重复计算和歧义
- 特别注意:
WHERE也不能用聚合结果过滤,得用HAVING;想按排名过滤(如只取前5),必须嵌套一层
MySQL 5.7 不支持窗口函数,得用变量模拟排名
如果还在用 MySQL 5.7 或更老版本,ROW_NUMBER()、RANK() 全不可用。临时方案是用用户变量,但要注意执行顺序不可靠:
- 必须先
ORDER BY再赋值,否则排名乱序 - 变量初始化必须在同一个语句中完成(例如用
(SELECT @rank := 0)子查询) - 并发查询下变量可能污染,仅限单次分析用,不可用于生产报表
更稳妥的做法是升级到 MySQL 8.0+,或把数据导出后用 Python/Pandas 计算排名——尤其当产品线数量大、毛利率精度要求高时,数据库外处理反而更可控。
真正难的不是写出语法正确的 SQL,而是确认「毛利率」在你们系统里是否已统一扣除退货、折扣、运费等调整项;这些业务口径一旦没对齐,再漂亮的排名也没意义。


















