加权中位数无法用标准SQL函数直接计算,必须通过权重展开为虚拟频次、排序后找累计权重首次≥总权重一半的位置来实现;PERCENTILE_CONT不支持权重参数,salary*weight是标量变换而非频次扩展,会严重偏离定义。

没有标准 SQL 函数能直接算“加权中位数”——它必须手动构造,核心是把权重展开为虚拟频次,再在扩展序列上求中位数。
为什么不能直接用 PERCENTILE_CONT + weight 相乘
PERCENTILE_CONT(0.5) 只接受原始值排序,不接受权重参数。写 PERCENTILE_CONT(0.5) OVER (PARTITION BY dept ORDER BY salary) 是对 salary 排序取中位,和 weight 无关;而 salary * weight 是标量变换,不是频次扩展——这会导致结果完全偏离加权定义。
- 加权中位数的定义:找一个值,使得权重累计和首次 ≥ 总权重的一半
- 它不是“加权平均数”,也不是“对加权值排序后的中位数”
- 常见误操作:
SUM(salary * weight) / SUM(weight)→ 这是加权平均,不是中位数
PostgreSQL/SQL Server 中用窗口函数模拟加权中位数
本质是两步:先按 salary 排序并计算累计权重,再定位第一个 cum_weight >= total_weight / 2 的行。需用 SUM() OVER 和自关联或 LATERAL(PostgreSQL)实现。
- 必须先用
SUM(weight) OVER (PARTITION BY dept)算出每组总权重 - 再用
SUM(weight) OVER (PARTITION BY dept ORDER BY salary ROWS UNBOUNDED PRECEDING)算累计权重 - 最后用
LAG()或子查询比对累计值是否跨过中点阈值 - 示例关键片段(PostgreSQL):
SELECT DISTINCT ON (dept) dept, FIRST_VALUE(salary) OVER ( PARTITION BY dept ORDER BY (cum_weight >= total_wt / 2.0)::int DESC, salary ) AS weighted_median FROM ( SELECT dept, salary, weight, SUM(weight) OVER (PARTITION BY dept) AS total_wt, SUM(weight) OVER (PARTITION BY dept ORDER BY salary) AS cum_weight FROM employees ) t;
MySQL 8.0+ 中只能靠变量或 CTE 手动扫描
MySQL 不支持 PERCENTILE_CONT,也不支持 LATERAL 或 FIRST_VALUE 在非窗口上下文中稳定使用。最可靠路径是:生成带行号的累计序列,再用 WHERE 精准命中中点位置。
- 先按
dept, salary排序,用@rn := @rn + weight模拟累计(注意变量初始化) - 外层查出每组
total_weight,再JOIN回去筛@rn >= total_weight / 2的最小 salary - 避免用
GROUP_CONCAT拼接——大数据下易触发group_concat_max_len截断 - 关键风险:MySQL 变量执行顺序不保证,必须用
ORDER BY显式固化扫描顺序
NULL 和重复 salary 的处理最容易被忽略
加权中位数对 NULL 和并列值异常敏感:weight 为 NULL 会让整行失效;多个相同 salary 对应不同 weight 时,累计曲线会出现平台段——中点可能落在平台中间,此时应返回该 salary 值(而非插值)。
- 务必用
COALESCE(weight, 0)替代裸 weight,防止 NULL 污染累计和 - ORDER BY 必须包含唯一键(如
ORDER BY salary, id),否则相同 salary 的累计顺序不确定 - 如果业务要求“salary 为空时视为 0”,得提前
COALESCE(salary, 0),不能依赖函数默认跳过 NULL

















