1. 为什么需要聚合函数和分组查询?

想象你是一家电商公司的数据分析师,老板让你统计最近三个月每个品类的销售总额、平均订单金额和订单数量。如果手动计算,你需要先按品类分类,再逐个求和、求平均——这工作量简直让人崩溃。而MySQL的聚合函数和分组查询就是为解决这类问题而生的。

聚合函数就像是一个智能计算器,能自动对一组数据进行统计计算。比如:

  • SUM() 帮你求和
  • AVG() 自动算平均数
  • COUNT() 快速计数

而分组查询(GROUP BY)则像是一个自动分类器,能按照你指定的列(比如商品类别)将数据分成若干组,再对每个组应用聚合函数。两者结合使用,就能轻松完成老板交代的统计任务。

2. 五大核心聚合函数详解

2.1 求和与平均:SUM()和AVG()

这两个函数专门处理数值型数据。我最近用它们分析过销售数据:

-- 计算所有商品销售总额
SELECT SUM(amount) AS total_sales FROM orders;

-- 计算手机类目的平均订单金额
SELECT AVG(amount) AS avg_order 
FROM orders 
WHERE category = '手机';

踩坑提醒 :AVG()计算时默认忽略NULL值。如果某商品的amount是NULL,它不会计入分母。如果需要将NULL视为0,可以这样写:

SELECT AVG(IFNULL(amount,0)) FROM orders;

2.2 极值函数:MAX()和MIN()

这两个函数很灵活,能处理数字、字符串甚至日期:

-- 找出最贵的商品价格(数字)
SELECT MAX(price) FROM products;

-- 找出字母排序最后的商品名(字符串)
SELECT MAX(product_name) FROM products;

-- 找出最早的注册日期(日期)
SELECT MIN(register_date) FROM users;

实测发现 :对ENUM类型字段使用MAX()/MIN()时,MySQL比较的是实际存储的数值而非字符串值,这点要特别注意。

2.3 计数函数COUNT()的三种用法

COUNT()有几种常见写法,效果大不同:

-- 统计总行数(推荐)
SELECT COUNT(*) FROM orders;  

-- 统计非空的user_id数量
SELECT COUNT(user_id) FROM orders;

-- 统计不重复的用户数
SELECT COUNT(DISTINCT user_id) FROM orders;

性能对比 :在InnoDB引擎下,COUNT(*)和COUNT(1)性能相当,都比COUNT(列名)快,因为前者可以直接读取索引统计信息。

3. GROUP BY分组实战技巧

3.1 单列分组基础用法

先看一个简单的分组统计:

-- 按部门统计平均薪资
SELECT 
    department,
    AVG(salary) AS avg_salary,
    COUNT(*) AS emp_count
FROM employees
GROUP BY department;

易错点 :SELECT中的非聚合列必须出现在GROUP BY中。以下写法会报错:

-- 错误示例!
SELECT 
    employee_name,  -- 未出现在GROUP BY中
    department,
    AVG(salary)
FROM employees
GROUP BY department;

3.2 多列分组与ROLLUP

当需要多维分析时,可以用多列分组:

-- 按部门和职位统计薪资
SELECT 
    department,
    job_title,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department, job_title;

如果需要小计和总计,可以加上WITH ROLLUP:

-- 带层级汇总的分组
SELECT 
    IFNULL(department, '所有部门') AS department,
    IFNULL(job_title, '全部职位') AS job_title,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department, job_title WITH ROLLUP;

注意 :ROLLUP与ORDER BY不能同时使用,且NULL值会被ROLLUP用作汇总行的占位符。

4. HAVING与WHERE的过滤区别

4.1 基础过滤对比

WHERE和HAVING都用于过滤,但时机不同:

  • WHERE在分组前过滤原始数据
  • HAVING在分组后过滤聚合结果
-- 先过滤再分组(效率高)
SELECT 
    category,
    AVG(price) AS avg_price
FROM products
WHERE price > 100  -- 先排除低价商品
GROUP BY category;

