oracle 存储过程解题示例一

问题概述

某一个报表数据来自一个复杂业务的查询,其中某类资产的数据需要按百分比展示。同时这类资产的百分比总和加起来应该是100% 。问题比如这个分类是2,2,2 百分比是0.33,0.33,0.33 最终给出的结果是需要修正的即走后一条数据应该修正为0.34;同理的 如果是 1,2,3,1 的百分比计算也需要修正最后一条数据。当时的业务场景下只能用存储过程

问题简化解析

首先加一些简单的测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- Create table
create table OOTEST
(
id NVARCHAR2(16),
types NVARCHAR2(16),
shares NUMBER
)
insert into ootest (ID, TYPES, SHARES)
values ('1', 'aa', 1);
insert into ootest (ID, TYPES, SHARES)
values ('2', 'aa', 1);
insert into ootest (ID, TYPES, SHARES)
values ('3', 'aa', 1);
insert into ootest (ID, TYPES, SHARES)
values ('4', 'bb', 1);
insert into ootest (ID, TYPES, SHARES)
values ('5', 'bb', 2);
select t.id,t.types,t.shares, rowid from ootest t ;

eg:
| ID | TYPES | SHARES |
| ————- |:————-:| ——-:|
| 1 | aa | 1 |
| 2 | aa | 1 |
| 3 | aa | 1 |
| 4 | bb | 1 |
| 5 | bb | 2 |

存储过程实现 (Version 1.0)

初步想法需要搞个临时表,先筛选数据进去,再遍历修改,之后再查询临时表返回数据即可。为了简单实现这里就不用临时表了,直接查询,然后循环处理数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
---
--
-- 当过程中含有输出参数时,调用时必须通过BEGIN END块,不能通过EXEC或CALL调用。如:
DECLARE
retcode NUMBER(7,2);
retnote varchar(4000);
BEGIN
queryPercentOne(retcode,retnote,'aa');
DBMS_OUTPUT.PUT_LINE(retcode);
DBMS_OUTPUT.PUT_LINE(retnote);
END;
---
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
create or replace procedure queryPercentOne(o_code out int, o_msg out varchar2,v_types in varchar2) is
v_count number := 0; -- 总记录数
v_total number := 0;
v_last number := 0;
v_last_per number :=0;
v_cid NVARCHAR2(16);
v_ctypes NVARCHAR2(16);
v_cshares number;
v_rownum number :=0;
v_cpercent number(3,2) :=0.0;
v_percent number(3,2) :=0.0;
v_crownum number :=1;
CURSOR cur IS SELECT t.* FROM ootest t where t.types= v_types;
begin
begin
select count(*) into v_count from ootest t where t.types= v_types;
select sum(t.shares) into v_total from ootest t where t.types= v_types;
EXCEPTION WHEN NO_DATA_FOUND THEN
v_count :=0;
v_total :=0;
end ;
open cur;
LOOP
fetch cur into v_cid,v_ctypes,v_cshares ;
exit when cur%notfound;
v_cpercent := v_cshares/v_total;
if v_crownum = v_count then
v_cshares := v_total;
v_cpercent := (1 - v_percent);
else
v_percent := v_percent + (v_cshares/v_total);
end if ;
dbms_output.put_line(v_crownum||' . '||v_cid||' . '||v_ctypes||' . '||v_cshares||' .'|| to_char(v_cpercent,'0.99'));
v_crownum := v_crownum+1;
end LOOP;
o_code :=-1;
o_msg :='nothing';
end queryPercentOne;

测试存储过程

1
2
3
4
5
6
begin
-- Call the procedure
querypercentone(o_code => :o_code,
o_msg => :o_msg,
v_types => :v_types);
end;

测试 aa

1 . 1 . aa . 1 . 0.33
2 . 2 . aa . 1 . 0.33
3 . 3 . aa . 3 . 0.34

测试 bb

1 . 4 . bb . 1 . 0.33
2 . 5 . bb . 3 . 0.67

复杂SQL也可以做到 (Version2.0)

使用下列oracle自带函数

ratio_to_report

主要完成对百分比的计算,语法为
ratio_to_report(exp) over()
也就是根据over窗口函数的作用区间,求出作用区间中的单个值在整个区间的总值的比重

decode

decode(条件,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值)

该函数的含义如下:
IF 条件=值1 THEN
    RETURN(翻译值1)
ELSIF 条件=值2 THEN
    RETURN(翻译值2)
    ……
ELSIF 条件=值n THEN
    RETURN(翻译值n)
ELSE
    RETURN(缺省值)
END IF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
select d.id,
d.shares,
d.ratio,
d.cnt,
d.total,
decode(d.cnt, 1, d.tail_rt + d.rate, d.rate) rate,
d.tail_rt
from (select c.id,
c.shares,
c.ratio, -- 份额
c.total,
round(c.ratio, 4) rate, --占比
1 - sum(round(c.ratio, 4)) over() tail_rt, --尾差
row_number() over(partition by c.types order by c.id) cnt
from (select t.id,
t.shares,
t.types,
b.total,
ratio_to_report((t.shares)) OVER(partition by t.types) ratio
from ootest t
left join((select t.types, sum(t.shares) total
from ootest t
group by t.types) b)
on t.types = b.types
where t.types = 'bb') c) d;
文章目录
  1. 1. 问题概述
  2. 2. 问题简化解析
  3. 3. 存储过程实现 (Version 1.0)
  4. 4. 测试存储过程
    1. 4.1. 测试 aa
    2. 4.2. 测试 bb
  5. 5. 复杂SQL也可以做到 (Version2.0)
    1. 5.1. ratio_to_report
    2. 5.2. decode