MySQL第七讲·怎么利用聚合函数实现高效地分组统计?

在这里插入图片描述

你好,我是安然无虞。

文章目录

  • 聚合函数:怎么高效地进行分组统计?
    • sum( )
    • avg( ) & max( ) & min( )
    • count( )

在这里插入图片描述

聚合函数:怎么高效地进行分组统计?

MySQL中有5种聚合函数较为常用,分别是求和函数sum(), 求平均函数avg(), 最大值函数max(), 最小值函数min()和计数函数count()。

在超市项目中有一个需求是这样的:经营者提出,他们需要统计一个门店,每天、每个单品的销售情况,包括销售数量和销售金额等。这里涉及3个数据表,具体信息如下所示:

销售明细表demo.transactiondetails:

img

销售单头表demo.transactionhead:

img

商品信息表demo.goodsmaster:

img

要统计销售,就要用到数据求和,请接着往下看:

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个表的连接。

img

第二步,对结果集按照交易时间和商品名称进行分组,我们可以分成下面4组。

组一:

img

组二:

img

组三:

img

组四:

img

第三步,对各组的销售数量和销售金额进行统计,并且按照交易日期和商品名称排序,这样就得到了我们需要的结果,如下所示:

+-----------------------+-----------+-----------------+-------------------+
| 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 进行了分组。

组一:

img

组二:

img

在第一组中,最大数量出现在第 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 次。

理解了这一点,你就可以利用计数函数对某个字段计数时,不统计空值的特点,对表中字段的非空值进行计数了。

遇见安然遇见你,不负代码不负卿。
谢谢老铁的时间,咱们下篇再见~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119478.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能网联汽车基础软件信息安全需求分析

目录 1.安全启动 2.安全升级 3.安全存储 4.安全通信 5.安全调试 6.安全诊断 7.小结 1.安全启动 对于MCU,安全启动主要是以安全岛BootROM为信任根,在MCU启动后,用户程序运行前,硬件加密模块采用逐级校验、并行校验或者混合校…

[AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]

场景 在使用Android Studio的虚拟设备运行App时,需要创建很大镜像文件。这些镜像文件一般都在系统盘,导致系统盘占用增大。怎么把这些镜像的存放路径设置在其他盘? 说明 虚拟设备的和它的镜像默认是放在用户目录\.android\avd位置。如果是在…

python-全自动二维码识别

纯笔记,可以做到全屏识别二维码,自动识别,复制链接,生成简单的二维码,将识别到的内容转为txt import pyautogui from PIL import Image from pyzbar.pyzbar import decode import tkinter as tk from tkinter import …

javaee实验:搭建maven+spring boot开发环境,开发“Hello,Spring Boot”应用