-- 先分组再过滤
SELECT 
    category,
    AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING avg_price > 1000;  -- 筛选高均价品类

4.2 性能优化建议

在大数据量下,WHERE能显著提高性能:

-- 高效写法
SELECT 
    user_id,
    COUNT(*) AS order_count
FROM orders
WHERE create_time > '2023-01-01'  -- 先缩小数据范围
GROUP BY user_id
HAVING order_count > 5;

-- 低效写法(不推荐)
SELECT 
    user_id,
    COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING order_count > 5 
   AND MIN(create_time) > '2023-01-01';

5. 完整SQL执行顺序解析

理解执行顺序能避免很多错误:

  1. FROM :确定数据来源
  2. WHERE :行级过滤
  3. GROUP BY :分组
  4. HAVING :组级过滤
  5. SELECT :选择字段
  6. ORDER BY :排序
  7. LIMIT :限制行数

举个例子:

SELECT 
    department,
    AVG(salary) AS avg_salary
FROM employees
WHERE hire_date > '2020-01-01'
GROUP BY department
HAVING AVG(salary) > 10000
ORDER BY avg_salary DESC
LIMIT 5;

这个查询的执行流程是:

  1. 从employees表取出数据
  2. 筛选2020年后入职的员工
  3. 按部门分组
  4. 过滤出平均薪资>1万的部门
  5. 计算每个部门的平均薪资
  6. 按平均薪资降序排列
  7. 只返回前5条记录

6. 实际业务场景案例

6.1 销售数据分析

假设需要分析季度销售数据:

SELECT 
    product_category,
    SUM(amount) AS total_sales,
    AVG(amount) AS avg_order,
    COUNT(DISTINCT user_id) AS customer_count,
    MAX(amount) AS max_order
FROM sales
WHERE quarter = '2023-Q2'
GROUP BY product_category
HAVING total_sales > 100000
ORDER BY total_sales DESC;

6.2 用户行为统计

分析用户活跃度:

SELECT 
    user_level,
    COUNT(*) AS active_users,
    AVG(login_count) AS avg_logins,
    SUM(CASE WHEN last_login > CURDATE() - INTERVAL 7 DAY THEN 1 ELSE 0 END) 
        AS recent_active_users
FROM users
WHERE status = 'active'
GROUP BY user_level
HAVING active_users > 100;

7. 常见错误与解决方案

错误1 :在WHERE中使用聚合函数

-- 错误写法
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 10000  -- 聚合函数不能用在WHERE
GROUP BY department;

-- 正确写法
SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING AVG(salary) > 10000;

错误2 :GROUP BY遗漏非聚合列

-- 错误写法
SELECT 
    product_id,
    product_name,  -- 未出现在GROUP BY中
    AVG(price)
FROM products
GROUP BY product_id;

-- 正确写法
SELECT 
    product_id,
    product_name,
    AVG(price)
FROM products
GROUP BY product_id, product_name;

错误3 :混淆COUNT用法

-- 统计总行数(包含NULL行)
SELECT COUNT(*) FROM table;

-- 统计某列非NULL值数量
SELECT COUNT(column) FROM table;

-- 统计某列去重后的数量
SELECT COUNT(DISTINCT column) FROM table;

8. 性能优化技巧

  1. 为GROUP BY列添加索引 :特别是大表分组时,索引能显著加快分组速度
  2. 先缩小数据范围 :先用WHERE过滤再分组,减少处理的数据量
  3. 避免过度分组 :只选择必要的分组列
  4. 考虑使用派生表 :对大数据集可以先过滤再分组
-- 优化后的查询示例
SELECT 
    category,
    AVG(price) AS avg_price
FROM (
    SELECT category, price 
    FROM products
    WHERE create_date > '2023-01-01'
) AS recent_products
GROUP BY category;

在实际项目中,我曾用这些优化技巧将一个原本需要30秒的报表查询优化到2秒内完成。关键是要理解数据特点,合理设计查询逻辑。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