PERCENT_RANK()返回的是比当前行小的行数占比,公式为(rank-1)/(total_rows-1),结果在0.0到1.0之间;需配合PARTITION BY分组、显式处理NULL,并避免用其等于0.5找中位数。

PERCENT_RANK() 返回的到底是不是“前 X%”
不是。它返回的是「比当前行小的行数占比」,公式是 (rank - 1) / (total_rows - 1),结果恒在 0.0 到 1.0 之间。比如 PERCENT_RANK() = 0.95 表示该行排在全组倒数 5% 的位置——即有 95% 的记录严格小于它,而不是“超过 95% 的人”。业务上想筛选“性能前 5%”,条件必须写成 PERCENT_RANK() >= 0.95,反着写会选错。
分组内排名必须显式写 PARTITION BY,否则全是全局排名
漏掉 PARTITION BY 是最常见错误。例如按服务模块(service_name)评估响应时间,若只写 ORDER BY response_time DESC,PostgreSQL 会把所有模块混在一起排序,导致某个低流量模块的 P99 响应时间被算成全站前 10%,完全失真。
正确写法要同时指定分组和方向:
SELECT
service_name,
request_id,
response_time,
PERCENT_RANK() OVER (
PARTITION BY service_name
ORDER BY response_time DESC
) AS pct_rank
FROM api_logs;注意:ORDER BY response_time DESC 才能让高耗时排前面、pct_rank 小值对应差性能,符合运维直觉;用 ASC 会把最快请求标成 1.0,后续过滤极易出错。
NULL 值不处理会污染分母和排序位置
PostgreSQL 默认把 NULL 排在最前(ASC)或最后(DESC),但 PERCENT_RANK() 仍参与计算:一个含 100 行、其中 5 行 response_time IS NULL 的分组,分母仍是 99,但 NULL 行会占据前 5 个 rank,拉低其余行的 pct_rank 值。
稳妥做法是显式控制空值位置,并在业务逻辑中决定是否排除:
- 想让 NULL 当作最差性能(如超时未上报):加
NULLS FIRST,并保留它们参与计算 - 想排除异常数据:在 WHERE 中提前过滤,如
WHERE response_time IS NOT NULL - 想让 NULL 排最后且不干扰有效值分布:用
ORDER BY response_time DESC NULLS LAST
别直接用 PERCENT_RANK() = 0.5 找中位数
因为它是离散计算,只对特定行数(奇数且无重复)才恰好产出 0.5。例如 4 行数据的 PERCENT_RANK() 结果是 0.0, 0.33, 0.66, 1.0,根本不存在 0.5 —— 这会导致 WHERE PERCENT_RANK() = 0.5 返回空。
真要算中位响应时间,推荐更稳定的方式:
WITH ranked AS (
SELECT
response_time,
ROW_NUMBER() OVER (ORDER BY response_time) AS rn,
COUNT(*) OVER () AS cnt
FROM api_logs
WHERE response_time IS NOT NULL
)
SELECT AVG(response_time::NUMERIC) AS median
FROM ranked
WHERE rn IN (
FLOOR((cnt + 1) / 2.0),
CEIL((cnt + 1) / 2.0)
);这个写法不依赖 PERCENT_RANK() 的浮点精度匹配,也兼容重复值和偶数行,是生产环境更可靠的选择。


















