Skip to content

DISTINCT 与去重 ​

DISTINCT 基础 ​

DISTINCT 用于去除结果集中的重复行。

基本语法:

sql
SELECT DISTINCT column1, column2, ...
FROM table;

为什么需要去重 ​

首先创建一个有重复数据的示例表:

sql
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_name VARCHAR(50),
    product VARCHAR(50),
    amount DECIMAL(10, 2)
);

INSERT INTO orders (customer_name, product, amount) VALUES
    ('Alice', 'Laptop', 1200.00),
    ('Bob', 'Mouse', 25.00),
    ('Alice', 'Keyboard', 80.00),
    ('Charlie', 'Monitor', 300.00),
    ('Bob', 'Headphones', 50.00),
    ('Alice', 'USB Cable', 10.00);

查询所有客户名称:

sql
SELECT customer_name FROM orders;

结果有重复:

text
 customer_name
---------------
 Alice
 Bob
 Alice
 Charlie
 Bob
 Alice

使用 DISTINCT 去重 ​

sql
SELECT DISTINCT customer_name FROM orders;

结果:

text
 customer_name
---------------
 Alice
 Bob
 Charlie

每个客户名只出现一次。

多列 DISTINCT ​

DISTINCT 作用于整行,只有所有选中的列都相同才算重复:

sql
SELECT DISTINCT customer_name, product FROM orders;

结果:

text
 customer_name |  product
---------------+------------
 Alice         | Laptop
 Bob           | Mouse
 Alice         | Keyboard
 Charlie       | Monitor
 Bob           | Headphones
 Alice         | USB Cable

没有去重,因为每行的 (customer_name, product) 组合都是唯一的。

常见误解:DISTINCT 不能只作用于部分列 ​

sql
-- 错误理解:只对 customer_name 去重
SELECT DISTINCT customer_name, product FROM orders;

这会对 (customer_name, product) 整体去重,而不是只对 customer_name 去重。

如果想要"每个客户的第一个产品",需要使用窗口函数或子查询(后续章节)。

DISTINCT 与聚合函数 ​

DISTINCT 可以在聚合函数内部使用(下一章详细讲解):

sql
-- 计算不同客户的数量
SELECT COUNT(DISTINCT customer_name) AS unique_customers FROM orders;

结果:

text
 unique_customers
------------------
                3

这与先去重再计数不同:

sql
-- 这会先去重,然后计数去重后的行数
SELECT COUNT(*) FROM (SELECT DISTINCT customer_name FROM orders) AS t;

DISTINCT 的执行顺序 ​

DISTINCT 在 SELECT 之后、ORDER BY 之前执行:

执行顺序:FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT

示例:

sql
SELECT DISTINCT customer_name 
FROM orders 
ORDER BY customer_name;

结果:

text
 customer_name
---------------
 Alice
 Bob
 Charlie

DISTINCT 与 ORDER BY 的陷阱 ​

ORDER BY 中的列必须在 SELECT 列表中(当使用 DISTINCT 时):

sql
-- 错误:product 不在 SELECT 列表中
SELECT DISTINCT customer_name 
FROM orders 
ORDER BY product;

错误信息(PostgreSQL):

text
ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list

为什么? 因为 DISTINCT 去重后,每个客户可能对应多个产品,数据库不知道按哪个产品排序。

正确写法:

sql
-- 同时选择两列,然后去重
SELECT DISTINCT customer_name, product 
FROM orders 
ORDER BY customer_name, product;

或者只按 SELECT 中的列排序:

sql
SELECT DISTINCT customer_name 
FROM orders 
ORDER BY customer_name;

DISTINCT ON(PostgreSQL 特有) ​

PostgreSQL 提供 DISTINCT ON 用于更灵活的去重:

sql
-- 每个客户的第一个订单(按 id 排序)
SELECT DISTINCT ON (customer_name) 
    customer_name, product, amount
FROM orders
ORDER BY customer_name, id;

结果:

text
 customer_name |  product | amount
---------------+----------+---------
 Alice         | Laptop   | 1200.00
 Bob           | Mouse    |   25.00
 Charlie       | Monitor  |  300.00

DISTINCT ON (column) 的语义:

  • 对指定列去重
  • 保留每组的"第一行"(由 ORDER BY 决定)

