Skip to Main Content
  • Questions
  • Subtotal, Grand Total, ordering and breaking on different fields

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, PHILLIP.

Asked: March 28, 2019 - 5:41 am UTC

Last updated: April 08, 2019 - 11:04 am UTC

Version: 11g

Viewed 1000+ times

You Asked

Hi Tom

I was looking through the already asked questions about Subtotals and Grand totals , but couldn't really find anything that suits my problem.

I have a table with the following fields

DATE         REGION     REG_NUM     AMOUNT
01-MAR-19    REG01      A01           1.00 
02-MAR-19    REG01      B01          10.00 
03-MAR-19    REG03      Z01         500.00 
03-MAR-19    REG04      C01        9500.00
04-MAR-19    REG01      A01          20.00 
10-MAR-19    REG03      E01          35.50
11-MAR-19    REG03      E01          20.25
19-MAR-19    REG04      C01        1000.00 
17-MAR-19    REG01      B01          15.00


My output has to order by REGION, but show subtotal on REG_NUM level:

DATE         REGION     REG_NUM     AMOUNT
01-MAR-19    REG01      A01           1.00
04-MAR-19    REG01      A01          20.00 
TOTAL                                21.00 
02-MAR-19    REG01      B01          10.00 
17-MAR-19    REG01      B01          15.00
TOTAL                                25.00 

03-MAR-19    REG03      Z01         500.00 
TOTAL                               500.00
10-MAR-19    REG03      E01          35.50
11-MAR-19    REG03      E01          20.25
TOTAL                                55.25

03-MAR-19    REG04      C01        9500.00
19-MAR-19    REG04      C01        1000.00 
                                  10500.00


This is what I've tried. I am not sure how to add REGION to the ORDER BY as it messes up the result:

SELECT DATE, REGION, REG_NUM, SUM(AMOUNT)
FROM MY_TRANSACTIONS
GROUP BY GROUPING SETS ( (DATE, REGION, REG_NUM, AMOUNT) , REG_NUM , () )
ORDER BY REG_NUM NULLS LAST; 


Thank you

and Chris said...

Here's one approach, assuming that each region can appear in only one reg_num:

Create a custom sort by:

- Using the grouping_id function to determine which subtotal level you're at
- If this line-level total, return the current region
- If it's the grand total, return null
- If it's the reg_num subtotal, then get the value for the last region by:

* Using last_value
* Partitioned by reg_num
* Ordered by the region and date, both nulls last

Which gives a case statement like:

 case 
   when grouping_id ( region, reg_num ) = 2 then
     last_value ( region ) ignore nulls over (
       partition by reg_num
       order by region nulls last, dt nulls last
     ) 
   when grouping_id ( region, reg_num ) = 3 then
     null
   else
     region
 end srt


I've also added a row_number() with the same window clause to aid sorting.

Then sort by the custom function, region, and other columns as needed:

create table t (
  dt date, region varchar2(10), reg_num varchar2(10), amt number
);

insert into t values ( date'2019-03-01', 'REG01', 'A01',    1.00 );
insert into t values ( date'2019-03-02', 'REG01', 'B01',   10.00 );
insert into t values ( date'2019-03-03', 'REG03', 'Z01',  500.00 );
insert into t values ( date'2019-03-03', 'REG04', 'C01', 9500.00 );
insert into t values ( date'2019-03-04', 'REG01', 'A01',   20.00 );
insert into t values ( date'2019-03-10', 'REG03', 'E01',   35.50 );
insert into t values ( date'2019-03-11', 'REG03', 'E01',   20.25 );
insert into t values ( date'2019-03-19', 'REG04', 'C01', 1000.00 );
insert into t values ( date'2019-03-17', 'REG01', 'B01',   15.00 );

select dt, region, reg_num, sum(amt), 
       grouping_id ( region, reg_num ) gid,
       row_number() over (
         partition by reg_num
         order by region nulls last, dt nulls last
       )  rn,
       case 
         when grouping_id ( region, reg_num ) = 2 then
           last_value ( region ) ignore nulls over (
             partition by reg_num
             order by region nulls last, dt nulls last
           ) 
         when grouping_id ( region, reg_num ) = 3 then
           null
         else
           region
       end srt
