how to add a subtotal rows in oracle? - oracle

i want to add subtotal in between the rows
for example
this the rows and column
NOTE : these three column i am fetching from maintable
a b c
a1 100 100
a2 200 200
a3 300 300
a4 400 400
a5 500 500
a6 600 600
a7 700 700
a8 800 800
a9 900 900
a10 900 900
i want like this
a b c
a1 100 100
a2 200 200
a3 300 300
a4 400 400
subtotal(a1 to a4) 1000 1000
a5 500 500
a6 600 600
subtotal(a5 to a6) 1100 1100
a7 700 700
subtotal(a7) 700 700
a8 800 800
a9 900 900
a10 900 900
subtotal(a8 to a10) 2600 2600
Grandtotal 5400 5400
note : grandtotal = addition of subtotals

Try to combine the use of rollup and grouping functions in this way:
select
decode(grouping(a),1,decode(grouping(subtot_id), 1, 'total', 'subtotal'),a) as a,
sum(b) as b, sum(c) as c
from (
select trunc((rownum-1)/3) as subtot_id, a, b, c
from your_table
order by a
)
group by rollup(subtot_id, a)
I made a sample here

I'm not very experienced in Oracle but I came out with this solution. Required types and function needs to be defined for each table you want to work with. It should be used on small tables because of the time of executing:
CREATE OR REPLACE TYPE row_abc
AS OBJECT (
a varchar2(10),
b integer,
c integer
);
CREATE OR REPLACE TYPE table_abc
AS TABLE OF row_abc;
CREATE OR REPLACE FUNCTION function_name(
report_freq INTEGER := 3
) RETURN table_abc AS
ret table_abc;
counter INTEGER := 0;
CURSOR abc_cursor IS
SELECT row_abc(a, b, c) FROM main_table;
BEGIN
ret := table_abc();
OPEN abc_cursor;
LOOP
counter := counter + 1;
IF counter!=1 AND mod(counter, report_freq)=1 THEN
ret.extend;
SELECT row_abc(
'subtotal',
(SELECT SUM(b) FROM
(SELECT b, rownum rn FROM main_table)
WHERE rn>=counter-report_freq AND rn<counter),
(SELECT SUM(c) FROM
(SELECT c, rownum rn FROM main_table)
WHERE rn>=counter-report_freq AND rn<counter)
) INTO ret(ret.count) FROM dual;
END IF;
ret.extend;
FETCH abc_cursor INTO ret(ret.count);
EXIT WHEN abc_cursor%NOTFOUND;
END LOOP;
CLOSE abc_cursor;
counter := counter - 1;
IF mod(counter, report_freq) != 0 THEN
SELECT row_abc(
'subtotal',
(SELECT SUM(b) FROM
(SELECT b, rownum rn FROM main_table)
WHERE rn>counter-mod(counter, report_freq) AND rn <=counter),
(SELECT SUM(c) FROM
(SELECT c, rownum rn FROM main_table)
WHERE rn>counter-mod(counter, report_freq)AND rn <=counter)
) INTO ret(ret.count) FROM dual;
ret.extend;
END IF;
SELECT row_abc(
'grandtotal',
(SELECT SUM(b) FROM TABLE(ret) WHERE a='subtotal'),
(SELECT SUM(c) FROM TABLE(ret) WHERE a='subtotal')
) INTO ret(ret.count) FROM dual;
RETURN ret;
END function_name;
And you execute it in sql with following line to get your requested output
SELECT * FROM table(function_name(report_freq));
where report_freq is value of frequency of subtotal reports.

Related

How can I extract columns name from row value of another table(oracle sql)?