目录 mavenspringboot实验目的实验内容环境的搭建 在开发中,maven和spring都是非常常用、非常重要的管理工具和框架,今天就在这里使用idea进行环境的搭建和创建第一个spring程序 maven 1.1maven是一个跨平台的项目管理工具(主要管理jar包&am…

如何实现Word文档中的书签双向定位

工作中,经常需要拟定合同,一般都有固定的模板,在特定的位置填写内容。通过zOffice编辑合同文件时,可以在模板需要填写的位置预设书签,配合zOffice SDK使用,利用zOffice书签双向定位的特性,更方便…

王道p18 第12题假设 A中的 n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素:否则输出-1

视频讲解在:👇 p18 第12题 c语言实现王道数据结构课后习题_哔哩哔哩_bilibili 从前向后扫描数组元素,标记出一个可能成为主元素的元素 Num。然后重新计数,确认 Num 是否是主元素。 我们可分为以下两步: 1.选取候选的主元素。依…

CocosCreator使用物理引擎和回调

在2d中开启碰撞需要在项目设置–功能裁剪–2D物理系统【选择 基于Box2D的2D物理系统】 如果想要两个物体碰撞,则需要添加刚体和碰撞【!!重点】 设置刚体类型 可以设为四种, Static 静态刚体,零质量,零速度…

【JAVA】:万字长篇带你了解JAVA并发编程-并发设计模式【五】

目录 【JAVA】:万字长篇带你了解JAVA并发编程-并发设计模式【五】模式分类Immutability模式【不可变模式】Copy-on-Write 模式Thread Local Storage 模式线程池中使用 Guarded Suspension模式扩展 Guarded Suspension 模式 Balking模式Thread-Per-MessageWorker Thr…

【C语言 | 符号】C语言中符号易出错的地方

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…

养老院展示服务预约小程序的作用是什么

养老院无论在哪个城市都有很高需求度,不少银发人群会因为种种原因而前往,而养老院近些年来各种服务也比较完善,增加了客户信任度及接受度,但对院方来说,也存在着一些痛点: 1、品牌传播服务呈现难 养老院也…

[NLP] 使用Llama.cpp和LangChain在CPU上使用大模型

一 准备工作 下面是构建这个应用程序时将使用的软件工具: 1.Llama-cpp-python 下载llama-cpp, llama-cpp-python [NLP] Llama2模型运行在Mac机器-CSDN博客 2、LangChain LangChain是一个提供了一组广泛的集成和数据连接器,允许我们链接和编排不同的模块。可以常…

华大基因推出产前筛查产品NIFTY®,助力防控显性单基因病

随着高通量测序技术在临床应用的迅速进步,从常见的染色体非整倍体扩展到性染色体和拷贝数变异,全球范围内已经开始将无创产前检测(Non-invasive Prenatal Testing,NIPT)的应用范围逐步扩大。近日,华大基因重…

【Mybatis小白从0到90%精讲】15: Mybatis配置打印SQL日志

文章目录 前言配置日志实现前言 日志(Log)是每个程序都不可或缺的一部分,它可以帮助开发人员诊断和调试问题。Mybatis,作为一款备受赞誉的ORM框架,自然也提供了强大的日志功能。 它不仅提供了内置的标准实现,还支持集成各种主流的日志框架,让我们可以轻松地查看最终执行…

休眠和睡眠有哪些区别?如何让电脑一键休眠?

电脑中有休眠和睡眠,那么它们有什么区别呢?下面我们就通过本文来了解一下。 休眠和睡眠的区别 电脑在睡眠状态时,会切断内存之外的设备电源,电脑会进入睡眠状态,当再次唤醒电脑后,不会影响睡眠前保存好的工…

柱状图:带误差棒

误差棒可以表示样本标准差,也可以表示样本标准误。 导入库: import pandas as pd 自定义用来绘制带误差棒(样本标准差或样本标准误)的柱状图: def col(y, x, face, df, errprbarstd) : print(ggplot(df.groupby([x…

搭建WAMP网站教程(Windows+Apache+MySQL+PHP)

之前为了学习网络安全,从搭建网站学起,对网站运行有个初步的了解。 今天翻到了之前的笔记,顺手发到csdn上了。 搭建网站步骤 一、Apache 安装Apache,下载Apache之后把Apache解压,此处解压到C:\目录下 2.然后要记得安…

Notepad++中删除连续的任意n行

使用Notepad里的行标记功能,可以删除指定的任意n行。 案例1,删除sample2.dat里的第201行到第10000行。方法如下: (1) 用户NotePad打开sample2.dat,右击201行 —》“开始/结束”/开始 图(1) 选择行的起点:201 (2) 接…

Git 的基本操作 ——命令行

Git 的工作流程 详解如下: 本地仓库:是在开发人员自己电脑上的Git仓库,存放我们的代码(.git 隐藏文件夹就是我们的本地仓库) 远程仓库:是在远程服务器上的Git仓库,存放代码(可以是github.com或者gitee.com 上的仓库,或者自己该公司的服务器…

【动手学深度学习】课程笔记 05-07 线性代数、矩阵计算和自动求导

05 线性代数 1. 基础知识补充 向量相关 矩阵相关 简单来说,范数是用来衡量矩阵(张量)大小的值,范数的值有不同的规定。 2. 代码实现 仅记录一些我比较陌生的知识。 张量的克隆 A torch.arange(20, dtypetorch.float32).resh…

10个免费3D模型网站

作为一名独立游戏开发者,自己创建图形、配乐、动画和更多东西是相当具有挑战性的。 创建资产所需的成本和时间有时是许多游戏开发商无法承受的。 这就是他们选择在互联网上搜索免费内容的原因。现在,在浩瀚的内容海洋中获得如此免费的东西有点困难。 本文…