MySQL 去重查询怎么写?附 3 种常见写法一次讲清

编程狮(w3cschool.cn) 2026-09-01 16:04:12 浏览数 (57)
反馈

MySQL 去重查询最直接的写法是 SELECT DISTINCT 列:在 SELECT 后面加 DISTINCT,数据库会对指定列做联合去重,只返回不重复的结果。本文讲清 3 种主流去重写法:SELECT DISTINCT、GROUP BY 分组、以及子查询或窗口函数,并说明它们各自的适用场景和容易踩的坑。你会看到「多列怎么去重」「NULL 算不算重复」「GROUP BY 和 DISTINCT 谁更合适」这些真实开发里常被问到的问题。读完你不仅能写出正确的去重 SQL,还知道哪种写法该用在哪个场景。今天编程狮就把这块讲透。

MySQL 去重查询方法图

一、为什么要去重查询

去重查询,本质是从一组可能重复的行里,只拿走「不重复」的那一份。最常见的场景有三:一是报表统计,比如统计一共有多少个不重复的城市、多少种不重复的商品类目,如果直接 COUNT 会把重复值算重;二是数据清洗,源数据因为多次导入、多表关联、日志重复上报,常常出现一模一样的行,查之前先去重才能保证口径正确;三是维度探查,你想看某张表里到底有哪些不同取值,比如订单状态有几种、用户等级有哪几档。

去重和「分组聚合」经常被混为一谈,但它们目标不同:去重要的是「不重复的行本身」,分组要的是「按某列归类后算一个汇总值」。比如「每个城市下单数」是分组,「城市有哪些不重复取值」是去重。理清这两者的边界,才知道该用 DISTINCT 还是 GROUP BY。另外,去重还常和「找重复数据的反面」一起出现——有时你不是要去掉重复,而是要找出哪些值重复了,这种场景往往用 GROUP BY 加 HAVING 更顺手。把需求说清楚:是要唯一值、还是要每组的统计,MySQL 去重查询的写法就跟着定了。

二、方法一:SELECT DISTINCT(最推荐)

适合谁:只想对一列或多列做去重,不需要任何聚合计算。代价:它只负责去重,不能顺便算 COUNT、SUM 这类汇总。

SELECT DISTINCT 的语法非常直白:在 SELECT 后面、列名前面加上 DISTINCT 关键字,MySQL 就会先按你列出的所有列做联合去重,再返回结果。

SELECT DISTINCT city
FROM orders;


-- 多列联合去重:只有 city 和 status 同时相同才被视为重复
SELECT DISTINCT city, status
FROM orders;

上面第一段是单列去重,返回 orders 表里所有不重复的 city;第二段是多列去重,只有 city 和 status 两个字段都一样的行才会被合并成一行。注意 DISTINCT 作用的是「它后面跟着的所有列的组合」,不是只对第一列生效,这点新手很容易误以为只去重 city。

单列去重最常见于维度探查,比如看用户表有哪些不重复的省份。多列去重则适合「某一组合是否出现过」这类判断,例如判断某个用户在某天是否下过单。想系统学 MySQL 基础,MySQL 入门教程 里有一页讲透。

性能上,DISTINCT 背后通常会触发一次排序或哈希去重,数据量大时会有开销。如果只需要知道「去重后有多少行」,优先用 COUNT(DISTINCT col) 而不是先把全部 DISTINCT 结果拉出来再数。一个隐藏坑:DISTINCT 和 ORDER BY 一起用时,ORDER BY 的列必须出现在 SELECT 列表里,否则 MySQL 会报错。SELECT DISTINCT 是 MySQL 去重查询最基础也最常用的入口,写起来最短、语义最清楚。

三、方法二:GROUP BY(去重还能顺手统计)

适合谁:去重的同时还要顺手做聚合,比如「每个城市有多少订单」。代价:写法比 DISTINCT 略长,且必须配合聚合函数才有意义。

GROUP BY 的思路是「按列分组,每组留一行」,当你只 SELECT 分组列、不加任何聚合函数时,它天然就起到了去重效果——每组只返回一行。

-- 只取分组列,等价于 SELECT DISTINCT city
SELECT city
FROM orders
GROUP BY city;


-- 去重 + 顺手聚合
SELECT city, COUNT(*) AS order_cnt
FROM orders
GROUP BY city;

第一段只 SELECT city 并 GROUP BY city,效果和 SELECT DISTINCT city 一样,都是返回不重复的 city;第二段则多算了每个城市的订单数,这是 DISTINCT 做不到的。所以当你「既要去重、又要统计」,GROUP BY 是更自然的选择,不必先 DISTINCT 再另外查。

GROUP BY 在多列去重时也成立:GROUP BY city, status 等价于 SELECT DISTINCT city, status。但一旦你加了聚合函数,语义就从「去重」变成了「分组汇总」,返回的行数等于分组数,不再是原始去重行。GROUP BY 是 MySQL 去重查询里和 DISTINCT 最常拿来对比的写法,二者在「只取唯一值」时结果一致,区别在能不能聚合。

一个常见误用是 SELECT 了没在 GROUP BY 里的列却又没包聚合函数,这在 ONLY_FULL_GROUP_BY 开启时会直接报错;老版本 MySQL 则会随便挑一行返回,结果不可控。所以写 GROUP BY 时,SELECT 列表里的每一列要么在 GROUP BY 中,要么被聚合函数包裹,这个原则记牢就不会翻车。

四、方法三:子查询 / 窗口函数(保留整行)

适合谁:要去重、但又想保留整行其它字段,或者只要「每组最新的那一条」。代价:写法最复杂,窗口函数对 MySQL 版本有要求(8.0+)。

