结论:用DENSE_RANK()去重必须配合子查询或CTE,因其是窗口函数,执行晚于WHERE/GROUP BY,不能直接在WHERE中使用;它仅打标签,真正去重需外层筛选。

直接说结论:用 DENSE_RANK() 做去重,必须配合子查询(或 CTE),不能在 WHERE 或 GROUP BY 中直接调用;它本身不删数据,只是打排名标签,真正去重得靠外层筛选。
为什么不能直接 WHERE DENSE_RANK() = 1
DENSE_RANK() 是窗口函数,执行顺序晚于 WHERE 和 GROUP BY。你在 WHERE 里写 DENSE_RANK() OVER (PARTITION BY id ORDER BY date DESC) = 1,Oracle 会报错 ORA-30483: window functions are not allowed here。
常见错误现象:语句看起来合理,但一执行就报语法错误,不是数据不对,是 SQL 执行阶段不允许。
- 窗口函数只能出现在 SELECT 列表或 ORDER BY 中
- WHERE、HAVING、GROUP BY 都无法引用窗口函数结果
- 想“按排名过滤”,必须把窗口计算放在子查询里,再在外层加 WHERE
标准写法:子查询套一层 + WHERE 过滤排名
典型场景:每个 user_id 只取最新一条登录记录(按 login_time 降序)。
SELECT user_id, login_time, ip_address
FROM (
SELECT user_id, login_time, ip_address,
DENSE_RANK() OVER (PARTITION BY user_id ORDER BY login_time DESC) AS drk
FROM logins
)
WHERE drk = 1;
注意点:
-
PARTITION BY user_id决定“每组内排名”,别漏写 -
ORDER BY login_time DESC决定谁排第一,升序/降序直接影响结果 - 如果同一
user_id有多个相同最大login_time,DENSE_RANK()会给它们都标 1 —— 这是“去重”但不是“唯一行”,要严格一行就得换ROW_NUMBER()
DENSE_RANK() 和 ROW_NUMBER() 在去重时的区别
两者都能用于“选每组头一条”,但行为不同,直接影响结果是否确定。
假设用户 1001 有两条记录,login_time 都是 2026-06-10 09:00:00:
-
DENSE_RANK()→ 两行都标drk = 1,外层WHERE drk = 1返回 2 行 -
ROW_NUMBER()→ 两行中随机(按 Oracle 内部顺序)给rn = 1和rn = 2,外层只返回 1 行
所以:
- 要“稳定返回且允许并列”,用
DENSE_RANK() - 要“强制单条、不care哪条胜出”,用
ROW_NUMBER() - 从不去依赖
ORDER BY中未指定字段的排序稳定性 —— Oracle 不保证相同值下的物理顺序
性能和可读性提醒
子查询 + DENSE_RANK() 会全表扫描并排序,大数据量时很慢。
- 确保
PARTITION BY字段和ORDER BY字段上有合适索引,例如(user_id, login_time DESC) - 如果只是取最新时间值(不要整行),用
GROUP BY + MAX()更快,比如SELECT user_id, MAX(login_time) FROM logins GROUP BY user_id -
DENSE_RANK()的真正价值在于需要“带原始字段的去重”,比如除了时间还要取ip_address或device_type,这时聚合函数没法直接满足
真正容易被忽略的是:你写的 DENSE_RANK() 是否真需要它——很多时候 ROW_NUMBER() 更符合业务对“唯一结果”的隐含要求,而开发者只因名字里有“dense”就下意识选它。


