I have 2 tables:
table1
no
a
b
c
x1
2
3
4
x2
10
11
12
x3
20
21
22
table2
from_val
in_out
cf_pv
term
a
out
cf
b
b
out
pv
b
c
in
cf
e
Define sum_out is sum of a, b, c in table1 with condition in_out='out' in table2 and sum_cf is sum of a, b, c in table1 with condition cf_pv='cf' in table2.
Shortly, values of from_val in table2 are columns name i.e. a, b, c in table1.
How can I extract and calculate sum_out or sum_cf of every no in Oracle?
sum_out of x1 = 2 + 3
sum_out of x2 = 10 + 11
sum_out of x3 = 20 + 21
sum_cf of x1 = 2 + 4
sum_cf of x2 = 10 + 12
sum_cf of x3 = 20 + 22
Thanks!
'''''''''''''''''''''''''''''''''''''''''''''
in additional,
i want to calculate
sum_out and cf of x1= 2 (=a)
sum_out and cf of x2= 10 (=b)
sum_out and cf of x3= 20 (=c)
Sample data
WITH
tbl_1 AS
(
Select 'x1' "COL_NO", 2 "A", 3 "B", 4 "C" From Dual Union All
Select 'x2' "COL_NO", 10 "A", 11 "B", 12 "C" From Dual Union All
Select 'x3' "COL_NO", 20 "A", 21 "B", 22 "C" From Dual
),
tbl_2 AS
(
Select 'A' "FROM_VAL", 'out' "IN_OUT", 'cf' "CF_PV", 'begin' "TERM" From Dual Union All
Select 'B' "FROM_VAL", 'out' "IN_OUT", 'pv' "CF_PV", 'begin' "TERM" From Dual Union All
Select 'C' "FROM_VAL", 'in' "IN_OUT", 'cf' "CF_PV", 'end' "TERM" From Dual
),
Create CTE (formulas) that generates formulas for IN_OUT = 'out' and For CF_PV = 'cf'
formulas AS
(
Select
CASE WHEN IN_OUT = 'out' THEN IN_OUT END "IN_OUT",
LISTAGG(FROM_VAL, ' + ') WITHIN GROUP (ORDER BY FROM_VAL) OVER(PARTITION BY IN_OUT) "IN_OUT_FORMULA",
CASE WHEN CF_PV = 'cf' THEN CF_PV END "CF_PV",
LISTAGG(FROM_VAL, ' + ') WITHIN GROUP (ORDER BY FROM_VAL) OVER(PARTITION BY CF_PV) "CF_PV_FORMULA"
From
tbl_2
),
IN_OUT
IN_OUT_FORMULA
CF_PV
CF_PV_FORMULA
C
cf
A + C
out
A + B
cf
A + C
out
A + B
B
Another CTE (grid) to connect COL_NO to formulas
grid AS
(
Select
t1.COL_NO,
CASE WHEN f1.IN_OUT = 'out' THEN f1.IN_OUT END "IN_OUT", CASE WHEN f1.IN_OUT = 'out' THEN f1.IN_OUT_FORMULA END "IN_OUT_FORMULA",
CASE WHEN f1.CF_PV = 'cf' THEN f1.CF_PV END "CF_PV", CASE WHEN f1.CF_PV = 'cf' THEN f1.CF_PV_FORMULA END "CF_PV_FORMULA"
From
tbl_1 t1
Left Join
formulas f1 ON(f1.IN_OUT Is Not Null AND f1.CF_PV Is Not Null)
)
COL_NO
IN_OUT
IN_OUT_FORMULA
CF_PV
CF_PV_FORMULA
x1
out
A + B
cf
A + C
x2
out
A + B
cf
A + C
x3
out
A + B
cf
A + C
Main SQL to get the final result
SELECT
g.COL_NO,
g.IN_OUT,
g.IN_OUT_FORMULA,
CASE WHEN g.IN_OUT = 'out' And INSTR(IN_OUT_FORMULA, 'A') > 0 THEN A ELSE 0 END +
CASE WHEN g.IN_OUT = 'out' And INSTR(IN_OUT_FORMULA, 'B') > 0 THEN B ELSE 0 END +
CASE WHEN g.IN_OUT = 'out' And INSTR(IN_OUT_FORMULA, 'C') > 0 THEN C ELSE 0 END "CALC_OUT",
--
g.CF_PV,
g.CF_PV_FORMULA,
CASE WHEN g.CF_PV = 'cf' And INSTR(CF_PV_FORMULA, 'A') > 0 THEN A ELSE 0 END +
CASE WHEN g.CF_PV = 'cf' And INSTR(CF_PV_FORMULA, 'B') > 0 THEN B ELSE 0 END +
CASE WHEN g.CF_PV = 'cf' And INSTR(CF_PV_FORMULA, 'C') > 0 THEN C ELSE 0 END "CALC_CF"
FROM
grid g
INNER JOIN
tbl_1 t1 ON(g.COL_NO = t1.COL_NO)
R e s u l t :
COL_NO
IN_OUT
IN_OUT_FORMULA
CALC_OUT
CF_PV
CF_PV_FORMULA
CALC_CF
x1
out
A + B
5
cf
A + C
6
x2
out
A + B
21
cf
A + C
22
x3
out
A + B
41
cf
A + C
42

How to get the data from oracle on the following demand?

The table like this.
bh sl productdate
a1 100 2022-1-1
a1 220 2022-1-2
a1 220 2022-1-3
a2 200 2022-1-1
a2 350 2022-1-2
a2 350 2022-1-3
The result like this.
bh sl_q(sl_before) sl_h(sl_after) sl_b(changeValue) productdate
a1 100 220 120 2022-1-2
a2 200 350 150 2022-1-2
Rules:the same field bh, when the field sl change,then get the record.
We can use a ROW_NUMBER trick here:
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY bh ORDER BY productdate) rn1,
ROW_NUMBER() OVER (PARTITION BY bh ORDER BY productdate DESC) rn2
FROM yourTable t
)
SELECT bh, MAX(CASE WHEN rn1 = 1 THEN sl END) AS sl_q,
MAX(CASE WHEN rn2 = 1 THEN sl END) AS sl_h,
MAX(CASE WHEN rn2 = 1 THEN sl END) -
MAX(CASE WHEN rn1 = 1 THEN sl END) AS sl_b
FROM cte
GROUP BY bh;

Applying Oracle Pivot to get horizentle data

(I am new to Oracle pivot.)
The result of my code is as follow.
ROWNUM C0 M SS
------- --- ---------- ---------- ----------
1 a a__ 3.5
2 a abd 1.5
3 a abe 3.5
4 a ace 5.5
5 b a__ 35
6 b abd 15
7 b abe 35
8 b ace 55
Items in C0 shows vertically as expected.
Now,The purpose is to show the values in C0 horizently.
How to modify my code to make result as follow?
ROWNUM M a b
---------- --------- ---------- ----------
1 a__ 3.5 35
2 abd 1.5 15
3 abe 3.5 35
4 ace 5.5 55
My code is as below;
CREATE TABLE T4 (
C0 VARCHAR2(10),
C1 VARCHAR2(10),
C2 NUMBER
);
INSERT INTO T4 VALUES ('a','abd',1);
INSERT INTO T4 VALUES ('a','abd',2);
INSERT INTO T4 VALUES ('a','abe',3);
INSERT INTO T4 VALUES ('a','abe',4);
INSERT INTO T4 VALUES ('a','ace',5);
INSERT INTO T4 VALUES ('a','ace',6);
INSERT INTO T4 VALUES ('b','abd',10);
INSERT INTO T4 VALUES ('b','abd',20);
INSERT INTO T4 VALUES ('b','abe',30);
INSERT INTO T4 VALUES ('b','abe',40);
INSERT INTO T4 VALUES ('b','ace',50);
INSERT INTO T4 VALUES ('b','ace',60);
SELECT ROWNUM,rr.C0,rr.M, rr.ss
FROM
(
SELECT C0,C1 M, AVG(C2) ss FROM T4 GROUP BY C0, C1
UNION
SELECT C0,SUBSTR(C1,1,1)||'__' ,AVG(C2) ss FROM T4 GROUP BY C0,SUBSTR(C1,1,1) ) rr
ORDER BY rr.C0,rr.M ASC;
You can achieve the result in two ways.
Using PIVOT and using a traditional technique (CASE WHEN).
PIVOT is the recommended technique to use. But I have written both just for your reference.
-- Using PIVOT --
WITH DATAA AS (
SELECT ROWNUM,rr.C0,rr.M, rr.ss
FROM
(
SELECT C0,C1 M, AVG(C2) ss FROM T4 GROUP BY C0, C1
UNION
SELECT C0,SUBSTR(C1,1,1)||'__' ,AVG(C2) ss FROM T4 GROUP BY C0,SUBSTR(C1,1,1) ) rr
)
-- USING PIVOT
SELECT
ROWNUM,
TAB.*
FROM
(
SELECT
*
FROM
(
SELECT
M,
C0,
SS
FROM
DATAA
) PIVOT (
SUM ( SS )
FOR ( C0 )
IN ( 'a' AS A, 'b' AS B )
)
ORDER BY
1
) TAB;
-- Using Traditional technique --
WITH DATAA AS (
SELECT ROWNUM,rr.C0,rr.M, rr.ss
FROM
(
SELECT C0,C1 M, AVG(C2) ss FROM T4 GROUP BY C0, C1
UNION
SELECT C0,SUBSTR(C1,1,1)||'__' ,AVG(C2) ss FROM T4 GROUP BY C0,SUBSTR(C1,1,1) ) rr
)
-- TRADITIONAL WAY
SELECT
ROWNUM,
M,
A,
B
FROM
(
SELECT
M,
MAX(CASE
WHEN C0 = 'a' THEN SS
END) AS A,
MAX(CASE
WHEN C0 = 'b' THEN SS
END) AS B
FROM
DATAA
GROUP BY
M
ORDER BY
M
);
-- Output --
db<>fiddle demo
Cheers!!

Progressive LOOP Statement ORACLE

How to create a function that returns a float(ChargeTotal)?
ChargeTotal is based on a progressive table using number of batches.
num of batches | charge
----------------------------
1-10 | 0
11-20 | 50
21-30 | 60
31-40 | 70
40+ | 80
If number of batches is 25 then
num of batches | charge
----------------------------
1-10 | 0
11-20 | 50*10
21-30 | 60*5
----------------------------
total | 800 <number I need to be returned(ChargeTotal)
So far I have come up with the following, but I'm unsure how to get the total for each loop, or if it is even possible to do more than one FOR statements:
CREATE OR REPLACE FUNCTION ChargeTotal
RETURN FLOAT IS
total FLOAT;
BEGIN
FOR a in 1 .. 10 LOOP
FOR a in 11 .. 20 LOOP
FOR a in 21 .. 30 LOOP
FOR a in 40 .. 1000 LOOP
RETURN Total;
END ChargeTotal;
Ok so take into consideration that right now I have no DB available to test this (there might be some syntax errors etc).
But I am thinking something along this lines of code...
function ChargeTotal(xin number) return number is
cursor mycursor is
select lowLimit,highLimit,charge
from progressive_table order by lowLimit;
sum number;
segment number;
x number;
begin
sum:=0;
x :=xin;
for i in mycursor loop
segment := (i.highLimit-i.lowLimit)+1;
x := greatest ( x - segment,x);
sum := sum + segment*i.charge;
if (x<segment) then
return sum;
end if;
end loop;
return sum;
end;
I think you can do the calculation via single sql without complex function
the logic is:
you have weights for each "band"
calculate the "band" each row
count(*) over to calculate number of rows in each "band"
join your weight table to get sub.total for each band
use rollup to get grand total
sql
select r.num_of_batches
,sum(r.subtotal_charge)
from (
with weights as
(select 1 as num_of_batches, 0 as charge from dual
union all
select 2 as num_of_batches, 50 as charge from dual
union all
select 3 as num_of_batches, 60 as charge from dual
union all
select 4 as num_of_batches, 70 as charge from dual
union all
select 5 as num_of_batches, 80 as charge from dual
)
select distinct n.num_of_batches
, w.charge
, count(*) over (partition by n.num_of_batches) as cnt
, count(*) over (partition by n.num_of_batches) * charge as subtotal_charge
from (
select num, case when floor(num / 10) > 4 then 5 else floor(num / 10)+1 end as num_of_batches
from tst_brances b
) n
inner join weights w on n.num_of_batches = w.num_of_batches
order by num_of_batches
) r
group by ROLLUP(r.num_of_batches)
populate test data
create table tst_branches(num int);
declare
i int;
begin
delete from tst_brances;
for i in 1..10 loop
insert into tst_brances(num) values (i);
end loop;
for i in 11..20 loop
insert into tst_brances(num) values (i);
end loop;
for i in 21..25 loop
insert into tst_brances(num) values (i);
end loop;
for i in 31..32 loop
insert into tst_brances(num) values (i);
end loop;
for i in 41..43 loop
insert into tst_brances(num) values (i);
end loop;
for i in 51..55 loop
insert into tst_brances(num) values (i);
end loop;
commit;
end;
results
1 1 0
2 2 500
3 3 360
4 4 140
5 5 640
6 1640

finding missing numbers from sequence after getting sequenuce from a string?

I have a millions of string record like this one with 310 types of them that have different format to get sequence,year,month and day from..
the script will get the sequence,year,month and day... now I want a Pl/Sql that will get the max and min value number of the sequence and find the missing number where is year and month are for example 14 - 06 how ??
You don't want to be looking at dual at all here; certainly not attempting to insert. You need to track the highest and lowest values you've seen as you iterate through the loop. based on some of the elements of ename representing dates I'm pretty sure you want all your matches to be 0-9, not 1-9. You're also referring to the cursor name as you access its fields, instead of the record variable name:
FOR List_ENAME_rec IN List_ENAME_cur loop
if REGEXP_LIKE(List_ENAME_rec.ENAME,'emp[-][0-9]{4}[_][0-9]{2}[_][0-9]{2}[_][0-9]{2}[_][0-9]{4}[_][G][1]') then
V_seq := substr(List_ENAME_rec.ename,5,4);
V_Year := substr(List_ENAME_rec.ename,10,2);
V_Month := substr(List_ENAME_rec.ename,13,2);
V_day := substr(List_ENAME_rec.ename,16,2);
if min_seq is null or V_seq < min_seq then
min_seq := v_seq;
end if;
if max_seq is null or V_seq > max_seq then
max_seq := v_seq;
end if;
end if;
end loop;
With values in the table of emp-1111_14_01_01_1111_G1 and emp-1115_14_02_02_1111_G1, that reports max_seq 1115 min_seq 1111.
If you really wanted to involve dual you could do this inside the loop, instead of the if/then/assign pattern, but it's not necessary:
select least(min_seq, v_seq), greatest(max_seq, v_seq)
into min_seq, max_seq
from dual;
I have no idea what the procedure is going to do; there seems to be no relationship between whatever you've got in test1 and the values you're finding.
You don't need any PL/SQL for this though. You can get the min/max values from a simple query:
select min(to_number(substr(ename, 5, 4))) as min_seq,
max(to_number(substr(ename, 5, 4))) as max_seq
from table1
where status = 2
and regexp_like(ename,
'emp[-][0-9]{4}[_][0-9]{2}[_][0-9]{2}[_][0-9]{2}[_][0-9]{4}[_][G][1]')
MIN_SEQ MAX_SEQ
---------- ----------
1111 1115
And you can use those to generate a list of all values in that range:
with t as (
select min(to_number(substr(ename, 5, 4))) as min_seq,
max(to_number(substr(ename, 5, 4))) as max_seq
from table1
where status = 2
and regexp_like(ename,
'emp[-][0-9]{4}[_][0-9]{2}[_][0-9]{2}[_][0-9]{2}[_][0-9]{4}[_][G][1]')
)
select min_seq + level - 1 as seq
from t
connect by level <= (max_seq - min_seq) + 1;
SEQ
----------
1111
1112
1113
1114
1115
And a slightly different common table expression to see which of those don't exist in your table, which I think is what you're after:
with t as (
select to_number(substr(ename, 5, 4)) as seq
from table1
where status = 2
and regexp_like(ename,
'emp[-][0-9]{4}[_][0-9]{2}[_][0-9]{2}[_][0-9]{2}[_][0-9]{4}[_][G][1]')
),
u as (
select min(seq) as min_seq,
max(seq) as max_seq
from t
),
v as (
select min_seq + level - 1 as seq
from u
connect by level <= (max_seq - min_seq) + 1
)
select v.seq as missing_seq
from v
left join t on t.seq = v.seq
where t.seq is null
order by v.seq;
MISSING_SEQ
-----------
1112
1113
1114
or if you prefer:
...
select v.seq as missing_seq
from v
where not exists (select 1 from t where t.seq = v.seq)
order by v.seq;
SQL Fiddle.
Based on comments I think you want the missing values for the sequence for each combination of the other elements of the ID (YY_MM_DD). This will give you that breakdown:
with t as (
select to_number(substr(ename, 5, 4)) as seq,
substr(ename, 10, 2) as yy,
substr(ename, 13, 2) as mm,
substr(ename, 16, 2) as dd
from table1
where status = 2
and regexp_like(ename,
'emp[-][0-9]{4}[_][0-9]{2}[_][0-9]{2}[_][0-9]{2}[_][0-9]{4}[_][G][1]')
),
r (yy, mm, dd, seq, max_seq) as (
select yy, mm, dd, min(seq), max(seq)
from t
group by yy, mm, dd
union all
select yy, mm, dd, seq + 1, max_seq
from r
where seq + 1 <= max_seq
)
select yy, mm, dd, seq as missing_seq
from r
where not exists (
select 1 from t
where t.yy = r.yy
and t.mm = r.mm
and t.dd = r.dd
and t.seq = r.seq
)
order by yy, mm, dd, seq;
With output like:
YY MM DD MISSING_SEQ
---- ---- ---- -------------
14 01 01 1112
14 01 01 1113
14 01 01 1114
14 02 02 1118
14 02 02 1120
14 02 03 1127
14 02 03 1128
SQL Fiddle.
If you want to look for a particular date you cold filter that (either in t, or the first branch in r), but you could also change the regex pattern to include the fixed values; so to look for 14 06 the pattern would be 'emp[-][0-9]{4}_14_06_[0-9]{2}[_][0-9]{4}[_][G][1]', for example. That's harder to generalise though, so a filter (where t.yy = '14' and t.mm = '06' might be more flexible.
If you insist in having this in a procedure, you can make the date elements optional and modify the regex pattern:
create or replace procedure show_missing_seqs(yy in varchar2 default '[0-9]{2}',
mm in varchar2 default '[0-9]{2}', dd in varchar2 default '[0-9]{2}') as
pattern varchar2(80);
cursor cur (pattern varchar2) is
with t as (
select to_number(substr(ename, 5, 4)) as seq,
substr(ename, 10, 2) as yy,
substr(ename, 13, 2) as mm,
substr(ename, 16, 2) as dd
from table1
where status = 2
and regexp_like(ename, pattern)
),
r (yy, mm, dd, seq, max_seq) as (
select yy, mm, dd, min(seq), max(seq)
from t
group by yy, mm, dd
union all
select yy, mm, dd, seq + 1, max_seq
from r
where seq + 1 <= max_seq
)
select yy, mm, dd, seq as missing_seq
from r
where not exists (
select 1 from t
where t.yy = r.yy
and t.mm = r.mm
and t.dd = r.dd
and t.seq = r.seq
)
order by yy, mm, dd, seq;
begin
pattern := 'emp[-][0-9]{4}[_]'
|| yy || '[_]' || mm || '[_]' || dd
|| '[_][0-9]{4}[_][G][1]';
for rec in cur(pattern) loop
dbms_output.put_line(to_char(rec.missing_seq, 'FM0000'));
end loop;
end show_missing_seqs;
/
I don't know why you insist it has to be done like this or why you want to use dbms_output as you're relying on the client/caller displaying that; what will your job do with the output? You could make this return a sys_refcursor which would be more flexible. but anyway, you can call it like this from SQL*Plus/SQL Developer:
set serveroutput on
exec show_missing_seqs(yy => '14', mm => '01');
anonymous block completed
1112
1113
1114

Resources