前两种写法有个共同短板:DISTINCT 和 GROUP BY 都会把行「压扁」,你只能拿到去重列,拿不到同一行上其它的字段。但真实需求常常是「每个用户只保留最新一笔订单」,这时候需要的是整行,而不是被压扁的一列。

-- 窗口函数:给每个用户的订单按时间编号,再取编号 1 的行
SELECT *
FROM (
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
  FROM orders
) t
WHERE rn = 1;

这段用 ROW_NUMBER() 按 user_id 分区、按 created_at 倒序编号,编号 rn = 1 的就是每个用户最新的一笔订单,外层 WHERE rn = 1 把它们筛出来。它做的是「每组取一行」,比 DISTINCT 灵活得多,因为你可以在 PARTITION BY 里放去重键、在 ORDER BY 里决定留哪一行。聚合函数的用法可以看 MySQL 函数速查

窗口函数这种写法,是 MySQL 去重查询在「保留整行」场景下的首选。低版本没有窗口函数时,可以用「子查询加聚合先找极值,再 JOIN 回原表」达到同样效果,但 SQL 会明显更长、更难读。一个坑:ROW_NUMBER 的 PARTITION BY 列如果有 NULL,NULL 会被当作相同的值分到同一组,这和 DISTINCT 把 NULL 视为相同的语义一致,但和你直觉可能不同,写之前要想清楚 NULL 该不该参与去重。

五、怎么选 + 坑(NULL / 多列)

三种写法怎么选,记住一句话:只去重用 DISTINCT,去重还要统计用 GROUP BY,要去重还保留整行用窗口函数。下面把高频坑列出来。

  • NULL 值算重复吗:算。DISTINCT、GROUP BY、窗口函数的 PARTITION BY 都把多个 NULL 值视为彼此相同,会合并成一行。如果你希望 NULL 不被合并,需要显式用 IS NULL 分支或 COALESCE 转成占位值再处理。
  • 多列去重的语义:SELECT DISTINCT col1, col2 是「两列组合相同才算重复」,不是「分别去重」。想分别对两列去重再笛卡尔组合,DISTINCT 做不到,得用两个子查询 UNION 或分别查询。
  • 性能别乱选:DISTINCT 和「只 SELECT 分组列的 GROUP BY」在多数情况下执行计划相近,别盲目认为 GROUP BY 一定更快;真正决定快慢的是索引——给去重列建索引,往往比纠结写法更管用。
  • COUNT(DISTINCT) 的坑:COUNT(DISTINCT col1, col2) 支持联合去重计数,但多个列时无法分别得到每列的基数,需要分开写两次 COUNT(DISTINCT)。

踩坑清单记这四条。NULL 值那条最容易被忽略——你以为 NULL 是「未知」不该算相同,但 MySQL 去重查询里它就是当作相同处理的,报表口径一旦错,数字会悄悄对不上。多列那条则是「过度期待」,以为 DISTINCT 会分别给每列去重,结果拿到的是组合去重。把「组合去重」和「分别去重」分清楚,SQL 就不会写偏。窗口函数那条属于版本约束,团队若还停在 5.7,就别硬上 ROW_NUMBER,老老实实用子查询加 JOIN。MySQL 去重查询整体就这三招,配上这几个坑,日常去重基本不会再卡壳。

MySQL 去重查询怎么选

总结

MySQL 去重查询,日常首选 SELECT DISTINCT,语法最短、语义最清楚,适合只拿唯一值;去重的同时还要做统计,用 GROUP BY 更自然,一组一行还能顺手算 COUNT、SUM;要去重但必须保留整行其它字段、或者只要每组最新一条,用窗口函数 ROW_NUMBER() 最灵活。要点带走:

  • DISTINCT 作用的是它后面所有列的组合,不是只对第一列生效;
  • DISTINCT 与「只 SELECT 分组列的 GROUP BY」在纯去重时结果一致,区别在能否聚合;
  • NULL 值在三种写法里都被视为相同值会合并,多列去重是「组合去重」而非「分别去重」。

把「只要唯一值 / 要统计 / 要整行」这三类的边界厘清,MySQL 去重查询的写法就不会选错。

延伸学习

想把这块知识系统补齐,可以按这个顺序来:

  1. 写完想立刻试,文本去重工具 帮你快速处理本地重复文本;
  2. 想看另一种讲法,参考 MySQL 去重实战笔记 的实战思路;
  3. 写完想直接跑,在线运行 SQLite 代码 免安装执行片段。

常见问题

Q:SELECT DISTINCT 是对哪一列去重?

A:DISTINCT 作用的是它后面跟着的所有列的组合,不是只对第一列。比如 SELECT DISTINCT city, status 是 city 和 status 都相同才合并。如果只想对单列去重,只把那一列写在 DISTINCT 后面即可。

Q:NULL 值在去重时算重复值吗?

A:算。DISTINCT、GROUP BY、以及窗口函数的 PARTITION BY 都把多个 NULL 值视为彼此相同,会合并成一行。如果你不希望 NULL 被合并,需要先用 COALESCE 把 NULL 转成占位值,或单独用 IS NULL 分支处理。

Q:只要每组最新一条记录,DISTINCT 能办到吗?

A:办不到。DISTINCT 和 GROUP BY 都会把行压扁,只留去重列。要保留整行、且只要每组最新一条,得用窗口函数 ROW_NUMBER() 按分组键分区、按时间倒序编号,再筛 rn = 1 的行。低版本 MySQL 可用子查询先找极值再 JOIN 回原表。

1 人点赞