from t
group by grouping sets ( (dt, region, reg_num, amt) , reg_num , () )
order by srt nulls last, reg_num, rn;

DT            REGION   REG_NUM   SUM(AMT)   GID   RN   SRT      
01-MAR-2019   REG01    A01                1     0    1 REG01    
04-MAR-2019   REG01    A01               20     0    2 REG01    
<null>        <null>   A01               21     2    3 REG01    
02-MAR-2019   REG01    B01               10     0    1 REG01    
17-MAR-2019   REG01    B01               15     0    2 REG01    
<null>        <null>   B01               25     2    3 REG01    
10-MAR-2019   REG03    E01             35.5     0    1 REG03    
11-MAR-2019   REG03    E01            20.25     0    2 REG03    
<null>        <null>   E01            55.75     2    3 REG03    
03-MAR-2019   REG03    Z01              500     0    1 REG03    
<null>        <null>   Z01              500     2    2 REG03    
03-MAR-2019   REG04    C01             9500     0    1 REG04    
19-MAR-2019   REG04    C01             1000     0    2 REG04    
<null>        <null>   C01            10500     2    3 REG04    
<null>        <null>   <null>      11101.75     3    1 <null>

Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

roll another one

Racer I., April 01, 2019 - 2:02 pm UTC

Hi,

Maybe this would work too :

Simple :
SELECT dt, region, reg_num, SUM(AMT) Amt
FROM t
GROUP BY reg_num, ROLLUP ( REGION, dt)
HAVING GROUPING(region) = 0 
ORDER BY REGION, REG_NUM, DT NULLS LAST


Nicer (should be done by the reporting tool though) :
SELECT NVL(TO_CHAR(Dt), 'TOTAL') Dt1,
  CASE WHEN dt IS NULL THEN NULL ELSE REGION END REGION1,
  CASE WHEN dt IS NULL THEN NULL ELSE REG_NUM END REG_NUM1, SUM(AMT) Amt
FROM t
GROUP BY reg_num, ROLLUP ( REGION, dt)
HAVING GROUPING(region) = 0 
ORDER BY REGION, REG_NUM, DT NULLS LAST 


regards,
Chris Saxon
April 02, 2019 - 10:19 am UTC

Nice... but it's missing the grand total!

with grouping sets and grouping_id columns

Rajeshwaran, Jeyabal, April 01, 2019 - 2:36 pm UTC

demo@PDB1> select * from t;

DT                      REGION     REG_N     AMOUNT
----------------------- ---------- ----- ----------
01-MAR-2019 12:00:00 am REG01      A01            1
02-MAR-2019 12:00:00 am REG01      B01           10
03-MAR-2019 12:00:00 am REG03      Z01          500
03-MAR-2019 12:00:00 am REG04      C01         9500
04-MAR-2019 12:00:00 am REG01      A01           20
10-MAR-2019 12:00:00 am REG03      E01         35.5
11-MAR-2019 12:00:00 am REG03      E01        20.25
19-MAR-2019 12:00:00 am REG04      C01         1000
17-MAR-2019 12:00:00 am REG01      B01           15

9 rows selected.

demo@PDB1> select nvl(to_char(dt),'Total') dt,region,reg_num,sum(amount),
  2    grouping_id( dt,reg_num ) grp
  3  from t
  4  group by grouping sets( (dt,region,reg_num),(reg_num) )
  5  order by reg_num,grp ;

DT                               REGION     REG_N SUM(AMOUNT)        GRP
-------------------------------- ---------- ----- ----------- ----------
01-MAR-2019 12:00:00 am          REG01      A01             1          0
04-MAR-2019 12:00:00 am          REG01      A01            20          0
Total                                       A01            21          2
02-MAR-2019 12:00:00 am          REG01      B01            10          0
17-MAR-2019 12:00:00 am          REG01      B01            15          0
Total                                       B01            25          2
03-MAR-2019 12:00:00 am          REG04      C01          9500          0
19-MAR-2019 12:00:00 am          REG04      C01          1000          0
Total                                       C01         10500          2
10-MAR-2019 12:00:00 am          REG03      E01          35.5          0
11-MAR-2019 12:00:00 am          REG03      E01         20.25          0
Total                                       E01         55.75          2
03-MAR-2019 12:00:00 am          REG03      Z01           500          0
Total                                       Z01           500          2

