SQLite中DISTINCT去重和聚合函数怎么配合使用?

来源:PHP教程作者:刘卫东头衔:网络博主
导读:本期聚焦于刘卫东创作的《SQLite中DISTINCT去重和聚合函数怎么配合使用?》,敬请观看详情。写查询时常常遇到重复行干扰统计结果的情况。SQLite的DISTINCT关键字能在返回结果中消除完全一致的记录,而SUM、COUNT、AVG等聚合函数则负责把多行压缩成单一数值。两者结合时,DISTINCT会先对参与计算的列去重,再交给聚合函数处理,这和先聚合后去重差别很大。例如统计不同用户的下单城市数量,用COUNT(DISTINCT city)才能避免同一城市被反复计数。理解执行顺序与适用边界,才能写出准确且高效的统计SQL。

在轻量级数据库SQLite里,做数据查询时我们经常会碰到重复数据的问题。比如一张订单表里,同一个用户可能在不同时间下了多笔订单,城市字段有大量重复值。如果我们直接对这些列做求和、计数,得到的结果往往偏大。这时候就需要用到DISTINCT去重,以及SUM、COUNT、AVG这类聚合函数。它们单独用都不难,但放到一条SQL里配合时,很多人会搞混执行逻辑,导致统计数字不对。本文从底层行为讲清楚它们怎么一起工作。

SQLite中DISTINCT去重和聚合函数怎么配合使用?

DISTINCT的基础去重机制与单独用法

SQLite里的DISTINCT是一个查询修饰符,放在SELECT关键字后面。它的作用是:对最终选出的所有列的组合进行比对,如果两行在所有被选列上值都一样,就只保留其中一行。注意它是基于“选出列”的整体去重,而不是只看着某一列。比如SELECT DISTINCT name, age FROM user,只有当name和age同时相同时才会被合并。

单独使用DISTINCT时,它不改变列的数量,只是减少行数。底层上SQLite会对结果集做一次排序或哈希,把重复元组剔除。如果表很大,这个操作会消耗临时内存和CPU。但它不需要配合GROUP BY就能完成基础去重,写法比分组统计简单。下面是一段建表和普通去重查询的例子:

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    user_id INTEGER,
    city TEXT,
    amount REAL
);

INSERT INTO orders (user_id, city, amount) VALUES
(1, '北京', 100.0),
(1, '北京', 200.0),
(2, '上海', 150.0),
(1, '上海', 300.0);

SELECT DISTINCT user_id, city FROM orders;

上面这段代码执行后,user_id为1且city为北京的两行会变成一行。这就是最直观的DISTINCT行为。它没有做任何计算,只是把肉眼看到的重复组合消掉。当我们要看“有哪些用户到过哪些城市”这类明细时,这样写就足够了。

聚合函数与DISTINCT的组合书写形式

聚合函数用来把多行数据算成一个值。SQLite支持COUNT、SUM、AVG、MIN、MAX等。当聚合函数的参数前加上DISTINCT,意思是“先对该列去重,再聚合”。语法是聚合函数(DISTINCT 列名)。这和在外层SELECT用DISTINCT完全不同:外层去重是整行维度,聚合内去重是单列维度。

以COUNT为例,COUNT(city)会统计所有非NULL的city行数,而COUNT(DISTINCT city)只统计出现过几个不同的city。对于订单表,前者算的是订单笔数,后者算的是涉及城市数。下面代码演示两者差异:

-- 统计订单总笔数(含重复城市)
SELECT COUNT(city) FROM orders;

-- 统计涉及的不同城市数量
SELECT COUNT(DISTINCT city) FROM orders;

-- 统计每个用户去过几个不同城市
SELECT user_id, COUNT(DISTINCT city) AS city_cnt
FROM orders
GROUP BY user_id;

从执行计划看,SQLite在处理COUNT(DISTINCT col)时,会对col建一个临时去重结构,再计数。它不会先全表聚合再剔重。因此当你需要“唯一值的数量”或“唯一值的和”时,必须把DISTINCT写在聚合函数括号里。写在外面是达不到目的的,比如SELECT DISTINCT COUNT(city)只是把单行计数结果去重,毫无意义。

执行顺序误区与性能优化建议

不少初学者以为SQLite会先跑聚合再去做重,其实对于聚合(DISTINCT col)这种写法,去重发生在聚合计算之前,作用范围仅限那个列。而如果是SELECT DISTINCT 聚合(col),则先算出聚合值,再对结果行去重,一般配合GROUP BY才有讨论价值。混淆这两者,是统计错误的高发原因。

在性能上,DISTINCT聚合会用到临时B树或内存表。如果列上有索引,SQLite有时能借用索引有序性加速去重;如果没有,就要自己排序。对于大表,推荐先缩小数据范围,比如加上WHERE条件过滤时间区间,再去做COUNT(DISTINCT)。另外,MIN和MAX本身对重复不敏感,加DISTINCT没有效果反而浪费资源,应避免画蛇添足。

-- 不推荐:对MAX使用DISTINCT,无意义
SELECT MAX(DISTINCT amount) FROM orders;

-- 推荐:直接取最大金额
SELECT MAX(amount) FROM orders;

-- 推荐:先过滤再去重统计,提升效率
SELECT COUNT(DISTINCT city)
FROM orders
WHERE amount > 100;

还有一个实际坑点:TEXT类型的字段去重受大小写和编码影响。SQLite默认按字节比较,如果同一城市写成“北京”和“BEIJING”不会被认定重复。若业务要求无视大小写,需要用LOWER(city)包一层再DISTINCT,即COUNT(DISTINCT LOWER(city))。这样逻辑才严谨,也不会漏统计。

常见业务场景与完整示例

假设我们要分析用户消费广度:每人去了几个城市、在不同城市的消费总额唯一值之和。这时DISTINCT和聚合就要混用。下面示例用CTE先把每人每城市汇总,再统计城市数,并演示错误与正确写法对比。

WITH user_city AS (
    SELECT user_id, city, SUM(amount) AS total
    FROM orders
    GROUP BY user_id, city
)
SELECT
    user_id,
    COUNT(DISTINCT city) AS city_count,
    SUM(total) AS all_spend
FROM user_city
GROUP BY user_id;

这段逻辑中,内层GROUP BY已经把同用户同城市合并,外层COUNT(DISTINCT city)其实等价于COUNT(city),因为city在组内已唯一。但如果是直接在原表上写SUM(DISTINCT amount),就会把相同金额的不同订单合并,导致总金额少算,这是典型误用。正确做法是先按维度聚合,再求和,不要对金额用DISTINCT。

总结来说,SQLite的DISTINCT和聚合函数配合核心只有一条:DISTINCT写在聚合括号内,就对那一列去重后算;写在外层,就对整行结果去重。弄清作用域与顺序,多用EXPLAIN QUERY PLAN观察临时结构,就能写出既准又快的统计SQL。

SQLiteDISTINCT聚合函数修改时间:2026-08-21 18:24:50

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。