【MySql】怎么利用聚合函数实现高效地分组统计?
文章目录
聚合函数:怎么高效地进行分组统计?
MySQL中有5种聚合函数较为常用,分别是求和函数sum(), 求平均函数avg(), 最大值函数max(), 最小值函数min()和计数函数count()。
在超市项目中有一个需求是这样的:经营者提出,他们需要统计一个门店,每天、每个单品的销售情况,包括销售数量和销售金额等。这里涉及3个数据表,具体信息如下所示:
销售明细表demo.transactiondetails:
销售单头表demo.transactionhead:
商品信息表demo.goodsmaster:
要统计销售,就要用到数据求和,请接着往下看:
sum( )
sum( ) 函数可以返回指定字段值的和。
我们可以用它来获取用户某个门店,每天、每种商品的销售总计数据:
mysql> select -> left(b.transdate, 10), -- 从关联表获取交易时间,并且通过LEFT函数,获取交易时间字符串的左边10个字符,得到年月日的数据 -> c.goodsname, -- 从关联表获取商品名称 -> sum(a.quantity), -- 数量求和 -> sum(a.salesvalue) -- 金额求和 -> from -> demo.transactiondetails a -> join -> demo.transactionhead b on (a.transactionid = b.transactionid) -> join -> demo.goodsmaster c on (a.itemnumber = c.itemnumber) -> group by left(b.transdate, 10) , c.goodsname -- 分组 -> order by left(b.transdate, 10) , c.goodsname; -- 排序 +-----------------------+-----------+-----------------+-------------------+ | LEFT(b.transdate, 10) | goodsname | SUM(a.quantity) | SUM(a.salesvalue) | +-----------------------+-----------+-----------------+-------------------+ | 2020-12-01 | 书 | 2.000 | 178.00 | | 2020-12-01 | 笔 | 5.000 | 25.00 | | 2020-12-02 | 书 | 4.000 | 356.00 | | 2020-12-02 | 笔 | 16.000 | 80.00 | +-----------------------+-----------+-----------------+-------------------+ 4 rows in set (0.01 sec)
对上面出现的2个关键字:left 和 order by进行解释:
left(str, n):表示返回字符串str最左边的n个字符。我们这里的 left(a.transdate,10),表示返回交易时间字符串最左边的 10 个字符。
在 MySQL 中,DATETIME 类型的默认格式是:YYYY-MM-DD,也就是说,年份 4 个字符,之后是“-”,然后是月份 2 个字符,之后又是“-”,然后是日 2 个字符,所以完整的年月日是 10 个字符。用户要求按照日期统计,所以,我们需要从日期时间数据中,把年月日的部分截取出来。
order by:表示按照指定的字段排序。
超市经营者指定按照日期和单品统计,那么,统计的结果按照交易日期和商品名称的顺序排序,会更加清晰。
对上述过程进行拆解,看看这个查询是如何进行的。
第一步,完成3个表的连接。
第二步,对结果集按照交易时间和商品名称进行分组,我们可以分成下面4组。
组一:
组二:
组三:
组四:
第三步,对各组的销售数量和销售金额进行统计,并且按照交易日期和商品名称排序,这样就得到了我们需要的结果,如下所示:
+-----------------------+-----------+-----------------+-------------------+ | LEFT(b.transdate, 10) | goodsname | SUM(a.quantity) | SUM(a.salesvalue) | +-----------------------+-----------+-----------------+-------------------+ | 2020-12-01 | 书 | 2.000 | 178.00 | | 2020-12-01 | 笔 | 5.000 | 25.00 | | 2020-12-02 | 书 | 4.000 | 356.00 | | 2020-12-02 | 笔 | 16.000 | 80.00 | +-----------------------+-----------+-----------------+-------------------+ 4 rows in set (0.01 sec)
需要注意的是,求和函数获取的是分组中的合计数据,所以你要对分组的结果有准确的把握,否则就很容易搞错。这也就是说,你要知道是按什么字段进行分组的。如果是按多个字段分组,你要知道字段之间有什么样的层次关系;如果是按照以字段作为变量的某个函数进行分组的,你要知道这个函数的返回值是什么,返回值又是如何影响分组的等。
avg( ) & max( ) & min( )
1.avg( )
首先,我们来学习下计算平均值的函数avg( )。它的作用是,通过计算分组内指定字段值的和,以及分组内的记录数,算出分组内指定字段的平均值。
举个例子,如果用户需要计算每天、每种商品,平均一次卖出多少个、多少钱,这个时候,我们就可以用到avg( )函数了,如下所示:
mysql> select -> left(a.transdate, 10), -> c.goodsname, -> avg(b.quantity), -- 平均数量 -> avg(b.salesvalue) -- 平均金额 -> from -> demo.transactionhead a -> join -> demo.transactiondetails b on (a.transactionid = b.transactionid) -> join -> demo.goodsmaster c on (b.itemnumber = c.itemnumber) -> group by left(a.transdate,10),c.goodsname -> order by left(a.transdate,10),c.goodsname; +-----------------------+-----------+-----------------+-------------------+ | LEFT(a.transdate, 10) | goodsname | AVG(b.quantity) | AVG(b.salesvalue) | +-----------------------+-----------+-----------------+-------------------+ | 2020-12-01 | 书 | 2.0000000 | 178.000000 | | 2020-12-01 | 笔 | 5.0000000 | 25.000000 | | 2020-12-02 | 书 | 2.0000000 | 178.000000 | | 2020-12-02 | 笔 | 8.0000000 | 40.000000 | +-----------------------+-----------+-----------------+-------------------+ 4 rows in set (0.00 sec)
2.max( )和min( )
MAX( ) 表示获取指定字段在分组中的最大值,MIN( ) 表示获取指定字段在分组中的最小值。
它们的实现原理差不多,下面重点讲一下 MAX( ),知道了它的用法,MIN( ) 也就很好理解了。我们还是来看具体的例子。假如用户要求计算每天里的一次销售的最大数量和最大金额,就可以用下面的代码,得到我们需要的结果:
mysql> select -> left(a.transdate, 10), -> max(b.quantity), -- 数量最大值 -> max(b.salesvalue) -- 金额最大值 -> from -> demo.transactionhead a -> join -> demo.transactiondetails b on (a.transactionid = b.transactionid) -> join -> demo.goodsmaster c on (b.itemnumber = c.itemnumber) -> group by left(a.transdate,10) -> order by left(a.transdate,10); +-----------------------+-----------------+-------------------+ | LEFT(a.transdate, 10) | MAX(b.quantity) | MAX(b.salesvalue) | +-----------------------+-----------------+-------------------+ | 2020-12-01 | 5.000 | 178.00 | | 2020-12-02 | 10.000 | 267.00 | +-----------------------+-----------------+-------------------+ 2 rows in set (0.00 sec)
代码很简单,你一看就明白了。但是,这里有个问题你要注意:千万不要以为 MAX(b.quantity)和 MAX(b.salesvalue)算出的结果一定是同一条记录的数据。实际上,MySQL 是分别计算的。下面我们就来分析一下刚刚的查询。
查询中用到 3 个相互关联的表:销售流水明细表、销售流水单头表和商品信息表。这 3 个表连接完成之后,MySQL 进行了分组。
组一:
组二:
在第一组中,最大数量出现在第 2 条记录,是 5;最大金额出现在第 1 条记录,是 178。同样道理,在第二组中,最大数量出现在第 4 条记录,是 10;最大金额则出现在第 1 条记录,是 267。
所以,max(字段)这个函数返回分组集中最大的那个值。如果你要查询max(字段1)和max(字符2),而它们是相互独立、分别计算的,所以我们千万不要想当然的认为结果在同一条记录上,那样的话就掉到坑里面了。
count( )
通过count( ), 我们可以了解数据集的大小,这对系统优化十分重要。
举个小例子,由于用户的销售数据很多,而且每天都在增长,因此,在做销售查询的时候,经常会遇到卡顿的问题。这是因为,查询的数据量太大了,导致系统不得不花很多时间来处理数据,并给数据集分配资源,比如内存什么的。
怎么解决卡顿的问题呢?我们想到了分页的策略。
所谓的分页策略,其实就是,不把查询的结果一次性全部返回给客户端,而是根据用户电脑屏幕的大小,计算一屏可以显示的记录数,每次只返回用户电脑屏幕可以显示的数据集。接着,再通过翻页、跳转等功能按钮,实现查询目标的精准锁定。这样一来,每次查询的数据量较小,也就大大提高了系统的响应速度。
这个策略的实现的一个关键,就是要计算出符合条件的记录有多少条,之后才能计算出一共有几页,能不能翻页或者跳转。
要计算记录数,就要用到count( )函数了,这个函数有两种情况:
count(*): 统计一共有多少条记录;
count(字段): 统计有多少个不为空的字段值。
1.count(*)
如果count(*)与group by一起使用,就表示统计分组内有多少条数据。它也可以单独使用,这就相当于数据集全体是一个分组,统计全部数据集的记录数。
举个例子,假设我们有个销售流水明细表如下:
mysql> select * -> from demo.transactiondetails; +---------------+------------+----------+-------+------------+ | transactionid | itemnumber | quantity | price | salesvalue | +---------------+------------+----------+-------+------------+ | 1 | 1 | 2.000 | 89.00 | 178.00 | | 1 | 2 | 5.000 | 5.00 | 25.00 | | 2 | 1 | 3.000 | 89.00 | 267.00 | | 2 | 2 | 6.000 | 5.00 | 30.00 | | 3 | 1 | 1.000 | 89.00 | 89.00 | | 3 | 2 | 10.000 | 5.00 | 50.00 | +---------------+------------+----------+-------+------------+ 6 rows in set (0.00 sec)
如果我们一屏可以显示30行,需要多少页才能显示完这个表的数据呢?
mysql> select count(*) -> from demo.transactiondetails; +----------+ | COUNT(*) | +----------+ | 6 | +----------+ 1 row in set (0.03 sec)
我们这里只有 6 条数据,一屏就可以显示了,所以一共 1 页。
那么,如果超市经营者想知道,每天、每种商品都有几次销售,我们就需要按天、按商品名称,进行分组查询:
mysql> select -> left(a.transdate, 10), c.goodsname, count(*) -- 统计销售次数 -> from -> demo.transactionhead a -> join -> demo.transactiondetails b on (a.transactionid = b.transactionid) -> join -> demo.goodsmaster c on (b.itemnumber = c.itemnumber) -> group by left(a.transdate, 10) , c.goodsname -> order by left(a.transdate, 10) , c.goodsname; +-----------------------+-----------+----------+ | LEFT(a.transdate, 10) | goodsname | COUNT(*) | +-----------------------+-----------+----------+ | 2020-12-01 | 书 | 1 | | 2020-12-01 | 笔 | 1 | | 2020-12-02 | 书 | 2 | | 2020-12-02 | 笔 | 2 | +-----------------------+-----------+----------+ 4 rows in set (0.00 sec)
运行这段代码,我们就得到了每天、每种商品有几次销售的全部结果。
2.count (字段)
count (字段)用来统计分组内这个字段的值出现了多少次。如果字段值是空,就不统计。
假设我们有一个商品信息表,里面包括了商品编号、条码、名称、规格、单位和售价的信息。
mysql> select * -> from demo.goodsmaster; +------------+---------+-----------+---------------+------+------------+ | itemnumber | barcode | goodsname | specification | unit | salesprice | +------------+---------+-----------+---------------+------+------------+ | 1 | 0001 | 书 | 16开 | 本 | 89.00 | | 2 | 0002 | 笔 | NULL | 支 | 5.00 | | 3 | 0002 | 笔 | NULL | 支 | 10.00 | +------------+---------+-----------+---------------+------+------------+ 3 rows in set (0.01 sec)
如果我们要统计字段“goodsname”出现了多少次,就要用到函数 COUNT(goodsname),结果是 3 次:
mysql> select count(goodsname) -- 统计商品名称字段 -> from demo.goodsmaster; +------------------+ | COUNT(goodsname) | +------------------+ | 3 | +------------------+ 1 row in set (0.00 sec)
如果我们统计字段“specification”,用 COUNT(specification),结果是 1 次:
mysql> select count(specification) -- 统计规格字段 -> from demo.goodsmaster; +----------------------+ | COUNT(specification) | +----------------------+ | 1 | +----------------------+ 1 row in set (0.00 sec)
说明:3 条记录里面的字段“goodsname”没有空值,因此被统计了 3 次;而字段“specification”有 2 个空值,因此只统计了 1 次。
理解了这一点,你就可以利用计数函数对某个字段计数时,不统计空值的特点,对表中字段的非空值进行计数了。
猜你喜欢
- 【MySql】批量替换数据表中某字段的值
- 系统域名变更,需要把数据库存储的图片、文件等链接域名也替换掉,使用了文字替换的方法,在此记录一下:数据库:mysqlUPDATE user SET headimg = REPLACE(headimg, 'a.com', 'b.com');其中 user 是数据表,headimg是其中的字段,把headimg中'a.com'全部替换成'b.com',headimg中
- 【MySql】select * 查询慢的原因和优化方法
- ‌使用SELECT *查询语句会导致查询效率低下的原因主要包括‌:它会查询所有的列和行数据,包括不需要的和重复的列,因此会占用更多的系统资源,导致查询效率低下。此外,由于...
- 【MySql】怎么利用聚合函数实现高效地分组统计?
- 文章目录聚合函数:怎么高效地进行分组统计?sum( )avg( ) & max( ) & min( )count( )聚合函数:怎么高效地进行分组统计?MySQL中有5种聚合函数较为常用,分别是求和函数sum(), 求平均函数avg(), 最大值函数max(), 最小值函数min()和计数函数count()。在超市项目中有一个需求是这样的:经营者提出,他们需要统计一个门店,每天、每个单品的销售情况,包括销售数量和销售金额等。这里涉及3个数据表,具体信息如下所示:销售明细表demo
- 【MySql】Centos7 安装 MySQL5.7 步骤
- Centos7 安装 MySQL5.7 步骤(一)、使用yum源方式安装4.1 开启mysql的远程访问权限4.2 为firewalld添加开放端口33064.3 远程连接测试3.1 启动mysql并查看状态3.2 获取临时密码3.3 登录mysql3.4 修改登录密码2.1 下载mysql的yum源配置2.2 安装mysql的yum源2.3 使用yum方式安装mysql2.3.1 安装过程中报错解决1、卸载系统自带 mariadb2、下载并安装MySQL官方的 Yum3、使用并设置mysql4
- 【MySql】mysql | mysql5.7升级8.0注意事项
- 一、说明 1、应公司要求,mysql5.7安全漏洞较多,需要升级到8.0 2、记录注意事项备不时之需二、注意事项1、加密算法1)加密算法8.0改了,导致navicat无法连接2)解决:use mysql;ALTER USER 'root'@'%'&n
- 【MySQL】MySQL索引原理与慢查询优化
- 浏览目录# 一、索引介绍二、索引方法三、索引类型四、聚合索引和辅助索引五、测试索引六、正确使用索引七、组合索引八、注意事项九、查询计划十、慢日志查询十一、大数据量分页优化一、索引介绍# 一般的应用系统,读写的比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出现问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。什么是索引:简单来说,相当于图书的目录,可以帮助用户快速找到需要的内容。 在mysql中
- 【MySQL】初识数据库
- 第一篇:初识数据库#一、数据库管理软件的由来#基于我们之前所学,数据要想永久保存,都是保存于文件中,毫无疑问一个文件仅仅只能存在于某一台机器上。 如果我们暂且忽略直接基于文件来存取数据的效率问题,并且假设程序所有的组件都运行在一台机器上,那么用文件存取数据,并没有问题。 很不幸,这些假设都是你自己意淫出来的,上诉假设存在以下几个问题:1.程序所有的组件就不可能运行在一台机器上##因为这台机器一旦挂掉则意味着整个软件的崩溃,并且程序的执行效率依赖于承载它的硬件,而一台机器的性能
- 【MySQL】MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁
- 一、视图#视图是一个虚拟表(非真实存在的),其本质是‘根据SQL语句获取动态的数据集,并为其命名‘ ,用户使用时只需使用“名称”即可获取结果集,可以将该结果集当做表来使用。使用视图我们可以把查询过程中的临时表摘出来,用视图去实现,这样以后再想操作该临时表的数据时就无需重写复杂的SQL了,直接去视图中查找即可,但视图有明显的效率问题,并且视图是存放在数据库中的,如果我们程序中使用的SQL过分依赖数据库中的视图,即强耦合,那就意味着扩展SQL极为不便,因此并不推荐使用视图有如下的特点: &