欢迎光临
我们一直在努力

【数据库基础|第5篇】聚合函数与 group by 分组查询详解

前言

上一篇我们学习了 where、order by、limit,已经可以完成比较常见的条件查询、排序查询和分页查询。

但是在真实项目中,查询并不只是把数据查出来展示。很多时候我们还需要做统计。

比如:

  • 查询员工总人数
  • 查询男性员工和女性员工分别有多少人
  • 查询每个部门有多少员工
  • 查询员工平均工资
  • 查询最高工资和最低工资
  • 查询每个部门的平均工资

这些需求就不是简单的 select * from emp 能解决的了。

这时候就需要用到 SQL 中非常重要的内容:聚合函数 和 分组查询。

这一篇我们就来学习 count、sum、avg、max、min 以及 group by 的使用。

一、什么是聚合函数?

聚合函数就是对一组数据进行统计计算的函数。

常见聚合函数如下:

聚合函数作用
count() 统计数量
sum() 求和
avg() 求平均值
max() 求最大值
min() 求最小值

比如员工表中有很多条员工数据,我们可以用聚合函数统计员工总数:

select count(*) from emp;

也可以统计员工平均工资:

select avg salary from emp;

不过上面这句写法是错误的,正确写法应该是:

select avg(salary) from emp;

聚合函数的参数一般要写在括号中。

二、准备员工表和测试数据

为了方便演示,我们继续使用员工表 emp。

create table emp (
id int unsigned primary key auto_increment comment '员工ID',
username varchar(20) not null unique comment '用户名',
name varchar(10) not null comment '姓名',
gender tinyint unsigned comment '性别:1男,2女',
dept_id int unsigned comment '部门ID',
salary decimal(10, 2) comment '工资',
entry_date date comment '入职日期',
create_time datetime comment '创建时间',
update_time datetime comment '修改时间'
) comment '员工表';

准备几条测试数据:

insert into emp(username, name, gender, dept_id, salary, entry_date, create_time, update_time)
values
('zhangsan', '张三', 1, 1, 8000.00, '2024-03-01', now(), now()),
('lisi', '李四', 1, 1, 9500.00, '2024-04-10', now(), now()),
('wangwu', '王五', 2, 2, 7200.00, '2024-05-15', now(), now()),
('zhaoliu', '赵六', 1, 2, 12000.00, '2024-06-01', now(), now()),
('xiaohong', '小红', 2, 3, 8800.00, '2024-06-20', now(), now());

这里多加了一个字段:

dept_id

表示员工所属部门 id。

后面做分组统计时会用到它。

三、count:统计数量

count 用来统计数量。

1. 统计员工总数

select count(*)
from emp;

这条 SQL 会统计 emp 表中一共有多少条员工记录。

2. 给统计结果起别名

直接查询 count(*) 时,结果列名可能不太直观。

可以使用 as 起别名:

select count(*) as total
from emp;

查询结果类似:

total
5

这样结果就更清楚。

3. 统计男性员工数量

select count(*) as total
from emp
where gender = 1;

这里先通过 where gender = 1 筛选男性员工,再使用 count(*) 统计数量。

也就是说:

where 先过滤数据
count 再统计过滤后的结果

四、count(*)、count(字段)、count(1) 的区别

在学习 SQL 时,经常会看到几种写法:

count(*)
count(id)
count(1)

它们都能统计数量,但含义有些区别。

1. count(*)

select count(*) from emp;

表示统计表中的记录数。

这是最常见的写法。

2. count(字段)

select count(phone) from emp;

表示统计 phone 字段不为 null 的记录数。

如果某些员工手机号为空,那么 count(phone) 不会统计这些空值。

3. count(1)

select count(1) from emp;

也可以统计记录数。

初学阶段建议优先使用:

count(*)

简单、清楚,也很常见。

五、sum:求和

sum 用来对某个数字字段求和。

1. 统计所有员工工资总和

select sum(salary) as total_salary
from emp;

查询结果类似:

total_salary
45500.00

2. 统计某个部门的工资总和

select sum(salary) as total_salary
from emp
where dept_id = 1;

这条 SQL 表示统计部门 id 为 1 的员工工资总和。

先通过 where 过滤部门,再对过滤后的工资求和。

六、avg:求平均值

avg 用来计算平均值。

1. 查询员工平均工资

select avg(salary) as avg_salary
from emp;

查询结果可能是:

avg_salary
9100.000000

MySQL 默认可能会保留较多小数位。

如果想保留两位小数,可以使用 round:

select round(avg(salary), 2) as avg_salary
from emp;

