top + cross apply 仅适用于分组数少、n 小且分组字段有索引的场景;它通过外层分组值驱动内层top查询实现每组取前n,但易因distinct漏值或无索引导致数据丢失或性能雪崩,语义和性能均不如row_number()稳健。

TOP + CROSS APPLY 是可行的,但仅适用于“每组取前 N 条且 N 较小、分组数不多”的场景;它不是通用替代方案,性能和语义都容易出问题。
为什么不能直接用 GROUP BY + TOP
SQL Server 不允许在 GROUP BY 后直接用 TOP 取每组前 N 条——TOP 作用于整个结果集,不是每个分组。写成 SELECT TOP 3 * FROM t GROUP BY group_col 会报错或逻辑完全错误(比如只返回全表前 3 行,根本没分组)。
CROSS APPLY 怎么配合 TOP 实现分组取前N
本质是把“对每个分组执行一次 TOP 查询”显式写出来:外层扫一遍唯一分组值,内层用 CROSS APPLY 对每个分组值查出其 TOP N 记录。
- 必须先有分组维度的唯一值源(比如
SELECT DISTINCT group_col FROM t或主表的分组字段去重) -
CROSS APPLY子查询里要能关联到外层分组值,并带TOP N和确定性ORDER BY - 不能省略
ORDER BY,否则TOP结果不可靠(SQL Server 不保证无序时的行顺序)
示例(取每个部门工资最高的 2 人):
SELECT d.dept_id, a.emp_id, a.name, a.salary
FROM (SELECT DISTINCT dept_id FROM employees) d
CROSS APPLY (
SELECT TOP 2 emp_id, name, salary
FROM employees e
WHERE e.dept_id = d.dept_id
ORDER BY salary DESC, emp_id ASC
) a;
ROW_NUMBER() 和 CROSS APPLY + TOP 的关键差异
两者都能实现目标,但行为和成本完全不同:
-
ROW_NUMBER()是一次性扫描全表,按分组+排序生成序号,再过滤——适合大数据量、分组多、N 稍大的情况 -
CROSS APPLY + TOP是“循环驱动”:外层每有一个分组值,就触发一次内层索引查找(如果dept_id有索引);分组数少、N 小、且分组字段有高效索引时,可能更快 - 如果分组字段无索引,
CROSS APPLY会变成对全表做 N 次扫描,性能雪崩 -
CROSS APPLY无法处理“并列名次需去重”或“严格保证恰好 N 条”等复杂需求,而ROW_NUMBER()可控性强得多
最容易被忽略的陷阱
很多人抄了 CROSS APPLY 写法却没意识到:它依赖外层分组值的完整性。如果外层 DISTINCT 源漏掉了某个实际存在的 dept_id(比如 NULL 值被 DISTINCT 过滤、或来自 JOIN 中未覆盖的分支),那对应分组的数据就彻底丢失——而 ROW_NUMBER() 是全表扫描,不存在这种隐式截断。











