distinct on 必须位于最外层 select 且严格匹配紧随其后的 order by 前缀字段顺序,子查询中使用会因缺少对应 order by 或层级隔离导致报错或结果不可靠。

DISTINCT ON 本身不支持嵌套子查询直接“配合”——它必须出现在最外层 SELECT 中,且 ORDER BY 必须紧随其后;试图在子查询里用 DISTINCT ON 再被外层引用,通常会因语法或语义错位导致结果不可靠。
为什么不能把 DISTINCT ON 放进子查询里?
PostgreSQL 要求 DISTINCT ON 和 ORDER BY 必须在同一查询层级、且顺序严格绑定。如果写成:
SELECT * FROM ( SELECT DISTINCT ON (user_id) user_id, login_time, ip FROM login_log ) AS t ORDER BY user_id;
这段代码会报错:ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions —— 因为子查询里没写 ORDER BY,而外层的 ORDER BY 对子查询里的 DISTINCT ON 完全无效。
-
DISTINCT ON不是函数,不能被“返回值化”或“管道传递” - 子查询若不含
ORDER BY,DISTINCT ON的行为未定义(实际执行可能随机选行) - 即使子查询加了
ORDER BY,外层再排序或过滤,也可能破坏内层分组逻辑(比如外层LIMIT截断前就已丢失某组首行)
真正可行的嵌套结构:子查询只做数据准备,DISTINCT ON 在顶层
想实现“先过滤再分组取最新”,正确做法是让子查询负责 WHERE / JOIN / 计算,把干净数据交给外层 DISTINCT ON 处理:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
SELECT DISTINCT ON (user_id) user_id, login_time, ip, device
FROM (
SELECT user_id, login_time, ip, device
FROM login_log
WHERE login_time >= '2026-06-01'
AND status = 'success'
) AS filtered
ORDER BY user_id, login_time DESC, id DESC;
- 子查询
filtered只做裁剪,不碰去重逻辑 -
DISTINCT ON仍在外层,且ORDER BY字段顺序与DISTINCT ON完全一致(user_id,login_time,id) - 末尾的
id DESC是防时间重复的必要 tie-breaker,不能省
替代方案:用 CTE 替代子查询更清晰
CTE 比嵌套子查询更适合表达多步逻辑,且 PostgreSQL 对 CTE 中 DISTINCT ON 的支持更稳定:
WITH recent_logs AS ( SELECT user_id, login_time, ip, device, id FROM login_log WHERE login_time >= NOW() - INTERVAL '7 days' ) SELECT DISTINCT ON (user_id) user_id, login_time, ip, device FROM recent_logs ORDER BY user_id, login_time DESC, id DESC;
- CTE 名称
recent_logs明确表达了意图,比匿名子查询易读 - 所有字段(包括用于排序但不输出的
id)必须在 CTE 中 SELECT 出来,否则外层ORDER BY会报column "id" does not exist - 如果 CTE 返回 100 万行,而你只想要 top 10 个用户最新记录,别在外层加
LIMIT 10—— 这会先取全部分组首行再截断,应改用WHERE user_id IN (...)或提前限制
容易被忽略的性能陷阱:索引必须覆盖 DISTINCT ON + ORDER BY 前缀
哪怕逻辑完全正确,没建对索引也会让查询慢几秒甚至超时:
- 对于
DISTINCT ON (user_id) ... ORDER BY user_id, login_time DESC, id DESC,推荐索引:CREATE INDEX idx_login_user_time_id ON login_log (user_id, login_time DESC, id DESC) - 索引列顺序必须和
ORDER BY前缀完全一致;(user_id, id, login_time)这种顺序几乎没用 - 如果子查询里做了 JOIN 或复杂计算,索引可能失效——先 EXPLAIN 看是否走索引扫描,而不是假设“写了 DISTINCT ON 就快”
真正难的不是写对语法,而是确保每一步(过滤、排序、分组取首)都落在索引驱动路径上。漏掉一个 DESC 或调换两列顺序,优化器就可能放弃索引,退回到全表排序。