14 rows selected.

demo@PDB1>

Chris Saxon
April 02, 2019 - 10:27 am UTC

REG03 rows should appear above REG04...

A reader, April 01, 2019 - 5:05 pm UTC


Hi Team , i have a table having column deptno and job data like below
10 PRESIDENT
10 MANAGER
10 CLERK

20 MANAGER
20 ANALYST
20 CLERK

30 SALESMAN
30 MANAGER
30 CLERK

40 salesman
40 clerk
40 clerk

50 clerk
50 analyst

rules :
1) if president exists print deptno , president
2)else if ,if manager exists print deptno, manager
3) else if , if analyst exists ptint deptno , analyst
4)else if , if salesman exists print deptno , salesman
5)else if , if clerk exists print deptno, clerk


o/p should be like below

10 PRESIDENT
20 MANAGER
30 MANAGER
40 salesman
50 analyst

Please help me how to get this with sql query .

el presidente

Racer I., April 02, 2019 - 6:17 am UTC

Hi,

@Reader :
- don't hijack threads
- provide usual info (create table, insert)
- normalize your schema (also avoids upper/lower case problems, spelling errors?)
- switch to a less hierarchical company ;)

with
prec as (
  select 1 Lvl, 'PRESIDENT' JobD from dual union all
  select 2 Lvl, 'MANAGER' JobD from dual union all
  select 3 Lvl, 'ANALYST' JobD from dual union all
  select 4 Lvl, 'SALESMAN' JobD from dual union all
  select 5 Lvl, 'CLERK' JobD from dual),
tdata as (
  select 10 DNo, 'PRESIDENT' JobD from dual union all
  select 10 DNo, 'MANAGER' JobD from dual union all
  select 10 DNo, 'CLERK' JobD from dual union all
  select 20 DNo, 'MANAGER' JobD from dual union all
  select 20 DNo, 'ANALYST' JobD from dual union all
  select 20 DNo, 'CLERK' JobD from dual union all
  select 30 DNo, 'SALESMAN' JobD from dual union all
  select 30 DNo, 'MANAGER' JobD from dual union all
  select 30 DNo, 'CLERK' JobD from dual union all
  select 40 DNo, 'salesman' JobD from dual union all
  select 40 DNo, 'clerk' JobD from dual union all
  select 40 DNo, 'clerk' JobD from dual union all
  select 50 DNo, 'clerk' JobD from dual union all
  select 50 DNo, 'analyst' JobD from dual)
select t.DNo, MAX(t.JobD) KEEP (DENSE_RANK FIRST ORDER By p.Lvl) TJobD 
from tdata t
  join prec p ON (UPPER(t.JobD) = UPPER(p.JobD))
GROUP BY t.DNo
ORDER BY t.DNo


regards,
Chris Saxon
April 02, 2019 - 10:27 am UTC

Indeed.

A reader, April 02, 2019 - 12:43 pm UTC

Hi Team ,

Thank your support .
Yesterday i was not able to open a new thread to ask the above scenario , so i used the related thread . Sorry for that .It won't repeat again .

Once again many thanks for your quick response.

a totaled statement

Racer I., April 08, 2019 - 7:40 am UTC

Hi,

With grand total and all subtotals (which I think is nicer) :

SELECT CASE GROUPING_ID(reg_num, region, dt)
  WHEN 7 THEN 'GRAND TOTAL'
  WHEN 5 THEN REGION  || ' SUBTOTAL'
  WHEN 1 THEN REG_NUM || ' SUBTOTAL'
  ELSE TO_CHAR(Dt) END Dt1,
  CASE WHEN dt IS NULL THEN NULL ELSE REGION END REGION1,
  CASE WHEN dt IS NULL THEN NULL ELSE REG_NUM END REG_NUM1, SUM(AMT) Amt
FROM t
GROUP BY ROLLUP (REGION, reg_num, dt)
ORDER BY REGION, REG_NUM, DT NULLS LAST 


Can still filter out the region subtotals (GID = 5)

regards,
Chris Saxon
April 08, 2019 - 11:04 am UTC

Nice stuff, thanks

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.