用PERCENT_RANK()配合PARTITION BY和ORDER BY可精准取每组前50%,但单行组返回NULL;NTILE(2)仅尽量均分,不保证恰好50%,易致业务偏差。

直接用 PERCENT_RANK() 配合 PARTITION BY + ORDER BY 就能精准取每组前 50%,但必须注意分组内行数是否 ≥2,否则单行组会返回 NULL。
为什么不用 NTILE(2)?
NTILE(2) 看似能分两组,但实际行为不可控:当某组总行数为奇数(比如 5 行),NTILE(2) 会分配成 3+2,第一组占 60%;若为 7 行,则是 4+3,第一组占 ~57%。它不保证“恰好前 50%”,只保证“尽量均分”。而业务上常需严格语义——比如“每个部门薪资最高的那批人,数量不超过该部门人数的一半”。这时 NTILE 的边界模糊会直接导致结果偏差。
PERCENT_RANK() 的过滤写法与陷阱
正确结构是子查询套一层,再在外部 WHERE 过滤:
SELECT emp_id, dept_id, salary
FROM (
SELECT emp_id, dept_id, salary,
PERCENT_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS pct_rank
FROM employees
) t
WHERE pct_rank < 0.5;-
ORDER BY salary DESC决定“高值在前”,才对应前 50%;若用 <code>ASC,就是最低的 50% - 必须显式写
PARTITION BY dept_id,漏掉就变成全表排序,所有人在一个大组里算排名 - 空值(
NULL)默认排在DESC最后、ASC最前;若salary可为空,建议加NULLS LAST(PostgreSQL/Oracle)或先WHERE salary IS NOT NULL -
PERCENT_RANK()第一行恒为0.0,最后一行在行数 ≥2 时才是1.0;所以是安全的开区间,不会意外漏掉刚好卡在 0.5 的边界行(它不存在)
MySQL 8.0+ 和 SQL Server 的兼容性提醒
PERCENT_RANK() 在 MySQL 8.0+、SQL Server 2012+、PostgreSQL 8.4+、Oracle 10g+ 均可用,但行为有细微差别:
- MySQL 8.0 不支持
NULLS FIRST/LAST,处理空值只能靠前置过滤或COALESCE() - SQL Server 对单行分组返回
NULL,不是0.0;若某部门只有 1 个员工,该行会被WHERE pct_rank 过滤掉——这是预期行为,但容易被忽略 - 所有数据库都强制要求
ORDER BY,写成OVER(PARTITION BY x)会报错,错误信息类似"Window function PERCENT_RANK requires ORDER BY"
真正容易出问题的地方不在语法,而在对“50%”的理解:它指位置比例,不是数值阈值。比如某组 100 行,第 50 名和第 51 名分数相同,PERCENT_RANK() 会给它们同值(比如都是 0.4949),那么 会把这两人全留下——这是正确行为,但若你本意是“只取严格前 50 行”,就得换 <code>ROW_NUMBER() + CEIL(COUNT(*) * 0.5)。

















