原创文章,转载请注明出处,谢谢合作。
https://blog.csdn.net/DarianMograine/article/details/108561662
日前,笔者在工作中被人问到这样一个问题:
Oracle数据库中,如何优化取每一行数据在当前表中同分组数据的汇总。
当前表取出数据量级1W+,原逻辑是先取每行后对表进行N次访问每次读取数据,虽然是主键回表汇总金额,由于数据条数过多,页面返回时间无法预期,希望能够通过SQL层面优化。
如下图所示,希望得到每日当月累计营业额:
以上是问题的背景。
实际上,类似场景有很多,如:
- 汇总部分门店每日当月累计营业额
- 按规则自动分组匹配(银行对账、核销、单据对比等)
这里给大家推荐一个思路,利用递归+分组开窗函数+OLAP分析函数一个SQL实现数据的抓取。
我们先来看下实现的代码:
- 创建演示表
CREATE TABLE test_cb
(key NUMBER
,iodate DATE
,amount NUMBER);
- 初始化模拟数据
INSERT INTO test_cb
(key, iodate, amount)
SELECT 1 key
,DATE '2020-05-01' iodate
,100 amount
FROM dual
UNION ALL
SELECT 1 key
,DATE '2020-05-01' iodate
,150 amount
FROM dual
UNION ALL
SELECT 1 key
,DATE '2020-05-03' iodate
,120 amount
FROM dual
UNION ALL
SELECT 2 key
,DATE '2020-05-01' iodate
,80 amount
FROM dual
UNION ALL
SELECT 2 key
,DATE '2020-05-02' iodate
,230 amount
FROM dual
UNION ALL
SELECT 2 key
,DATE '2020-05-04' iodate
,710 amount
FROM dual;
- 抓取数据的SQL代码
SELECT sub2.key
,sub2.iodate
,sub2.amount "当前行金额"
,dbms_aw.eval_number(substr(sys_connect_by_path(sub2.amount, '+'), 2)) "汇总到当前行金额"
,lag(dbms_aw.eval_number(substr(sys_connect_by_path(sub2.amount, '+')
,2))) over(PARTITION BY sub2.key ORDER BY sub2.iodate) "汇总到上一行金额"
FROM (SELECT sub1.key
,sub1.iodate
,SUM(sub1.amount) amount
,row_number() over(PARTITION BY sub1.key ORDER BY sub1.iodate) rn
FROM test_cb sub1
GROUP BY sub1.iodate
,sub1.key) sub2
CONNECT BY nocycle(PRIOR sub2.rn = sub2.rn - 1
AND PRIOR sub2.key = sub2.key)
START WITH rn = 1
接下来我们来看一下这段代码是怎么解决的这个问题:
-
我们利用分析函数row_number对数据以字段key为分组依据做分组牌序,得到每行数据展示的顺序
-
利用递归天然有序的特性,将之前行的金额形成形如“+200+100+150”这样的字符串并去掉第一个+,这样我们就得到一个字符串200+100+150,亦即代码中**substr(sys_connect_by_path(sub2.amount, ‘+’), 2)**部分。
-
利用OLAP函数dbms_aw.eval_number计算200+100+150的值,类似excel里=200+100+150的功能得到450。
-
利用分析函数lag在以key为基准,以iodate为排序字段的分组中获取上一行的步骤3金额,即得到上一行的汇总金额。
优化后的SQL中由于使用的是Oracle的内置函数,速度基本可以控制在2s以内。
是不是很神奇?感兴趣的筒子可以一试。
原创文章,转载请注明出处,谢谢合作。
https://blog.csdn.net/DarianMograine/article/details/108561662