数据库聚合查询完整教程:从基础函数到实战统计

一、什么是聚合查询

聚合查询是数据库中最常见的统计分析方式,它可以把表中的多行数据按照一定规则汇总成一行结果。例如,统计用户总数、计算订单金额总和、求某门课程的平均分等,都离不开聚合查询。

在 SQL 中,聚合查询主要依赖两类关键字:

  • 聚合函数 :用于对某一列或多列的数据执行计算,如 COUNT、SUM、AVG、MAX、MIN。
  • GROUP BY:用于把数据按指定字段分组,再对每个分组分别执行聚合计算。

下面先准备一张示例表,后续所有 SQL 都基于这份数据展开。

sql 复制代码
CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_name VARCHAR(50),
    product_name VARCHAR(50),
    category VARCHAR(50),
    amount DECIMAL(10, 2),
    status VARCHAR(20),
    created_at DATE
);

INSERT INTO orders VALUES
(1, '张三', '手机', '电子产品', 3999.00, '已完成', '2024-01-05'),
(2, '李四', '键盘', '电脑配件', 299.00, '已完成', '2024-01-08'),
(3, '张三', '耳机', '电子产品', 599.00, '已完成', '2024-01-10'),
(4, '王五', '显示器', '电脑配件', 1599.00, '已取消', '2024-01-12'),
(5, '李四', '鼠标', '电脑配件', 129.00, '已完成', '2024-01-15'),
(6, '赵六', '平板', '电子产品', 2599.00, '已完成', '2024-01-18'),
(7, '张三', '充电器', '电子产品', 89.00, '已退货', '2024-01-20'),
(8, '王五', '机械键盘', '电脑配件', 799.00, '已完成', '2024-01-22');

二、五个核心聚合函数

聚合函数接收一列值,返回单个统计结果。最常用的五个聚合函数如下:

函数 作用 说明
COUNT 统计行数 可以统计所有行,也可以使用 DISTINCT 去重
SUM 求和 通常用于数值列
AVG 求平均值 通常用于数值列
MAX 求最大值 可用于数字、日期和字符串
MIN 求最小值 可用于数字、日期和字符串

2.1 COUNT:统计行数

COUNT(*) 会统计所有行的数量,包括包含 NULL 的行;COUNT(column_name) 只统计该列不为 NULL 的行。

sql 复制代码
-- 统计订单总条数
SELECT COUNT(*) AS total_orders
FROM orders;

-- 统计指定列不为 NULL 的行数
SELECT COUNT(status) AS status_count
FROM orders;

-- 统计去重后的客户数量
SELECT COUNT(DISTINCT customer_name) AS customer_count
FROM orders;

2.2 SUM:计算总和

SUM 用于对数值列求和,常用于金额、销量、积分等场景。

sql 复制代码
-- 统计所有订单金额总和
SELECT SUM(amount) AS total_amount
FROM orders;

-- 统计已完成订单的金额总和
SELECT SUM(amount) AS completed_amount
FROM orders
WHERE status = '已完成';

2.3 AVG:计算平均值

AVG 返回指定数值列的平均值。需要注意,AVG 默认会忽略该列中的 NULL 值。

sql 复制代码
-- 计算订单平均金额
SELECT AVG(amount) AS avg_amount
FROM orders;

-- 计算电子产品类目的平均金额
SELECT AVG(amount) AS avg_electronics_amount
FROM orders
WHERE category = '电子产品';

2.4 MAX 与 MIN:获取最大值和最小值

MAX 和 MIN 不仅适用于数字,也可以用于日期和字符串,返回最大或最小的值。

sql 复制代码
-- 查询最高和最低订单金额
SELECT MAX(amount) AS max_amount,
       MIN(amount) AS min_amount
FROM orders;

-- 查询最早和最晚的订单日期
SELECT MAX(created_at) AS latest_order,
       MIN(created_at) AS earliest_order
FROM orders;

三、GROUP BY:分组聚合

如果只是对整个表执行聚合,结果只有一行。实际业务中,我们更希望按维度分组统计,例如按客户、按类目、按月份统计。GROUP BY 就是实现这一目标的关键。

3.1 按单字段分组

sql 复制代码
-- 统计每个类目的订单数量和总金额
SELECT category,
       COUNT(*) AS order_count,
       SUM(amount) AS total_amount
FROM orders
GROUP BY category;

执行后会得到每个类目的统计结果,每个类目一行。

3.2 按多字段分组

当需要更细的统计粒度时,可以同时按多个字段分组。

sql 复制代码
-- 统计每个客户在每个类目下的订单数量和总金额
SELECT customer_name,
       category,
       COUNT(*) AS order_count,
       SUM(amount) AS total_amount
FROM orders
GROUP BY customer_name, category;

3.3 注意 SELECT 列表的约束

使用 GROUP BY 时,SELECT 中出现的非聚合字段通常必须出现在 GROUP BY 中,否则数据库会报错。下面这条 SQL 在大多数数据库中是非法的:

sql 复制代码
-- 错误示例:customer_name 未出现在 GROUP BY 中,MySQL 较老版本可能不报错但结果不可控
SELECT customer_name, category, COUNT(*)
FROM orders
GROUP BY category;

正确写法是把 customer_name 一并加入 GROUP BY,或者把它作为聚合函数的参数。

四、HAVING:过滤分组结果

WHERE 用于在分组前过滤行,HAVING 用于在分组后过滤聚合结果。这是聚合查询中最常见也最容易混淆的知识点。

sql 复制代码
-- 统计每个客户的订单总金额,只保留订单总金额大于 1000 的客户
SELECT customer_name,
       COUNT(*) AS order_count,
       SUM(amount) AS total_amount
FROM orders
GROUP BY customer_name
HAVING SUM(amount) > 1000;

4.1 WHERE 与 HAVING 的执行顺序

SQL 的逻辑执行顺序大致为:FROM 找到数据源,WHERE 过滤行,GROUP BY 分组,然后执行聚合函数,最后 HAVING 过滤分组,再执行 SELECT。因此:

  • 如果过滤条件作用于原始行,使用 WHERE。
  • 如果过滤条件作用于聚合结果,使用 HAVING。
sql 复制代码
-- 同时使用 WHERE 和 HAVING:
-- 先排除已取消订单,再统计每个客户订单数,仅保留订单数大于 1 的客户
SELECT customer_name,
       COUNT(*) AS order_count
FROM orders
WHERE status <> '已取消'
GROUP BY customer_name
HAVING COUNT(*) > 1;

五、进阶聚合技巧

5.1 条件聚合

业务中经常需要在一行里同时展示多个统计口径,例如同时统计总订单数、已完成订单数和已取消订单数。可以结合 CASE WHEN 与聚合函数实现条件聚合。

sql 复制代码
-- 按客户统计总订单数、已完成订单数和取消/退货订单数
SELECT customer_name,
       COUNT(*) AS total_orders,
       SUM(CASE WHEN status = '已完成' THEN 1 ELSE 0 END) AS completed_orders,
       SUM(CASE WHEN status IN ('已取消', '已退货') THEN 1 ELSE 0 END) AS invalid_orders
FROM orders
GROUP BY customer_name;

5.2 分组汇总:ROLLUP

如果希望在分组结果的基础上再增加一行总计,可以使用 WITH ROLLUP(MySQL)或 ROLLUP(PostgreSQL、SQL Server 等)。

sql 复制代码
-- MySQL 示例:按类目统计,最后额外输出一行所有类目的总计
SELECT category,
       COUNT(*) AS order_count,
       SUM(amount) AS total_amount
FROM orders
GROUP BY category
WITH ROLLUP;

在上面的结果中,类目为 NULL 的那一行就是汇总行。如果希望显示为"总计",可以使用 COALESCE 处理。

sql 复制代码
SELECT COALESCE(category, '总计') AS category,
       COUNT(*) AS order_count,
       SUM(amount) AS total_amount
FROM orders
GROUP BY category
WITH ROLLUP;

5.3 使用 DISTINCT 去重统计

当某列存在重复值时,如果希望统计不同值的数量,可以使用 COUNT(DISTINCT column_name)。

sql 复制代码
-- 统计购买过商品的客户数量
SELECT COUNT(DISTINCT customer_name) AS unique_customers
FROM orders;

-- 统计每个类目下购买过商品的客户数量
SELECT category,
       COUNT(DISTINCT customer_name) AS unique_customers
FROM orders
GROUP BY category;

六、窗口函数:保留明细的聚合

普通聚合查询会把多行合并成一行,而窗口函数可以在保留每一行明细的同时计算聚合值,例如计算移动平均、排名、累计和等。

