distinct on 必须配合 order by 才能稳定取每组首行,分组字段需在 order by 首位且顺序一致;取最新记录需用 login_time desc nulls last,并配合适当复合索引。

DISTINCT ON 是 PostgreSQL 里倒序分组取第一行最直接、最高效的方式,但必须配合正确的 ORDER BY 才能稳定生效。
为什么 DISTINCT ON 必须跟 ORDER BY 一起用
单独写 DISTINCT ON (user_id) 不加 ORDER BY,结果是不确定的——PostgreSQL 可能返回任意一条匹配的记录,每次执行都可能不同。真正决定“哪条是第一行”的,是 ORDER BY 的排序逻辑。
-
DISTINCT ON本身不排序,它只按括号内表达式分组,并在每组中挑出ORDER BY排序后最靠前的那条 - 分组字段(如
user_id)必须出现在ORDER BY最前面,且顺序一致;否则报错或行为异常 - 如果分组字段相同,靠后面的排序字段(如
login_time DESC)才起作用,用于打破平局
DISTINCT ON 倒序取最新记录的写法
要取每个用户最新一次登录记录,核心是让“最新”排在每组最前面:
SELECT DISTINCT ON (user_id) user_id, login_time, ip, device FROM login_log ORDER BY user_id, login_time DESC, id DESC;
-
user_id在ORDER BY开头,和DISTINCT ON保持一致 -
login_time DESC确保时间大的排前面 → 最新记录优先被选中 -
id DESC是防重复的保险:当两个登录时间完全相同时,用主键确保唯一确定性
NULL 值导致结果错乱怎么办
如果排序字段(如 login_time)允许为 NULL,默认情况下 NULL 会被排在最前面(ASC)或最后面(DESC),这可能把空值记录误当成“最新”返回。
- 显式加上
NULLS LAST(对DESC)或NULLS FIRST(对ASC)来控制位置 - 索引也得同步声明
NULLS LAST,否则无法命中排序索引,性能会断崖下跌 - 正确写法示例:
ORDER BY user_id, login_time DESC NULLS LAST, id DESC
容易忽略的性能陷阱
DISTINCT ON 效率高,但前提是能走索引。如果 ORDER BY 字段没建合适索引,它就退化成全表扫描 + 内存排序。
- 推荐复合索引:
CREATE INDEX idx_login_log_user_time_id ON login_log (user_id, login_time DESC NULLS LAST, id DESC); - 避免在
ORDER BY里用函数或表达式(如date(login_time)),否则索引失效 - 如果查询带
WHERE条件,注意索引字段顺序是否覆盖过滤字段,否则仍可能慢
最常出问题的地方不是语法写错,而是 ORDER BY 和索引不匹配,或者忘了处理 NULL 的排序行为。










