简体   繁体   中英

Multiple levels of aggregation in SQL

I have the below table with 1 amount column and few other columns.

Input

Country Zone dept accnt amount
IND North CS A 100
IND West IT B 200
IND West IT D 500
US East IT C 300

I want to find the below.

  1. Total amount per country
  2. Under each country the zone with max amount
  3. Under each country the account with max amount

Output

Country tot_amnt Zone accnt
IND 800 West D
US 300 East C

Code I wrote

    with tot_rev as (select country,sum(amount)as net_amnt from sales group by country),
max_zone as (
    select * from
  (
  select a.country,a.zone,a.sum1,rank()over(PARTITION BY country order by a.sum1 desc)as rnk from
  (
  select distinct country,Zone,sum(amount)over(PARTITION BY country,zone)as sum1 from Sales
  )a)b
  where b.rnk=1
 ),
 
 max_accnt as (  select * from
  (
  select a.country,a.accnt,a.sum1,rank()over(PARTITION BY country order by a.sum1 desc)as rnk from
  (
  select distinct country,accnt,sum(amount)over(PARTITION BY country,accnt)as sum1 from Sales
  )a)b
  where b.rnk=1)
  
  select tot_rev.country,tot_rev.net_amnt,max_zone.zone,max_accnt.accnt from 
  tot_rev inner join max_zone
  on tot_rev.country=max_zone.country
  inner join max_accnt
  on max_zone.country=max_accnt.country

Though the code I wrote gives correct results, please let me know if there is a better more efficient way of writing this. I am dealing with a around 500 million records.

You can use aggregation with the KEEP clause:

SELECT Country,
       SUM( amount ) AS total_amount,
       MAX( Zone  ) KEEP ( DENSE_RANK LAST ORDER BY amount ) AS Zone,
       MAX( Accnt ) KEEP ( DENSE_RANK LAST ORDER BY amount ) AS Accnt
FROM   sales
GROUP BY Country;

Which, for the sample data:

CREATE TABLE sales ( Country, Zone, dept, accnt, amount ) AS
SELECT 'IND', 'North', 'CS', 'A', 100 FROM DUAL UNION ALL
SELECT 'IND', 'West',  'IT', 'B', 200 FROM DUAL UNION ALL
SELECT 'IND', 'West',  'IT', 'D', 500 FROM DUAL UNION ALL
SELECT 'IND', 'South', 'ZZ', 'E', 600 FROM DUAL UNION ALL
SELECT 'US',  'East',  'IT', 'C', 300 FROM DUAL;

Outputs:

 COUNTRY | TOTAL_AMOUNT |  ZONE | ACCNT:------ |  -----------: |:---- |:---- IND |  1400 |  South | E US | 300 |  East | C 

Update

If you want the Zone with the highest total (rather than the highest individual amount):

SELECT Country,
       SUM( amount ) AS total_amount,
       MAX( Zone  ) KEEP ( DENSE_RANK LAST ORDER BY zone_total_amount ) AS Zone,
       MAX( Accnt ) KEEP ( DENSE_RANK LAST ORDER BY amount ) AS Accnt
FROM   (
  SELECT s.*,
         SUM( amount ) OVER ( PARTITION BY Country, Zone ) AS zone_total_amount
  FROM   sales s
)
GROUP BY Country;

or

SELECT Country,
       SUM( zone_total_amount ) AS total_amount,
       MAX( Zone  ) KEEP ( DENSE_RANK LAST ORDER BY zone_total_amount ) AS Zone,
       MAX( Accnt ) KEEP ( DENSE_RANK LAST ORDER BY max_amount ) AS Accnt
FROM   (
  SELECT Country,
         Zone,
         MAX( accnt ) KEEP ( DENSE_RANK LAST ORDER BY Amount ) AS Accnt,
         MAX( amount ) AS max_amount,
         SUM( amount ) AS zone_total_amount
  FROM   sales s
  GROUP BY Country, Zone
)
GROUP BY Country;

Which both output:

 COUNTRY | TOTAL_AMOUNT |  ZONE | ACCNT:------ |  -----------: |:--- |:---- IND |  1400 |  West | E US | 300 |  East | C 

db<>fiddle here

I would do something like this. Notice that the subquery uses rollup aggregation, including the grouping_id identifier. https://oracle-base.com/articles/misc/rollup-cube-grouping-functions-and-grouping-sets#grouping_id

select country, min(case gid when 3 then amount end) as amount,
       min(zone)  keep (dense_rank last
                  order by decode(gid, 1, amount) nulls first) as zone,
       min(accnt) keep (dense_rank last 
                  order by decode(gid, 0, amount) nulls first) as accnt
from   (
         select country, zone, accnt,
                grouping_id(zone, accnt) as gid, sum(amount) as amount
         from   sales
         group  by country, rollup (zone, accnt)
       )
group  by country
;

COUNTRY     AMOUNT ZONE  ACCNT
------- ---------- ----- -----
IND           1400 West  E    
US             300 East  C    

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM