拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SQL去重:为什么加了DISTINCT,同一所学校还是出现多次?

SQL去重:为什么加了DISTINCT,同一所学校还是出现多次?

原来的题目很短:运营想知道用户来自哪些学校,从用户表里取出学校的去重数据。牛客原题:查询结果去重。

答案只有一行:

SELECT DISTINCT university FROM user_profile;

但会写这行,不代表已经理解去重。比如再加一列用户编号,为什么同一所学校又出现了?这不是 DISTINCT 失效,而是我们改变了“什么算重复”。

1. 先看一张只有必要字段的表

下面是独立实验表,不需要使用或修改已有的 user_profile。请在自己的练习数据库中执行,表名已存在时换一个名字。

CREATE TABLE sql3_distinct_demo ( id INT NOT NULL PRIMARY KEY, university VARCHAR(32), province VARCHAR(32) NOT NULL ); INSERT INTO sql3_distinct_demo VALUES (1, '北京大学', '北京'), (2, '北京大学', '北京'), (3, '复旦大学', '上海'), (4, '复旦大学', '其他'), (5, NULL, '北京'), (6, NULL, '上海');
iduniversityprovince
1北京大学北京
2北京大学北京
3复旦大学上海
4复旦大学其他
5NULL北京
6NULL上海

为了观察 NULL,这里允许学校为空;原题学校列是 NOT NULL,原题答案并不依赖这些扩展行。

2. 单列去重:我们只关心学校

SELECT DISTINCT university FROM sql3_distinct_demo;

结果集合是三项:北京大学、复旦大学、NULL。这里不列固定的先后顺序,因为 SQL 没有 ORDER BY 时不能依赖返回顺序。

可以把这个小例子想成:

先取出需要的列 再判断结果行是否重复 北京大学 北京大学 北京大学 复旦大学 复旦大学 -> 复旦大学 NULL NULL NULL

这只是理解查询语义的画法,不代表数据库一定先物化整张中间表,再做一次扫描;实际执行可能借助索引等方式。

3. 多列去重:相同学校不再等于相同结果行

SELECT DISTINCT university, province FROM sql3_distinct_demo;

结果集合变成五项:

universityprovince
北京大学北京
复旦大学上海
复旦大学其他
NULL北京
NULL上海

第 1、2 行的两列都相同,合成一项。第 3、4 行学校一样,但省份不同,所以必须保留两项。

DISTINCT 不是只修饰紧跟在它后面的第一列,而是对整个选出的结果行去重。MySQL 官方也给出了多列 DISTINCT 与按这些列 GROUP BY 的对应例子。官方说明。

所以这句也不会得到“一所学校只留一个用户”:

SELECT DISTINCT id, university FROM sql3_distinct_demo;

它仍然返回六行:id 每行不同,整个二元组就没有重复。

4. 去重不是删除,也不是挑一个代表

上面的查询不会删掉表里的第 2 行,也不会把第 4 行的学校改掉。

SELECT COUNT(*) AS original_rows FROM sql3_distinct_demo;

仍是 6。DISTINCT 改的是结果集合,不是原始数据。

如果需求是“一所学校挑一个用户”,还必须说明挑谁:最早注册的?编号最小的?最近活跃的?只写 DISTINCT id, university 无法表达这些规则。原题只是取学校名单,不需要引入这个额外问题。

5. NULL 与排序:两个小边界

去重时,两项 NULL 可以被合并;这不意味着 WHERE university = NULL 能筛出空学校。筛空值仍要用 IS NULL。MySQL 的 NULL 说明。

如果业务只想要已知学校,并且要一个明确的排序:

SELECT DISTINCT university FROM sql3_distinct_demo WHERE university IS NOT NULL ORDER BY university;

这里 DISTINCT 负责去重,WHERE 负责筛选,ORDER BY 负责排序。中文具体先后受排序规则影响,不能把“拼音排序”当默认承诺。

字符是否相等也受 MySQL 的 collation 影响。本文用完全相同的中文字符串验证重复,没有用 SQLite 的结果替代 MySQL 大小写、重音或尾空格行为。

6. 我怎么验证这些解释

本次在本地 SQLite 内存数据库执行上面的实验 SQL,校验以下结果;MySQL 语义另按官方文档核对。没有把它写成已经运行 MySQL 的截图,也没有测性能。

查询校验目标
单列 DISTINCT3 项,NULL 只出现一次
两列 DISTINCT5 项,不误删不同省份的复旦大学
id + 学校 DISTINCT6 项
原表 COUNT(*)6 行,查询没有删除记录
排除 NULL 后去重2 所已知学校

另外验证了空表、全部重复和全部 NULL。无排序的查询按集合核对,不把某次碰巧返回的顺序写进断言。

写 SQL 前,我现在会先问一句:我要去重的对象,到底是一个字段的值,还是几列组成的一整行?这句话比记住 DISTINCT 的拼写更有用。

返回列表