首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一个分组查询引发的思考

一个分组查询引发的思考

作者头像
时间静止不是简史
发布于 2022-01-05 06:44:44
发布于 2022-01-05 06:44:44
1.1K00
代码可运行
举报
文章被收录于专栏:Java探索之路Java探索之路
运行总次数:0
代码可运行

一个分组查询引发的思考

我们在看项目代码或者SQL语句时, 往往会看到很多非常复杂的业务或者SQL 那么问题来了. 复杂SQL是如何写成的? 下面通过一个数据展示的需求来体会到复杂的SQL是如何书写的

1. 计算平均等待时间

当你拿到需求是一般都是比较简单的, 例如统计某些数据, eg: 统计每天平均等待时间

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- 计算平均等待时间
-- 逻辑
select 等待总时间/等待数 as 列名
from 表名
where 时间(后续根据要求可改为按年月日查询)
between 起始时间
and 结束时间
group by 业务名称

-- eg
select businame,
round(sum(waitingTime)/count(case when `state`=2 or 3 then 1 else null end)/60,1) as avgWaitingTime
from t_number_takers 
where DATE_FORMAT(takeTime,'%Y-%m-d%')
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by businame

2. 分组统计各项业务

下面产品经理又给你说, 那你搞个按年月日进行统计吧, 这样用户可以按照年月日进行统计各种信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- 通过时间分组(年月日)并根据分组显示每天时长
-- 逻辑
select 各项业务
from 表名
where 时间(后续根据要求可改为按年月日查询)
between 起始时间
and 结束时间
group by 时间

-- eg: 按日分组查询
select  DATE_FORMAT(takeTime,'%Y-%m-%d') as '时间',
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长(min)',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
WHERE 1=1
and takeTime
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y-%m-%d')

-- eg: 按月分组查询(见下图)
select  DATE_FORMAT(takeTime,'%Y-%m') as '时间',
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长(min)',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
WHERE 1=1
and takeTime
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y-%m')

-- eg: 按年分组查询
select  DATE_FORMAT(takeTime,'%Y') as '时间',
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长(min)',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
WHERE 1=1
and takeTime
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y')

ps: 按照时间段(年月日)进行显示时, 用到了 DATE_FORMAT() 函数, 这个改动会对后面的统计带来意想不到的影响

3. 将汇总和统计写在一张表

这个时候, 产品经理又又提出需求了, 需要将汇总信息写在一个接口中返回.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- a.利用 with rollup 进行汇总,1. 我们可以看到汇总的那一行为空值
select  DATE_FORMAT(takeTime,'%Y-%m') as '时间',
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长(min)',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
WHERE 1=1
and takeTime
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y-%m')
with rollup


-- b.解决为空的情况, 利用 ifnull()函数
 ifnull(列名,'列名为空后的字段') as 列名 <=> 当前列下如果有数据为null,就将该列下这一行null改为: 列名为空后的字段

--  当该列不为函数时, 如图2
select ifnull(businame,'总计') as businame,count(1) as busiNum
from t_number_takers
WHERE DATE_FORMAT(takeTime,'%Y-%m-d%')
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by businame  with rollup 

-- 当列为函数时会报错, 如下代码  ifnull(DATE_FORMAT(takeTime,'%Y-%m'),"合计") as '时间'
select  ifnull(DATE_FORMAT(takeTime,'%Y-%m'),"合计") as '时间',
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长(min)',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
WHERE 1=1
and takeTime
BETWEEN  '2021-01-01 00:00:00'
AND  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y-%m')
with rollup

