distinct 去的是「整行组合」的重复,不是某一列自己的重复。
这一点是初学者最容易搞错的地方。
Demo 里 DISTINCT class_id 给 3 行,DISTINCT class_id, gender 给 6 行,
DISTINCT gender 给 2 行——
同一个 distinct,三种结果,差别只在后面写了几列。
count(distinct 列) 还能数出「有几种」。
上一篇限制行数,这一篇处理重复。 它是查询四件套里最后一个。
它也是最容易掩盖问题的一个:
结果里出现不该有的重复,加 distinct 就「看不见」了,但根因还在。
一、单列去重:有几种不同的值
SELECT DISTINCT 列 FROM 表 返回这一列所有不重复的值,等价于「这一列有几种」。
DISTINCT 作用于 SELECT 后面的所有列,
所以写一列就是「这一列去重」,写两列就是「组合去重」。
DISTINCT 必须紧跟在 SELECT 后面,写成 SELECT 列, DISTINCT 列2 是语法错。
二、多列去重:去的是组合
DISTINCT class_id, gender 去的是「班级和性别都相同」的行。
三个班两种性别,组合出 6 行——正好是 3 × 2。
它不等于「每列各自去重」:
后者会得到 3 + 2 = 5 个值,而且是拼不回去的。 组合去重才是 SQL 的行为。
记法:distinct a, b = 「把 (a, b) 当成一个整体看有几种」。
三、数一数:count(distinct 列)
COUNT(DISTINCT 列) 直接给「有几种不同的值」,比先 distinct 再数少一步。
COUNT(*) 与 COUNT(DISTINCT 列) 放一起,才知道去重掉了多少:Demo 里 12 行、3 种班级。
COUNT(列) 与 COUNT(DISTINCT 列) 也不一样:
前者不数 NULL(见第 4 章),后者也不数 NULL。 两个都不数 NULL,这是它们的共同点。
四、distinct 和 group by 的关系
SELECT DISTINCT a, b FROM t 与 SELECT a, b FROM t GROUP BY a, b 结果一样——
前者是后者的简写。
区别在「能不能带聚合」:distinct 只能去重、不能算;
group by 后面可以跟 count()/sum()。 要算就用 group by。
两个叠在一起基本没用(Demo 第五段):group by 已经把每组压成一行,再去重也没东西可去。
五、distinct 的代价,以及它暴露的问题
代价:去重要排序或建哈希表,数据量大时明显慢;用不上索引时会走临时表。
更值得警惕的是它的「症状」作用:
查询结果里出现不该有的重复,正确做法是查「为什么重复」——
多半是少写了 join 条件、或者一对多关系没处理。
加 distinct 把重复盖住,等于把问题藏起来:
数据一多,性能先垮;哪天去重盖不住了,结果就开始错。
六、小结
教材小结:distinct 用于去掉结果集中的重复行,可以作用于单列或多列组合。
本课补三条:一是它去的是「整行组合」的重复,不是单列各自去重;
二是 count(distinct 列) 直接数「有几种」;三是重复出现先查原因,别急着加 distinct。
七、动手 Demo:一个 distinct,三种结果
Demo 五段:单列去重、count(distinct)、多列组合去重、与 group by 叠加,最后讲代价。
第二段之后那个对比最值得看:distinct gender 给 2 行,
distinct class_id, gender 给 6 行——同一个关键字,两种含义。
import _q46 as Q
c = Q.con()
c.execute("SELECT DISTINCT class_id FROM students ORDER BY class_id")
c.execute("SELECT COUNT(*) AS all_rows, "
"COUNT(DISTINCT class_id) AS kinds FROM students")
# 多列:去的是「组合」重复
c.execute("SELECT DISTINCT class_id, gender FROM students "
"ORDER BY class_id, gender")
c.execute("SELECT DISTINCT gender FROM students")
# 与 group by 叠加基本没用
c.execute("SELECT DISTINCT class_id, COUNT(*) AS n FROM students "
"GROUP BY class_id")
实跑输出:
--------------------------------------------------------------------
第十四课:distinct,去的是「整行」不是「某一列」
--------------------------------------------------------------------
单列去重:班级有几种
SQL> SELECT DISTINCT class_id FROM students ORDER BY class_id
class_id
---------
1
2
3
(3 行)
**12 个学生只有 3 个班,去重后 3 行。**
去重之后数一数:count(distinct ...)
SQL> SELECT COUNT(*) AS all_rows, COUNT(DISTINCT class_id) AS kinds FROM students
all_rows kinds
----------------------
12 3
(1 行)
**`count(*)` 数行,`count(distinct 列)` 数「有几种不同的值」。**
**两个数放一起,才知道去重掉了多少。**
多列去重:distinct 作用于整行组合
SQL> SELECT DISTINCT class_id, gender FROM students ORDER BY class_id, gender
class_id gender
----------------------
1 女
1 男
2 女
2 男
3 女
3 男
(6 行)
**`distinct class_id, gender` 去掉的是「两个值都相同」的重复行。**
**它不等于「每列各自去重」——那是两件事。**
SQL> SELECT DISTINCT gender FROM students
gender
-------
男
女
(2 行)
**这一句给 2 行(男、女),但上一句给 6 行(班级与性别的组合)。**
**差别就在「去重的是哪几列的组合」。**
distinct 与聚合一起用:容易踩的一个坑
SQL> SELECT DISTINCT class_id, COUNT(*) AS n FROM students GROUP BY class_id
class_id n
----------------------
1 4
2 4
3 4
(3 行)
**这句里的 distinct 基本没用**:`group by` 已经把每个班合成一行了,再去重也没东西可去。
**distinct 和 group by 常常二选一,别叠加。**
distinct 的代价
**去重要排序或建哈希表**,数据量大时明显慢。
**更要紧的是**:
**`select distinct` 常常是「查询写错了」的症状**——本来该用 `group by` 或该加 `where`,
却用去重把重复行盖住。 **看到结果里有不该出现的重复,先去查为什么重复,别急着加 distinct。**
**性能上**:`distinct` 用不上索引时会走临时表;
**`group by` 在 8.0 里能吃到索引顺序**,同样的事优先用它。
第一段单列。 12 个学生 3 个班,去重后 3 行。
第二段两个数。 count(*) 给 12、
count(distinct class_id) 给 3——两个数放一起才知道去掉了 9 行。
第三段组合。 class_id, gender 给 6 行,正好 3 × 2。
第四段对照。 同一张表,distinct gender 只有 2 行——差别在于 distinct 作用于几列。
第五段叠加。 distinct class_id, count(*) 加 group by,
distinct 白写——group by 已经压成一行了。
30 秒实验
SELECT DISTINCT gender, class_id与SELECT DISTINCT class_id, gender行数一样吗?- (先想再跑)
SELECT COUNT(DISTINCT gender) FROM students给几? - 把第五段的
distinct去掉,结果会变吗?
常见误区
- 以为
distinct a, b是「a 去重、b 去重」。 它是把 (a, b) 当整体去重,结果行数完全不同。 - 用 distinct 掩盖 join 写错。 一对多没处理干净时结果会翻倍,加 distinct 只是把症状盖住, 数据一多还是要爆。
distinct与group by叠加。 基本没用,还多一次去重开销。
自测三问(点开对答案)
1. `DISTINCT a, b` 去的是什么? —— 去掉「a 和 b 都相同」的重复行,也就是把 (a, b) 当成一个整体去重。 2. `COUNT(DISTINCT 列)` 与 `COUNT(列)` 有什么区别? —— 前者数「有几种不同的值」,后者数「非空的行数」;两者都不统计 NULL。 3. 结果里出现重复,第一反应该做什么? —— 查为什么重复(join 条件、一对多关系),而不是先加 distinct 把重复盖住。查询四件套(select/where/order by/limit/distinct)齐了,
下一篇开始算数:sum/avg/max/min/count,以及分组。
- ☐ (本篇完,下一篇 4.1 算术运算)