首页/数据/SQL 速查/多列去重与 PostgreSQL DISTINCT ON

多列去重与 PostgreSQL DISTINCT ON

多列 DISTINCT 按组合去重;PostgreSQL 的 DISTINCT ON 能实现“每组保留一行”,MySQL 需要用窗口函数改写。

SQL 速查数据更新于 2026-10-13

多列去重与每组取一行

DISTINCT 作用于它后面的整个列清单,去重的是列的组合:只要任何一列不同,两行就被当成不同数据。日常需求里更常见的是另一种形态——每个分组只保留一行,比如每个城市只取一条代表记录。这件事必须附带一个选择规则,否则结果不可预期。PostgreSQL 为此提供了专门语法 DISTINCT ON,MySQL 没有对应写法,只能用窗口函数给每行打行号再筛出行号等于 1。两种写法结果可以完全一致,差别在可读性、执行方式与迁移成本。

基本信息

适用平台MySQL 8.0 / PostgreSQL 12 及以上
多列去重列清单写在 DISTINCT 之后,按组合去重,任一列不同即为不同行
每组取一行PostgreSQL 用 DISTINCT ON 配合排序,决定保留哪一行
MySQL 等价改写用窗口函数按分组打行号,外层筛行号等于 1
组合个数统计统一写成子查询:先取得去重结果,外层再计数,两库都能跑

典型使用场景

  • 导出唯一的「城市加状态」组合清单,作为筛选下拉框的选项
  • 每个城市保留一条代表记录:注册最早、金额最大或时间最新的一条
  • 组合个数统计:关心有多少种不同组合,而不是表里有多少行
  • 列表页按业务键去重后再分页,避免同一对象在相邻页重复出现
  • 从 PostgreSQL 迁移到 MySQL 时把 DISTINCT ON 批量改写成窗口函数
  • 核对上游重复数据:先看去重前后行数差多少,再决定是加唯一约束还是清洗数据

三种写法

三种写法
-- 一、多列组合去重:任一列不同即为不同行
SELECT DISTINCT city, status FROM users;

-- 二、PostgreSQL:每个城市保留姓名排序后的第一行
SELECT DISTINCT ON (city) city, name
FROM users
ORDER BY city, name;

-- 三、MySQL 等价改写:窗口函数打行号再筛
SELECT city, name
FROM (
  SELECT city, name,
         ROW_NUMBER() OVER (PARTITION BY city ORDER BY name) AS rn
  FROM users
) t
WHERE rn = 1;

-- 组合个数统计:子查询形式两库通用
SELECT COUNT(*) AS combos FROM (SELECT DISTINCT city, status FROM users) x;

写法语义对照

写法语义注意事项
DISTINCT 多列按列组合去重作用于整个清单,不是只对第一列去重
DISTINCT ON每个分组保留一行表达式必须是排序条件的最左前缀
窗口函数取行号每个分组保留一行,可移植写法更长,但两库行为一致
分组后取裸列看似能取一行,实际不确定严格模式下直接报错,不要采用
「每组取最新一行」的正确做法是窗口函数或 DISTINCT ON。写成按分组列聚合、再直接选出未参与聚合的列,属于结果不确定的写法:同一 SQL 在不同时间可能返回不同行,排查线上数据不一致时很难定位。
去重后紧跟分页要留意代价:数据库必须先拿到完整去重结果才能排序取前若干行,大表上非常慢。如果业务允许,先在业务键上加唯一约束,从写入侧治理重复,比查询侧去重更省资源。

易错点

  • DISTINCT ON 的表达式与排序条件的最左前缀不一致会报错,排序要按「分组列在前、取值列在后」组织
  • 把 DISTINCT 当成按某一列去重:加在整行上经常去不掉预期重复,应先想清楚保留哪一行
  • 分组后直接选出未聚合的列,结果不确定且严格模式下报错,属于必须修掉的写法
  • 去重后立刻分页:去重结果集先物化再排序,大表上开销明显,考虑从写入侧加约束治理
  • 多列计数照抄到另一种数据库可能不被接受,统一改成子查询形式可避免改写

数据来源与参考

  • MySQL 8.0 Reference Manual · DISTINCT
  • PostgreSQL Documentation · DISTINCT Clause
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