> 1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'callmachine.t_number_takers.takeTime' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
原因是: MySQL 5.7.5及以上功能依赖检测功能。
如果启用了ONLY_FULL_GROUP_BY SQL模式(默认情况下),MySQL将拒绝选择列表,
HAVING条件或ORDER BY列表的查询引用在GROUP BY子句中既未命名的非集合列,也不在功能上依赖于它们。
(5.7.5之前,MySQL没有检测到功能依赖关系,默认情况下不启用ONLY_FULL_GROUP_BY


-- c.根据b后面出现的问题进行解决, 执行第一条指令(当前shell生效), 然后再执行b中出现问题的语句,3.  
-- 可以看到虽然执行没问题, 但是返回结果却没有根据ifnull将 takeTime 为null时的字段改成 我们想要定制的字段: 总计
-- 分组异常时执行下面命令
set sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
-- 还原之前设置
set sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
-- 查询当前sql模式
SELECT @@sql_mode



-- d 利用 union all 进行组合查询, 通过组合查询将总计信息拼在原来的列下(4)
-- 虽然这样写不太规范(在时间里面返回了一个总计的字段, 但是满足了一张表返回所有分组信息和总计结果)
select  DATE_FORMAT(takeTime,'%Y-%m') as '时间' ,
round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长',
round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
sum(case when `id` is not null then 1 else null end) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers
where 1=1
and takeTime
between  '2021-01-01 00:00:00'
and  '2021-12-31 23:59:59'
group by DATE_FORMAT(takeTime,'%Y-%m')
union all select
'总计' as '时间' ,
round(sum(waitingTime)/60,1) as "客户平均等待时长",
round(sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/60,1) as '业务平均办理时长',
count(1) as '业务总数',
sum(case when businame ='开户' then 1 else null end) as '开户',
sum(case when businame ='缴费' then 1 else null end) as '缴费',
sum(case when businame ='业务' then 1 else null end) as '业务',
sum(case when businame ='销户' then 1 else null end) as '销户'
from t_number_takers 

图1

图2

图3

图4

union和union all区别 union all只是合并查询结果,并不会进行去重和排序操作,在没有去重的前提下,使用union all的执行效率要比union高

ps: 后续因为某些原因, 总计还是单独作为一个接口来了, 尴尬~~~

4. 按照任意字段排序

产品又又又有要求, 让页面显示的统计信息可以根据某个字段进行任意排序

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 <!--利用mybaties的${}输出, 对其进行任意字段排序 order by ${param3(排序字段)} ${param4(升序降序asc,desc)} -->
   <select id="statisticalBusinessByYear" resultType="map">
       select  DATE_FORMAT(takeTime,'%Y') as '时间' ,
       round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长',
       round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
       sum(case when `id` is not null then 1 else null end) as '业务总数',
	sum(case when businame ='开户' then 1 else null end) as '开户',
	sum(case when businame ='缴费' then 1 else null end) as '缴费',
	sum(case when businame ='业务' then 1 else null end) as '业务',
	sum(case when businame ='销户' then 1 else null end) as '销户'
       from t_number_takers
       where 1=1
       <if test="param1 != null and param1 != ''">
           <if test="param2 != null and param2 != ''">
               and takeTime
               between  #{param1,jdbcType=VARCHAR}
               and  #{param2,jdbcType=VARCHAR}
           </if>
       </if>
       group by DATE_FORMAT(takeTime,'%Y') 
       order by ${param3} ${param4}
   </select>

5. 动态查询列信息

之前按照产品原型写的业务列统计, 如 业务总数/开户/销户/业务/缴费都是根据原型图写的. 产品又不干了. 说万一后面客户增加了其他列的话, 你这种固定显示的业务列统计怎么合乎规范呢? 好吧, 我们继续改. 不慌. 动态查询列信息核心逻辑: 在原来的基础上, 首先新增一个获取所有业务列的接口, 然后在当前查询Dao接口传入这个list. mapper.xml 中 通过使用 mybaties的 < foreach >标签进行遍历

首先看下臃肿的dao层接口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<String> queryBusinessList();

List<Map<String, Object>> statisticalBusinessByMounth(@Param("startTime") String startTime, @Param("endTime") String endTime,
                                                      @Param("statisticalBusinessType") String statisticalBusinessType,
                                                      @Param("sortType") String sortType,@Param("list") List<String> businessList);

mapper.xml下对应的查询语句

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<select id="queryBusinessList" resultType="java.lang.String">
    select distinct(businame) from t_number_takers
</select>
    
<select id="statisticalBusinessByMounth" resultType="map">
        select  DATE_FORMAT(takeTime,'%Y-%m') as '时间' ,
        round((sum(waitingTime)/count(case when `state` in(2,3) then 1 else null end))/60,1) as '客户平均等待时长',
        round((sum(UNIX_TIMESTAMP(endTime) - UNIX_TIMESTAMP(acceptTime))/count(case when `state`in (2,3) then 1 else null end)/60),1) as '业务平均办理时长',
        sum(case when `id` is not null then 1 else null end) as '业务总数'
        <if test="list != null and list.size() != 0">
            ,
            <foreach collection="list" item="item" separator=",">
                sum(case when businame = #{item} then 1 else null end) as #{item,jdbcType=VARCHAR}
            </foreach>
        </if>
        from t_number_takers
        where 1=1
        <if test="param1 != null and param1 != ''">
            <if test="param2 != null and param2 != ''">
                and takeTime
                between  #{param1,jdbcType=VARCHAR}
                and  #{param2,jdbcType=VARCHAR}
            </if>
        </if>
        group by DATE_FORMAT(takeTime,'%Y-%m')
        order by ${param3} ${param4}
    </select>

总结:

虽然上面的例子可能不如你在其他项目中看到的其他项目那么复杂. 就个人经历而言, 复杂的SQL语句或者业务, 不是一下子就写成的, 而是随着业务的复杂之后, 慢慢的优化而成的. 我们不要过于的惧怕这些复杂的代码, 应该在战略上蔑视它, 在战术上重视它. 多多磨练自己的技巧, 并且及时反思, 而这些行为一定会给你带来意想不到的收获~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/12/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
全网最全Linux 运行jar包的几种方式[通俗易懂]
最常用的启动jar包命令,特点:当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出
全栈程序员站长
2022/11/10
6.2K0
全网最全Linux 运行jar包的几种方式[通俗易懂]
nohup基本使用
兮动人
2024/02/07
5620
Linux系统:第十一章:常用命令
whereis搜索redis服务执行文件:whereis redis-server
Java廖志伟
2021/02/02
8650
Linux系统:第十一章:常用命令
关于 Linux后台运行进程
最近在搭建kafka的过程中,发现启动相关服务后,终端界面无法再进行操作。每次都需要开启多个终端界面,实在是特别麻烦。于是就研究了下Linux后台启动程序的方法,并整理了如下的笔记。
用户8710643
2021/06/11
2.6K0
linux nohup.out文件过大解决方法
在一般使用时,默认的是标准输出,即1.当我们需要特殊用途时,可以使用其他标号。例如,将某个程序的错误信息输出到log文件中:./program 2>log。这样标准输出还是在屏幕上,但是错误信息会输出到log文件中。 另外,也可以实现0,1,2之间的重定向。2>&1:将错误信息重定向到标准输出。 Linux下还有一个特殊的文件/dev/null,它就像一个无底洞,所有重定向到它的信息都会消失得无影无踪。这一点非常有用,当我们不需要回显程序的所有信息时,就可以将输出重定向到/dev/null。
似水的流年
2021/10/12
6K0
部署jar项目服务命令
查看 ps aux | grep 服务关键字 关闭进程,否则启动的时候报错:java.net.BindException: Address already in use kill -9 12148 重启服务 nohup java -jar 微服务.jar &
oktokeep
2024/10/09
1650
linux下的java部署
注:在运行 JAR 包之前,确保你的 JAR 文件是可执行的,并且包含了正确的类和依赖项。如果 JAR 包依赖于其他库或配置文件,确保它们也在正确的位置可用。
Kiba518
2024/03/06
3080
linux下的java部署
linux的nohup命令
在应用Unix/Linux时,我们一般想让某个程序在后台运行,于是我们将常会用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台: /usr/local/mysql/bin/mysqld_safe –user=mysql &。可是有很多程序并不想mysqld一样,这样我们就需要nohup命令,怎样使用nohup命令呢?这里讲解nohup命令的一些用法。
全栈程序员站长
2022/11/15
1.8K0
【Java专题_09】生产环境Jvm参数设置
最近公司有个独立的小模块是我这边负责开发上线,涉及到jvm参数调整方面,主要是根据服务器的配置4C16G配置了以下的参数,程序单纯只是接口查询redis以及信息上报,数据量大概是每天3000万!
夏之以寒
2024/03/04
2730
Linux命令之nohup
语法:nohup Command [ Arg ... ] [ & ] 描述:nohup 命令运行由 Command 参数和任何相关的 Arg 参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup 命令,添加 & ( 表示“and”的符号)到命令的尾部。
AsiaYe
2019/11/06
2.4K0
一、问题
本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/265
joshua317
2022/09/08
1.4K0
linux 后台运行进程:& , nohup
当我们在终端或控制台工作时,可能不希望由于运行一个作业而占住了屏幕,因为可能还有更重要的事情要做,比如阅读电子邮件。对于密集访问磁盘的进程,我们更希望它能够在每天的非负荷高峰时间段运行(例如凌晨)。为了使这些进程能够在后台运行,也就是说不在终端屏幕上运行,有几种选择方法可供使用。
DevOps在路上
2023/05/16
5.5K0
linux 后台运行进程:& , nohup
nohup command > out.file 2>&1 & 命令详解
hup是hang up的缩写,是挂断、挂起的意思,而顾名思义,nohup就是不挂断、不挂起的意思。在 Unix 的早期版本中,每个终端都会通过modem和系统通讯。当用户 logout 时,modem就会挂断(hang up)。并且,当modem和系统断开连接时,就会给系统发送hangup信号来通知其关闭该终端打开的所有进程。 而nohup命令的用途就是让该终端提交的命令忽略该hangup信号,从而能够在系统中继续执行。
saintyyu
2021/11/22
4.4K0
Linux编辑启动停止重启springboot jar包脚本
1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null" 2 表示stderr标准错误
陈灬大灬海
2019/03/21
4.9K0
Linux编辑启动停止重启springboot jar包脚本
Linux之nohup命令
原文链接:https://rumenz.com/rumenbiji/linux-nohup.html
入门笔记
2021/08/25
1.9K0
Frp命令大全
说明: 其中,>为输出重定向符号,>/dev/null 2>&1。这条命令其实分为两命令,一个是>/dev/null,另一个是2>&1。 1. >/dev/null 这条命令的作用是将标准输出1重定向到/dev/null中。/dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。 2. 2>&1 这条命令用到了重定向绑定,采用&可以将两个输出绑定在一起。这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。 linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1的作用就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。
Dabenshi
2023/05/26
2.1K0
shell脚本 >/dev/null 2>&1
1:> 代表重定向到哪里,例如:echo "123" > /home/123.txt
week
2018/08/27
4910
Linux 常用命令
这条命令的作用是将标准输出1重定向到/dev/null中。 /dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。
茶半香初
2021/11/26
4610
Linux 常用命令
nohup java -jar 启动java项目
nohup是一个常用的Unix命令,用于在忽略挂起信号(SIGHUP)的情况下运行指定的命令或进程。当用户注销或关闭终端时,通常后台运行的进程会收到SIGHUP信号而终止。使用nohup可以避免这种情况,使得进程即使在用户注销后也能继续运行。
王也518
2024/04/25
1.5K0
015 Linux 标准输入输出、重定向、管道和后台启动进程命令
IO 重定向是为了改变默认输入、输出的位置,如默认情况下标准输出(STDOUT),标准错误输出(STDERR)都是输出到显示终端,如对标准输出、标准错误输出改变其默认输出位置,可重定向输出到指定的文件中(实际工作中经常这么使用),要重定向就要配合一些语法符号。
落寞的鱼丶
2022/02/21
2.1K0
相关推荐
全网最全Linux 运行jar包的几种方式[通俗易懂]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验