sql 复制代码
-- 保留每一笔订单,同时计算每个客户的累计订单金额
SELECT customer_name,
       id,
       amount,
       SUM(amount) OVER (PARTITION BY customer_name ORDER BY created_at) AS cumulative_amount
FROM orders
ORDER BY customer_name, created_at;

上面的查询使用 PARTITION BY 按客户分区,再按日期排序计算累计金额,适合查看用户消费增长趋势。

七、实战案例:销售数据统计

根据示例订单表,假设产品同学希望看到一个简单的销售概览,包含每条统计口径,可以这样写:

sql 复制代码
SELECT
    COUNT(*) AS total_orders,
    COUNT(DISTINCT customer_name) AS unique_customers,
    SUM(amount) AS total_sales,
    AVG(amount) AS avg_order_value,
    MAX(amount) AS max_order_amount,
    MIN(amount) AS min_order_amount
FROM orders
WHERE status = '已完成';

如果需要按类目生成销售报表:

sql 复制代码
SELECT
    category AS 类目,
    COUNT(*) AS 订单数,
    COUNT(DISTINCT customer_name) AS 购买人数,
    SUM(amount) AS 销售总额,
    AVG(amount) AS 平均客单价,
    MAX(amount) AS 最高订单金额,
    MIN(amount) AS 最低订单金额
FROM orders
WHERE status = '已完成'
GROUP BY category
ORDER BY SUM(amount) DESC;

八、常见错误与优化建议

8.1 常见错误

  • SELECT 列未出现在 GROUP BY 中 :非聚合列必须包含在 GROUP BY 里,否则在严格模式下会报错。
  • 混淆 WHERE 和 HAVING :聚合函数不能直接放在 WHERE 中,例如 WHERE SUM(amount) > 1000 是错误的。
  • 忽略 NULL 的影响 :COUNT(column)、AVG(column) 会忽略 NULL,而 COUNT(*) 不会,统计行数时要注意。
  • 使用中文别名时缺少引号:如果使用中文别名,建议用反引号包裹,避免兼容性问题。

8.2 性能优化建议

  • 提前用 WHERE 缩小数据范围 :把能在原始行上过滤的条件放在 WHERE 中,减少分组数据量。
  • 为分组字段建立索引 :GROUP BY 和 JOIN 的字段加索引,可以显著提升查询速度。
  • 避免大范围模糊统计:如果只需要部分统计结果,优先考虑分页、时间范围过滤或提前聚合的统计表。
  • 谨慎使用 DISTINCT :在大表上执行 COUNT(DISTINCT column) 可能消耗较多资源,必要时可考虑用近似统计或用索引优化。

九、总结

聚合查询是数据分析型 SQL 的核心能力。掌握以下要点,可以应对绝大多数统计需求:

  1. 用 COUNT、SUM、AVG、MAX、MIN 完成基础统计。
  2. 用 GROUP BY 按维度分组,用 HAVING 过滤分组后的结果。
  3. 理解 WHERE 与 HAVING 的执行顺序,正确放置过滤条件。
  4. 使用 CASE WHEN 条件聚合、ROLLUP 汇总和窗口函数应对复杂统计场景。
  5. 注意 NULL 值、分组字段约束和索引优化,避免常见错误和性能问题。

建议读者先在本地数据库执行一遍示例表,再逐步套用到自己的业务数据上,通过实际查询加深理解。

相关推荐
wangchunyu1141 小时前
JAVA 类加载机制与双亲委派模型
java·开发语言
小蒜学长1 小时前
基于 Spring Boot 的英语单词背诵系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
lambo mercy1 小时前
持久化01——线程级持久化
java·开发语言·jvm
CoderYanger2 小时前
Java EE 进阶:5. Spring IoC&DI(第一弹):Spring IoC
java·开发语言·spring boot·spring·面试·职场和发展·java-ee
中电金信2 小时前
开发更高效、监控更实时、运维更省心,源启数据开发平台构筑一站式数据开发新底座
网络·数据库·人工智能
泡海椒2 小时前
深度解析JQuick-SQL 谓词下推与投影下推优化机制
数据库·sql
zyseo82 小时前
谷歌SEO 内容更新焕新流程:让老页面重新起量
java·前端·javascript
萧瑟余晖2 小时前
Spring Data JDBC与R2DBC详解
java·后端·spring
杰克尼2 小时前
今日面试题总结
java·开发语言·人工智能·langchain