Menu

SQLite DISTINCT:在 SELECT 查询中去除重复

SQLite 中 SELECT DISTINCT 的工作方式:单列、多列、NULL 的处理、COUNT(DISTINCT),以及什么时候 GROUP BY 才是更合适的工具。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

DISTINCT 去除重复的行

默认情况下,SELECT 会返回每一个匹配的行,包括重复的行。DISTINCT 告诉 SQLite 把所选列完全相同的行合并起来,让每种唯一组合只出现一次。

输入五行,输出三行。SQLite 查看 customer 列,丢掉重复值,每个唯一值返回一行。顺序没有保证,如果你在意顺序,就加上 ORDER BY。

DISTINCT 作用于整个选择列表

这一点常常让人犯错。DISTINCT 不会挑出某一列来去重,而是根据你选择的所有列对整行去重。

每一个唯一的 (customer, country) 组合出现一次。如果同一个客户对应了两个不同的国家,你会看到两行,因为在 SQLite 看来它们并不重复。

没有一种 DISTINCT(customer) 的写法能忽略其他列。括号看起来很诱人,但 SELECT DISTINCT(customer), country 的解析结果和 SELECT DISTINCT customer, country 一模一样,括号只是把一个表达式括起来而已。如果你真的想每个客户只要一行,并配上某个选定的国家,那是 GROUP BY 加聚合函数该做的事。

COUNT(DISTINCT col)

一个常见的需求:某一列中有多少个“不同”的值?COUNT(*) 统计行数,COUNT(col) 统计非 NULL 值的个数,而 COUNT(DISTINCT col) 统计不同的非 NULL 值的个数。

五个订单,三个不同的客户,三个不同的国家。COUNT(DISTINCT ...) 是 DISTINCT 最有用的聚合形式,每当你想统计“出现了多少种不同的东西”时,都会用到它。

注意,SQLite 只允许在 COUNT(DISTINCT ...) 中放一列。要统计多列的唯一组合数,就把它们放进子查询:SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t)。

DISTINCT 如何对待 NULL

NULL 在 SQL 中以古怪著称,因为 NULL = NULL 的结果是 NULL,而不是 TRUE。但 DISTINCT 设了一个特例:在去重时,所有 NULL 都被视为彼此相等。

返回三行:'ada@example.com'、'dan@example.com' 和一个 NULL。三个为 NULL 的 email 合并成了一个。同样的规则也适用于 GROUP BY 以及 UNION 这样的集合运算。当你在排查“为什么那个 NULL 行只出现一次而不是三次”时,记住这一点会很有帮助。

DISTINCT 在 ORDER BY 和 LIMIT 之前执行

SELECT 中的各个子句遵循一个逻辑顺序:FROM → WHERE → GROUP BY → HAVING → SELECT/DISTINCT → ORDER BY → LIMIT。所以 DISTINCT 先过滤掉重复项,然后 ORDER BY 对剩下的行排序,最后 LIMIT 截取结果。

WHERE 保留四行,DISTINCT 合并 Boris 的重复行,ORDER BY 按字母顺序排序,LIMIT 返回前两行。值得亲手推演一遍:对结果顺序的困惑,通常都来自忘了哪一步先执行。

DISTINCT 与 GROUP BY

如果只是去重,下面两条查询返回的行是一样的:

结果相同。区别在于你“接下来”能做什么:

  • DISTINCT 只用于“给我唯一的行”,别的什么都不做。
  • GROUP BY 用于“把行分组,并为每组计算一些东西”:COUNT(*)、SUM(amount)、MAX(created_at) 等等。

如果你用了 DISTINCT,然后发现还想要每个客户的总额,这就是该换成 GROUP BY 的信号:

每个客户一行,并带上你想要的聚合值。DISTINCT 做不到这一点,它没有办法表达“每组一行,‘再加上’一个求和”。

需要注意的几件事

  • 性能。 DISTINCT 通常需要 SQLite 对行进行排序或哈希来找出重复项。结果集很大时,在去重的列上建索引会有帮助。如果你对一张很宽的表的每一列都做 SELECT DISTINCT,先问问自己是否真的需要每一列。
  • DISTINCT * 很少见。 它是合法的(SELECT DISTINCT * FROM t 对整行去重),但如果表有主键,每一行本来就是唯一的,所以这样做没有任何用处。
  • 不要和 UNIQUE 混淆。 UNIQUE 是表上的约束,从源头上阻止插入重复值。DISTINCT 是查询时的过滤,在结果中隐藏重复项。不同的工具,不同的用途。

下一篇:CASE 表达式

一旦你能用 SELECT、WHERE、ORDER BY 和 DISTINCT 来塑造结果行,下一步就是在查询“内部”使用条件逻辑。CASE 表达式让你根据条件返回不同的值,相当于 SQL 版的 if/else 阶梯,下一页就来讲它。

常见问题

SQLite 中的 SELECT DISTINCT 是如何工作的?

SELECT DISTINCT 会从结果集中去除重复的行。SQLite 会比较选择列表中的每一列,每种唯一的组合只保留一行。它在 WHERE 和 JOIN 之后、ORDER BY 和 LIMIT 之前执行。

在 SQLite 中可以对多列使用 DISTINCT 吗?

可以。DISTINCT 总是作用于整个选择列表,而不是单独某一列。SELECT DISTINCT city, country FROM users 返回每一个唯一的 (city, country) 组合。没有一种 DISTINCT(city) 的写法能忽略其他列;如果你需要这样的效果,就用 GROUP BY 加聚合函数。

SQLite 中的 DISTINCT 如何处理 NULL 值?

在去重时,DISTINCT 把 NULL 视为与其他 NULL 相等,所以多个值为 NULL 的行会合并成一行。这与 WHERE 子句中 = 的行为不同,在那里 NULL = NULL 的结果是未知。这是专门为 DISTINCT、GROUP BY 和 UNION 设的特殊规则。

SQLite 中 DISTINCT 和 GROUP BY 有什么区别?

如果只是去重,SELECT DISTINCT col 和 SELECT col FROM t GROUP BY col 的结果完全一样。区别在于用意:只想要唯一的行时用 DISTINCT;还想按组计算 COUNT(*) 或 SUM(amount) 这样的聚合值时,用 GROUP BY。

Coddy 编程语言插图

和 Coddy 一起学编程

开始