sql server无内置众数函数,需用窗口函数实现:先用count() over统计频次,再用row_number()或dense_rank()按频次排序取top1;前者返回单众数,后者保留所有并列众数,并需注意null处理与索引优化。

SQL Server里没有内置众数函数,得自己构造
SQL Server确实不提供 MODE() 这类聚合函数,所以必须靠窗口函数组合实现。核心思路是:先按分组和值计数,再对每个分组内计数值倒序排名,取排名为 1 的那个值——但要注意并列众数(多个值出现频次相同且最高)的处理策略。
用 COUNT() OVER + ROW_NUMBER() 实现单众数选取
这是最常用也最可控的做法:每个分组只返回一个众数值(即使有并列,也靠 ORDER BY 决定取哪个)。关键在于两层窗口计算的嵌套顺序:
SELECT group_col, value_col
FROM (
SELECT
group_col,
value_col,
COUNT(*) OVER (PARTITION BY group_col, value_col) AS freq,
ROW_NUMBER() OVER (
PARTITION BY group_col
ORDER BY COUNT(*) OVER (PARTITION BY group_col, value_col) DESC, value_col
) AS rn
FROM your_table
) t
WHERE rn = 1;
-
COUNT(*) OVER (PARTITION BY group_col, value_col)算出每个「组+值」组合的出现次数 -
ROW_NUMBER()在每个group_col内,按频次降序、再按value_col升序排,确保结果可复现 - 不能把
COUNT写在ORDER BY里直接引用——SQL Server 不允许在窗口排序中直接用未命名的聚合表达式,必须提前算好作为列
遇到并列众数时,COUNT(*) OVER 和 DENSE_RANK() 的区别
如果想保留所有并列众数(比如某组里 A 和 B 都出现 5 次,且是最高频),就不能用 ROW_NUMBER(),得换 DENSE_RANK():
SELECT group_col, value_col
FROM (
SELECT
group_col,
value_col,
DENSE_RANK() OVER (
PARTITION BY group_col
ORDER BY COUNT(*) OVER (PARTITION BY group_col, value_col) DESC
) AS dr
FROM your_table
) t
WHERE dr = 1;
-
ROW_NUMBER()强制给并列项不同序号,适合“只要一个”场景 -
DENSE_RANK()对相同频次赋予相同排名,适合“全都要”场景 - 注意:这里
ORDER BY里没加二级排序(如value_col),否则会破坏并列判断——DENSE_RANK()只看排序表达式的值是否相等
性能和 NULL 值的现实坑点
真实表数据常含大量 NULL,而 COUNT(*) 会忽略 NULL,但 GROUP BY 或 PARTITION BY 中的 NULL 是独立分组的。这意味着:
- 若
value_col允许为NULL,且某组中NULL出现最多次,它会被当作众数返回——这未必符合业务预期 - 解决方案:在子查询里加
WHERE value_col IS NOT NULL,或用COALESCE(value_col, '<null>')</null>统一处理 - 大数据量下,双重
OVER子句可能引发重复扫描;如有合适索引(如(group_col, value_col)),执行计划通常能走索引查找,否则容易触发 Sort 算子拖慢速度
真正难的不是写出语句,而是想清楚“当多个值并列最高频时,业务上到底要哪一个”。这个选择决定了该用 ROW_NUMBER() 还是 DENSE_RANK(),也决定了要不要预处理 NULL。










