CROSS APPLY 能实现分组 Top N,但仅在分组数少、N 小、外层分组键完整且有索引时更合适;多数场景下比 ROW_NUMBER() 更慢、更难控、易丢数据。
直接说结论:CROSS APPLY 能实现分组 Top N,但“灵活”是有代价的——它只在特定条件下比 ROW_NUMBER() 更合适,多数时候反而更难控、更慢、更容易丢数据。
CROSS APPLY 本质是“为每个外层值执行一次子查询”,所以它的“灵活性”仅体现在:子查询里能用外层字段做参数、TOP 数量可动态(比如 TOP (a.num)),还能配合 OUTER APPLY 保留空分组。但它不等于通用解法:
SELECT DISTINCT dept_id FROM employees 漏掉 NULL 或 JOIN 不全,对应分组就彻底消失ORDER BY,否则 TOP 结果不可复现TOP 只认行数,不认逻辑排名OFFSET ... FETCH 在 CROSS APPLY 子查询中使用,只能靠 TOP
当真需要按不同分组取不同数量(比如销售部取前 5,行政部取前 2),必须把 N 存进关联表或用 CASE 计算,不能硬编码:
SELECT d.dept_name, a.emp_id, a.salaryFROM departments dCROSS APPLY ( SELECT TOP ( CASE d.dept_name WHEN 'Sales' THEN 5 WHEN 'HR' THEN 2 ELSE 3 END ) emp_id, salary FROM employees e WHERE e.dept_id = d.dept_id ORDER BY salary DESC, emp_id ASC) a;
注意:TOP 后面的括号不能省,且表达式必须返回整数;ORDER BY 里加 emp_id ASC 是为了打破并列时的不确定性。
每执行一次内层子查询,SQL Server 都会尝试走索引查找——前提是 WHERE 条件字段(如 dept_id)上有索引。没有索引时:
N=1,也比 ROW_NUMBER() 一次性扫描慢得多验证方法:看执行计划里内层是否出现 Index Seek;如果全是 Table Scan 或 Clustered Index Scan,立刻停用 CROSS APPLY 方案。
遇到以下任一情况,CROSS APPLY + TOP 就该放弃:
N > 10(Top-20/Top-50 类需求)DENSE_RANK() / RANK() 更可控)JOIN,中间可能丢行)最易被忽略的一点:很多人抄了 CROSS APPLY 示例却没检查外层 DISTINCT 是否覆盖全部业务分组值——漏掉的组不会报错,只会静默消失。而 ROW_NUMBER() 是全表驱动,天然保底。