2. 查询女性员工平均工资

select round(avg(salary), 2) as avg_salary
from emp
where gender = 2;

这条 SQL 表示统计女性员工平均工资。

七、max 和 min:最大值和最小值

max 用来求最大值,min 用来求最小值。

1. 查询最高工资

select max(salary) as max_salary
from emp;

2. 查询最低工资

select min(salary) as min_salary
from emp;

3. 查询最早入职日期

select min(entry_date) as earliest_entry_date
from emp;

日期也可以比较大小。

对于日期来说:

越早的日期越小
越晚的日期越大

所以 min(entry_date) 可以查询最早入职日期。

4. 查询最晚入职日期

select max(entry_date) as latest_entry_date
from emp;

八、多个聚合函数一起使用

聚合函数可以一起查询。

比如统计员工总数、平均工资、最高工资、最低工资:

select
count(*) as total,
round(avg(salary), 2) as avg_salary,
max(salary) as max_salary,
min(salary) as min_salary
from emp;

查询结果类似:

totalavg_salarymax_salarymin_salary
5 9100.00 12000.00 7200.00

这种写法在统计接口中非常常见。

比如后台首页需要展示:

员工总数
平均工资
最高工资
最低工资

就可以通过一条 SQL 查询出来。

九、group by:分组查询

前面的聚合函数都是对整张表或者某个条件过滤后的数据做统计。

但是有时候我们需要按照某个字段分组统计。

比如:

每个性别各有多少员工
每个部门各有多少员工
每个部门平均工资是多少

这时候就需要用到:

group by

1. 按性别统计员工数量

select gender, count(*) as total
from emp
group by gender;

查询结果类似:

gendertotal
1 3
2 2

这条 SQL 的执行逻辑可以理解为:

  • 按照 gender 字段把员工分成多组
  • 每一组分别统计数量
  • 返回每组的性别和数量
  • 2. 按部门统计员工数量

    select dept_id, count(*) as total
    from emp
    group by dept_id;

    查询结果类似:

    dept_idtotal
    1 2
    2 2
    3 1

    这表示:

    部门 1 有 2 个员工
    部门 2 有 2 个员工
    部门 3 有 1 个员工

    不过这里只能看到部门 id,还看不到部门名称。

    如果想看到部门名称,就需要多表查询。这个内容我们下一篇会详细讲。

    十、分组后使用聚合函数

    group by 经常和聚合函数一起使用。

    1. 统计每个部门平均工资

    select dept_id, round(avg(salary), 2) as avg_salary
    from emp
    group by dept_id;

    查询结果类似:

    dept_idavg_salary
    1 8750.00
    2 9600.00
    3 8800.00

    2. 统计每个部门最高工资

    select dept_id, max(salary) as max_salary
    from emp
    group by dept_id;

    3. 统计每个部门工资总和

    select dept_id, sum(salary) as total_salary
    from emp
    group by dept_id;

    这类统计在后台系统中很常见。

    比如部门报表、薪资统计、订单统计、销售统计,本质上都离不开分组查询。

    十一、having:对分组后的结果再过滤

    where 是对原始数据进行过滤。

    having 是对分组统计后的结果进行过滤。

    比如我们要查询员工数量大于 1 的部门:

    select dept_id, count(*) as total
    from emp
    group by dept_id
    having count(*) > 1;

    这条 SQL 的意思是:

  • 先按照 dept_id 分组
  • 每组统计员工数量
  • 只保留员工数量大于 1 的部门
  • where 和 having 的区别

    关键字作用阶段能否使用聚合函数
    where 分组前过滤原始数据 一般不能直接使用聚合函数
    having 分组后过滤统计结果 可以使用聚合函数

    比如查询工资大于 8000 的员工,再按部门统计:

    select dept_id, count(*) as total
    from emp
    where salary > 8000
    group by dept_id;

    这里的 where salary > 8000 是先过滤员工。

    如果要筛选统计后人数大于 1 的部门:

    select dept_id, count(*) as total
    from emp
    group by dept_id
    having count(*) > 1;

    这里的 having count(*) > 1 是对分组后的结果过滤。

    十二、group by 查询中的常见错误

    1. select 中乱写非分组字段

    错误示例:

    select dept_id, name, count(*)
    from emp
    group by dept_id;

    这条 SQL 的问题是:

    name

    既不是分组字段,也不是聚合函数字段。

    因为按部门分组后,一个部门中可能有多个员工姓名,数据库不知道应该返回哪个 name。

    所以更合理的写法是:

    select dept_id, count(*) as total
    from emp
    group by dept_id;

    简单记忆:

    使用 group by 时,select 后面一般写分组字段和聚合函数。

    2. where 中直接使用聚合函数

    错误示例:

    select dept_id, count(*) as total
    from emp
    where count(*) > 1
    group by dept_id;

    这是错误的。

    因为 where 是分组前执行的,那个时候还没有 count(*) 的统计结果。

    正确写法应该使用 having:

    select dept_id, count(*) as total
    from emp
    group by dept_id
    having count(*) > 1;

    十三、SQL 执行顺序简单理解

    虽然 SQL 写法顺序是:

    select ...
    from ...
    where ...
    group by ...
    having ...
    order by ...
    limit ...

    但可以简单理解执行逻辑是:

    from 找到表
    where 过滤原始数据
    group by 分组
    having 过滤分组结果
    select 选择返回字段
    order by 排序
    limit 分页限制

    不需要一开始就死记底层执行流程,但至少要知道:

    where 在 group by 前
    having 在 group by 后

    这样就不容易把 where 和 having 用混。

    十四、对应到 Spring Boot + MyBatis 项目

    假设我们要写一个统计接口:统计每个性别的员工数量。

    1. VO 类设计

    public class GenderCountVO {
    private Integer gender;
    private Long total;

    // getter、setter 省略
    }

    2. Controller 层

    @GetMapping("/count/gender")
    public Result countByGender() {
    List<GenderCountVO> list = empService.countByGender();
    return Result.success(list);
    }

    3. Service 层

    @Override
    public List<GenderCountVO> countByGender() {
    return empMapper.countByGender();
    }

    4. Mapper 层

    @Mapper
    public interface EmpMapper {

    @Select("select gender, count(*) as total from emp group by gender")
    List<GenderCountVO> countByGender();
    }

    文字说明

    这就是一个简单的统计接口。

    数据库中通过:

    group by gender

    按性别分组。

    通过:

    count(*) as total

    统计每组数量。

    查询结果封装成 GenderCountVO 返回给前端。

    如果前端要画饼图、柱状图,这种接口就很常见。

    十五、常见问题总结

    1. count(*) 会统计 null 吗?

    count(*) 统计的是记录数,不关心某个字段是否为 null。

    但是 count(字段) 只统计该字段不为 null 的记录。

    2. avg 会不会计算 null?

    avg(字段) 会忽略 null 值。

    比如有 5 条数据,其中 1 条工资是 null,那么 avg(salary) 只会对非空工资进行平均计算。

    3. group by 后 select 能写哪些字段?

    一般写:

    分组字段
    聚合函数

    比如:

    select dept_id, count(*)
    from emp
    group by dept_id;

    不要随便写没有分组、也没有聚合的普通字段。

    4. where 和 having 怎么选?

    如果是过滤原始数据,用 where。

    如果是过滤分组统计后的结果,用 having。

    5. 统计接口返回实体类还是 VO?

    建议返回 VO。

    比如 GenderCountVO、DeptCountVO、SalaryStatsVO。

    因为统计结果不一定和数据库表完全对应,使用 VO 更清楚。

    十六、实际开发建议

    写聚合统计 SQL 时,可以注意下面几点:

  • 统计总数优先使用 count(*)
  • 聚合函数结果建议起别名
  • 金额、工资平均值可以配合 round 保留小数位
  • 分组查询时,select 后面尽量只写分组字段和聚合字段
  • 过滤原始数据用 where
  • 过滤分组后的结果用 having
  • 统计接口建议使用 VO 接收结果
  • 如果需要显示部门名称、分类名称,一般要结合多表查询
  • 分组统计结果经常用于图表展示
  • 写统计 SQL 时先想清楚“按什么分组”和“统计什么”
  • 十七、总结

    这一篇主要学习了 SQL 中的聚合函数和分组查询。

    聚合函数用来对数据进行统计计算,常见的有 count、sum、avg、max、min。

    group by 用来按照某个字段分组统计,比如按性别统计人数、按部门统计人数、按部门统计平均工资。

    where 和 having 是很容易混淆的两个关键字。where 用来在分组前过滤原始数据,having 用来在分组后过滤统计结果。

    这一篇内容已经开始从普通查询走向统计查询了。后面写项目中的首页统计、部门统计、报表接口、图表接口时,基本都会用到这些知识点。

    下一篇我们继续学习多表查询,也就是 inner join、left join、right join 的使用。

    赞(0)
    未经允许不得转载:171主机测评 » 【数据库基础|第5篇】聚合函数与 group by 分组查询详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址