MySQL 没有 DISTINCT ON,需要用子查询或窗口函数实现。

DISTINCT 的性能影响 ​

去重需要排序或哈希 ​

数据库通常用两种方式实现 DISTINCT:

  1. 排序:先排序,然后跳过相邻的重复行
  2. 哈希:用哈希表记录已见过的行

两种方式都有开销,大数据集上 DISTINCT 可能很慢。

查看执行计划 ​

PostgreSQL:

sql
EXPLAIN SELECT DISTINCT customer_name FROM orders;

可能的输出:

text
Unique  (cost=...)
  ->  Sort  (cost=...)
        Sort Key: customer_name
        ->  Seq Scan on orders  (cost=...)

或:

text
HashAggregate  (cost=...)
  Group Key: customer_name
  ->  Seq Scan on orders  (cost=...)

性能优化建议 ​

  1. 避免不必要的 DISTINCT:先确认数据是否真的有重复。

  2. 在列上创建索引:索引可以加速排序。

  3. 考虑 GROUP BY:有时 GROUP BY 配合聚合函数更高效(下一章)。

  4. 限制选择的列:DISTINCT 作用于所有选中的列,列越多去重越慢。

DISTINCT vs GROUP BY ​

以下两个查询结果相同:

sql
-- 使用 DISTINCT
SELECT DISTINCT customer_name FROM orders;

-- 使用 GROUP BY
SELECT customer_name FROM orders GROUP BY customer_name;

GROUP BY 通常配合聚合函数使用(下一章详细讲解):

sql
-- 每个客户的订单数量
SELECT customer_name, COUNT(*) AS order_count
FROM orders
GROUP BY customer_name;

结果:

text
 customer_name | order_count
---------------+-------------
 Alice         |           3
 Bob           |           2
 Charlie       |           1

ALL(默认行为) ​

SELECT 默认返回所有行(包括重复),这等价于 SELECT ALL:

sql
-- 以下两个查询等价
SELECT customer_name FROM orders;
SELECT ALL customer_name FROM orders;

ALL 是默认值,很少显式写出。

练习 ​

使用本节创建的 orders 表完成以下练习:

  1. 基本去重:查询所有不重复的产品名称。

  2. 多列去重:查询所有不重复的 (customer_name, product) 组合。

  3. 统计去重:计算有多少个不同的客户(使用 COUNT DISTINCT)。

  4. 去重后排序:查询所有不同的客户名称,按字母顺序排序。

  5. 理解差异:解释以下两个查询的区别:

    sql
    SELECT DISTINCT customer_name FROM orders;
    SELECT customer_name FROM orders GROUP BY customer_name;
  6. DISTINCT ON(PostgreSQL):查询每个客户金额最大的订单(提示:使用 DISTINCT ON 和 ORDER BY)。

  7. 性能对比:在一个有 10000 行的表上,比较有无 DISTINCT 的查询性能(使用 EXPLAIN ANALYZE)。

  8. 实际场景:从 orders 表中获取至少下过一次单的所有客户名称(去重),并按客户名称排序。

完成练习后,打开本章答案页核对。

常见错误 ​

错误 1:以为 DISTINCT 只作用于第一列 ​

sql
-- 错误理解:只对 customer_name 去重
SELECT DISTINCT customer_name, product FROM orders;

-- 实际:对 (customer_name, product) 整体去重

错误 2:DISTINCT 与 ORDER BY 不匹配 ​

sql
-- 错误:ORDER BY 的列不在 SELECT 中
SELECT DISTINCT customer_name FROM orders ORDER BY amount;

错误 3:过度使用 DISTINCT ​

sql
-- 不必要的 DISTINCT(如果 id 是主键,本来就不会重复)
SELECT DISTINCT id, name FROM users;

先检查是否真的有重复,避免无谓的性能开销。

本章小结 ​

第一章建立了 SQL 查询的基础:

  • 数据库安装与连接
  • SELECT 与列选择
  • WHERE 过滤条件
  • ORDER BY 排序与 LIMIT 分页
  • DISTINCT 去重

这些是所有 SQL 查询的基础。第二章将学习聚合函数和分组。

回到第一章概览 · 返回SQL 学习路线

用心记录每一步成长