这段 SQL 大佬能给点优化建议吗?统计一个月每天的数据还有更加简洁的写法吗?目前运行速度太慢了

2019-09-25 10:35:52 +08:00
 galaChe

SELECT su.real_name AS realName, sd.name AS deptName, su.user_name AS userName, ahh.* FROM ( SELECT ROUND( SUM( IF ( DAY ( ah.audit_time ) = '1', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '1', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '2', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '2', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '3', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '3', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '4', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '4', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '5', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '5', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '6', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '6', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '7', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '7', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '8', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '8', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '9', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '9', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '10', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '10', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '11', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '11', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '12', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '12', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '13', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '13', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '14', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '14', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '15', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '15', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '16', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '16', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '17', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '17', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '18', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '18', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '19', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '19', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '20', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '20', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '21', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '21', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '22', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '22', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '23', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '23', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '24', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '24', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '25', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '25', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '26', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '26', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '27', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '27', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '28', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '28', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '29', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '29', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '30', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '30', ROUND( SUM( IF ( DAY ( ah.audit_time ) = '31', ah.audit_num * ifnull( kpi.kpi_value, 1 ), NULL ) ), 2 ) AS '31', SUM( ( ROUND( ( ah.audit_num * ifnull( kpi.kpi_value, 1 ) ), 2 ) ) ) AS auditCount, ah.account_number AS empNumber, ah.emp_name AS employeeName, SUM( ROUND( ( ah.pass_num * ifnull( kpi.kpi_value, 1 ) ), 2 ) ) AS passCount, SUM( ROUND( ( ah.delete_Num * ifnull( kpi.kpi_value, 1 ) ), 2 ) ) AS delCount FROM audit_days AS ah LEFT JOIN skill_group_kpi AS kpi ON ( ah.group_name = kpi.group_name ) AND kpi.is_deleted = 0 WHERE 1 = 1 GROUP BY ah.account_number ORDER BY NULL ) ahh LEFT JOIN sys_user su ON su.qq LIKE concat( '%', ahh.empNumber, '%' ) AND ahh.empNumber != 0 LEFT JOIN sys_department sd ON sd.id = su.department_id

9075 次点击
所在节点    MySQL
74 条回复
changdy
2019-09-25 14:34:11 +08:00
1 对 audit_time 进行增加 Date() 虚拟列,并创建索引 (当然也可以创建分区), 不要使用 if 判断.分成 31 条 sql 分别执行
2 看看能不能推迟 left join 在 group 之后
------
楼上说 sql 复杂的...这其实已经算是比较简单的 sql 了...23333
Marstin
2019-09-25 14:35:47 +08:00
1、on 一般用于强关联的,你这样用 like 真的极少见,同时,ahh.empNumber != 0 建议加在查询出 ahh 结果的 where 条件中,会提升性能,增加可读性。
2、建议把你从 1 号到 31 号这个日期穷举判断的逻辑放到业务代码中,不要分这么多列,在代码中完成这一部分的任务会简单很多,不需要这样穷举,也会规避一些错误,效率也会有所提升。
3、分区,走时间索引,我在你的 sql 里没有看到时间查询条件
5、join 的使用,最左边应该是 user 表,然后按照有效 user 添加信息,你是直接取有效 kpi,再关联人员数据,对应人员的 kpi 数据缺失时,容错性不足
4、我严重怀疑表结构的设计有问题,建议发表结构出来,你这个逻辑其实很简单,就四张表联查,不要听上面说得那么恐怖。看具体数据量,一个月分区数据不超过百万条,三秒钟以内
Marstin
2019-09-25 14:37:26 +08:00
@changdy 是的,就四张表,无语。果然 v2 是前端的天下
jaylee4869
2019-09-25 14:42:24 +08:00
硬件优化,逃(
CamWang
2019-09-25 14:43:56 +08:00
替 v2 说句话,上面要求所有论坛发帖均实名制。
aguesuka
2019-09-25 14:45:57 +08:00
首先,like 可以改成 instr,最好改成 =;然后给 join 加上索引.然后把日期的查询和总数的查询拆成两个查询. 如果两个查询只有一个慢,重点改慢的查询,如果都慢,改外面的 join. 我猜是时间的问题 ,建议一个账号的数据不要放在一样而是拆成 31 行,然后在 sql 里合并表头或者放程序里处理
```

GROUP BY ah.account_number ,DAY(ah.audit_time)

```
这是非常简单的四表联查,几万条数据而已就算没有索引也不应该超过 1 秒
admintest001
2019-09-25 15:09:45 +08:00
每天跑个定时任务统计前一天的数据,跑完之后数据存起来,此外,跑之前去读一下当月前几天的数据存不存在,若不存在,则再往前多查一天,依此类推
npe
2019-09-25 15:21:51 +08:00
说点别的:
1.常用的报表统计,如果有条件可以上大数据分析。
2.不常用的报表,可以做成一个任务中心,采用离线、异步、阻塞的方式供用户使用。
lygmqkl
2019-09-25 15:24:14 +08:00
每每看到这种 SQL 我都想说,这压根不是 SQL 的锅,项目初期如果能设计的恰当,数据库自然能兼容到,压根不应该出现这样的场景。

其次 即使不得已遇到这样的情况,也应该做内存级计算,而不是 SQL 级计算,虽然 sql 有 group, like, on blabla, 但是这真不是这样用的。真的哪怕不用 go, python, 你用 php 的 cli 读出来处理都会快几十倍 甚至更多。

最后,这段 SQL 真的很 low,上面的各位已经很留情了。
janus77
2019-09-25 15:31:54 +08:00
咋了,我感觉楼上嘲讽的不多啊,好像不超过 5 条回复?这就忍不住了吗?
大部分都是说让你改方案,这难道算嘲讽吗?
<del>还是说都被我 b 了所以看不到……</del>
l00t
2019-09-25 16:15:53 +08:00
@Marstin #42 我觉得你没完全看懂。它是 left join,ahh 里的数据是不能丢的,每条都要,不可以加 where 过滤的。同理,交换 left join 两边的表也是不可行的,直接把业务逻辑都改了。
Marstin
2019-09-25 16:38:43 +08:00
@l00t 我是出于业务角度去考虑修改的,不好意思,可能确实有失妥当。
目前的查询逻辑是查所有 kpi 信息,那么就可能查出来没有对应人员的 kpi 数据,且不能查出哪些人员的 kpi 数据缺失,
我认为应该是针对所有有效人员去查 kpi。(该业务逻辑确实是我的问题,我还是坚持这种方式更好点,哈哈)

基于以上考虑
account_number AS empNumber 且 GROUP BY ah.account_number 那么 account_number 为空,应该是脏数据了,在 where 中去掉应该没问题,where 中可以加上 ahh.empNumber != 0

最费解的是他这里 account_number 应该就是账户信息了,怎么用 qq 去匹配,如果 qq 对应的就是 account_number 字段,那就直接=,如果有生成逻辑,那就要冗余一个字段吧,用 qq 的 like 匹配肯定会有问题啊。
123456 就会匹配到 1234567 12345678 的数据
l00t
2019-09-25 16:55:59 +08:00
@Marstin #52 我也觉得你对他的业务逻辑的质疑是很有道理的哈。qq 这里,我推测它是存在一个人有多个 qq 的情况,然后存一个字段里了,甚至这个字段可能还夹了别的不相干的文本内容,而 qq 的不同位数的问题它又没考虑进去。
akmissxt
2019-09-25 17:02:15 +08:00
lz 下次可以试试这个 [Carbon]( https://carbon.now.sh/)
Marstin
2019-09-25 17:04:51 +08:00
@l00t 问题很多,估计这老哥也是接盘,以后有得玩了
smallpython
2019-09-25 17:08:32 +08:00
楼主对于自己不喜欢的回复可以选择隐藏处理
pieors
2019-09-25 17:11:34 +08:00
@ccgoing10 MySQL 数据库
CruelMoon
2019-09-25 17:16:31 +08:00
看执行计划
realpg
2019-09-25 17:26:11 +08:00
写商业软件出身的吧

上次面了一个就是这套路 人家毫秒级的非得给你写个秒级的单 SQ 解决问题
galaChe
2019-09-25 17:27:31 +08:00
@lygmqkl 其实是知道 low 的。上面大佬说的方案我是知道的。考虑到这个功能的不确定,就不想在其他方面进行优化了(怕麻烦之后懒得清理这些冗余代码)。钻了牛角尖死磕 SQL 所以提了这个问题,希望看看大家是如何处理这类问题的,也算是学习一下见贤思齐。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/603956

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX