DISTINCT:去重
本教程共 50 篇 · 第 26 篇 · 更新于 2026-07-31
26. DISTINCT:去重
本节目标:学完本章你能用 SELECT DISTINCT 去掉查询结果里的重复行,理解单列去重与多列组合去重的区别,并知道它和 GROUP BY 的取舍。
为什么需要去重
SELECT 默认是”你查什么、它返什么”,如果底层数据本身有重复,或者你的查询只挑了部分列,结果集里就可能出现一模一样的行。比如你想看”用户都来自哪些城市”,只查 city 这一列:
sqlite> SELECT city FROM users ORDER BY city;
一个城市往往对应很多用户,于是 北京、上海 会出现很多次。但你只关心”有哪些不同的城市”,重复的出现纯属噪音。这时就要用 DISTINCT 把重复行合并掉,每个不同的值只保留一行。
DISTINCT 是 SELECT 的一个可选关键字,必须紧跟在 SELECT 之后、列名之前。
单列去重
sqlite> SELECT DISTINCT city FROM users ORDER BY city;
现在每个城市只出现一次。这里虽然我们的 users 表示例里没有 city 列,你可以把它想象成任意会重复的 TEXT 列(比如 email 的域名部分)。为了贴合全局统一表,我们用 users 表的 age 演示”有哪些不同的年龄值”:
sqlite> SELECT DISTINCT age FROM users ORDER BY age;
结果就是去重后的年龄清单,从最小到最大排列。
多列组合去重
DISTINCT 后面可以跟多列,用逗号分隔。这时它判断”是不是重复行”的依据,是这些列的组合——只有当多列的值都完全一样时,才视为重复、只保留一行。
sqlite> SELECT DISTINCT age, name FROM users ORDER BY age;
注意:这里”重复”指的是 (age, name) 这一对组合相同。如果有两个人都叫”张三”但年龄不同,那 (20,'张三') 和 (25,'张三') 是不同组合,都会保留。只有两列都相同才会被合并。
Note重点提示:
DISTINCT永远是看”整行选中列的组合”,而不是某一列单独去重。很多人误以为SELECT DISTINCT age, name会”把 age 去重、把 name 也去重”,其实不是——它是把(age, name)这对组合去重。想单独对某一列去重,就只在DISTINCT后写那一列。
DISTINCT 与 NULL 的关系
SQLite 把 NULL 当作”重复值”来对待:如果某列里有多个 NULL,DISTINCT 只会保留一个 NULL 行。这和 SQL 标准一致——在去重语义里,所有 NULL 被视为相等。
sqlite> SELECT DISTINCT email FROM users;
如果某些用户没填邮箱(email 为 NULL),结果里 NULL 只出现一次,而不是每行一个。写统计”有多少不同邮箱”时要留意:这些 NULL 也算作一个”不同值”。如果只想统计”非空的、不同的邮箱数”,用 COUNT(DISTINCT email) 并配合 WHERE email IS NOT NULL。
DISTINCT 配合聚合函数
DISTINCT 不仅能用在 SELECT 顶层,还能塞进聚合函数里,表示”先对列去重,再聚合”。最典型的是 COUNT(DISTINCT 列):
sqlite> SELECT COUNT(DISTINCT user_id) FROM orders;
这条算出”有多少个不同的用户下过单”,而不是订单总行数。如果用户 user_id = 1 下了 10 单,普通 COUNT(user_id) 会数成 10,而 COUNT(DISTINCT user_id) 只算 1,这才是”活跃用户数”的正确口径。
DISTINCT 与 GROUP BY 的取舍
很多”取唯一组合”的需求,用 GROUP BY 也能做:
sqlite> SELECT age, name FROM users GROUP BY age, name;
效果上,GROUP BY age, name 和 SELECT DISTINCT age, name 在”去重组合”这件事上结果类似。两者区别在意图:
DISTINCT语义是”我只要不重复的行”,通常不附带聚合;GROUP BY语义是”我要分组并进一步统计”,经常跟着COUNT/SUM等聚合。
如果只是想去重查看,用 DISTINCT 更直白;如果需要”去重的同时再算每组的数量/总额”,用 GROUP BY 更自然。顺带一提,GROUP BY 默认也会对每个组产出唯一一行,所以单纯去重时两者可互换,但 DISTINCT 可读性更好。
Warning常见坑:别把
DISTINCT当成”让查询更快”的开关。去重意味着 SQLite 内部要先给结果排序或建哈希来识别重复行,数据量大时反而有额外开销。只对确实需要唯一值的列加DISTINCT,不要无脑给每个查询都套上。
Tip实用技巧:想看”去重后到底有多少个不同值”,两层写法任选:
SELECT COUNT(*) FROM (SELECT DISTINCT 列 FROM 表);或者直接SELECT COUNT(DISTINCT 列) FROM 表。后者更简洁,是统计”不同值个数”的惯用法。
适用场景小结
- 枚举维度值:“都有哪些年龄 / 城市 / 分类”——单列
DISTINCT。 - 唯一组合清单:“有哪些不同的 (年龄, 姓名) 配对”——多列
DISTINCT。 - 去重计数:“有多少不同用户”——
COUNT(DISTINCT user_id)。 - 配 NULL 时小心:
NULL只保留一个,统计口径要想清楚是否排除。
DISTINCT 结果没有默认顺序
DISTINCT 只负责”去重”,不保证返回的顺序。同一次查询里,去重后的行以什么次序出现,SQLite 不承诺稳定。所以只要你对顺序有要求(几乎总是有),就要显式加上 ORDER BY:
sqlite> SELECT DISTINCT age FROM users ORDER BY age;
别依赖”上次跑出来是排好序的”就以为永远如此,那是巧合不是保证。
多列去重的一个易错点
回到多列组合去重:判断重复看的是”选中列的整体组合”。这意味着下面两条语句结果不一定一样:
sqlite> SELECT DISTINCT age, name FROM users;
sqlite> SELECT DISTINCT name, age FROM users;
两者都是对 (age, name) 组合去重,只是输出时列的顺序换了——去重口径相同,只是展示列次序不同。真正容易错的是:有人以为”前面写 age 就先按 age 去重、后面的 name 随便留一个”,这是误解。DISTINCT 不会”先定一个列再去重另一列”,它永远是对整组选中列做唯一化。想对某一列取”每组一个代表行”(比如每个年龄留一个名字),那要用到 GROUP BY 或窗口函数,而不是 DISTINCT。
SQLite 没有 DISTINCT ON
顺带提一个容易从别的数据库带过来的习惯:PostgreSQL 支持 SELECT DISTINCT ON (age) ...,表示”按 age 去重、每组取第一行”,但SQLite 不支持这个语法。在 SQLite 里要实现”每组取一个代表”,正确路线是 GROUP BY 配合聚合(如 MIN(name) 取该组名字最小的),或后续章节会讲的窗口函数。别把别的数据库的写法直接搬过来,会直接报错。
类比小结
DISTINCT 像是你对着一长串名单”把念过名字的人划掉,后面再出现就不算了”。单列去重就是只盯一个属性划重,多列去重是盯着”几个属性的组合”一起划重——组合不同就不算重复。NULL 在这个规则里被当成同一个”空座位”,只留一个。需要”去重 + 顺便统计每组多少”时,交给 GROUP BY;纯粹只想看不重复的清单,用 DISTINCT 最清爽。但记住:去重不是免费的,它要付出排序的代价,别滥用。