Recipe12.13.Calculating Subtotals for All Possible Expression Combinations


Recipe 12.13. Calculating Subtotals for All Possible Expression Combinations

Problem

You want to find the sum of all salaries by DEPTNO, and by JOB, for every JOB/ DEPTNO combination. You also want a grand total for all salaries in table EMP. You want to return the following result set:

 DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     30 CLERK     TOTAL BY DEPT AND JOB     950     30 SALESMAN  TOTAL BY DEPT AND JOB    5600     30 MANAGER   TOTAL BY DEPT AND JOB    2850     20 MANAGER   TOTAL BY DEPT AND JOB    2975     20 ANALYST   TOTAL BY DEPT AND JOB    6000        CLERK     TOTAL BY JOB             4150        ANALYST   TOTAL BY JOB             6000        MANAGER   TOTAL BY JOB             8275        PRESIDENT TOTAL BY JOB             5000        SALESMAN  TOTAL BY JOB             5600     10           TOTAL BY DEPT            8750     30           TOTAL BY DEPT            9400     20           TOTAL BY DEPT           10875                  GRAND TOTAL FOR TABLE   29025 

Solution

Extensions added to GROUP BY in recent years make this a fairly easy problem to solve. If your platform does not supply such extensions for computing various levels of subtotals, then you must compute them manually (via self joins or scalar subqueries).

DB2

For DB2, you will need to CAST the results from GROUPING to the CHAR(1) data type:

  1 select deptno,  2        job,  3        case cast(grouping(deptno) as char(1))||  4             cast(grouping(job) as char(1))  5             when '00' then 'TOTAL BY DEPT AND JOB'  6             when '10' then 'TOTAL BY JOB'  7             when '01' then 'TOTAL BY DEPT'  8             when '11' then 'TOTAL FOR TABLE'  9        end category, 10        sum(sal) 11   from emp 12  group by cube(deptno,job) 13  order by grouping(job),grouping(deptno) 

Oracle

Use the CUBE extension to the GROUP BY clause with the concatenation operator ||:

  1 select deptno,  2        job,  3        case grouping(deptno)||grouping(job)  4             when '00' then 'TOTAL BY DEPT AND JOB'  5             when '10' then 'TOTAL BY JOB'  6             when '01' then 'TOTAL BY DEPT'  7             when '11' then 'GRAND TOTAL FOR TABLE'  8        end category,  9        sum(sal) sal 10   from emp 11  group by cube(deptno,job) 12  order by grouping(job),grouping(deptno) 

SQL Server

Use the CUBE extension to the GROUP BY clause. For SQL Server, you will need to CAST the results from GROUPING to CHAR(1), and you will need to use the + operator for concatenation (as opposed to Oracle's || operator):

  1 select deptno,  2        job,  3        case cast(grouping(deptno)as char(1))+  4             cast(grouping(job)as char(1))  5             when '00' then 'TOTAL BY DEPT AND JOB'  6             when '10' then 'TOTAL BY JOB'  7             when '01' then 'TOTAL BY DEPT'  8             when '11' then 'GRAND TOTAL FOR TABLE'  9        end category, 10        sum(sal) sal 11   from emp 12  group by deptno,job with cube 13  order by grouping(job),grouping(deptno) 

PostgreSQL and MySQL

Use multiple UNION ALLs, creating different sums for each:

  1 select deptno, job,  2        'TOTAL BY DEPT AND JOB' as category,  3        sum(sal) as sal  4   from emp  5  group by deptno, job  6  union all  7 select null, job, 'TOTAL BY JOB', sum(sal)  8   from emp  9  group by job 10  union all 11 select deptno, null, 'TOTAL BY DEPT', sum(sal) 12   from emp 13  group by deptno 14  union all 15 select null,null,'GRAND TOTAL FOR TABLE', sum(sal) 16  from emp 

Discussion

Oracle, DB2, and SQL Server

The solutions for all three are essentially the same. The first step is to use the aggregate function SUM and group by both DEPTNO and JOB to find the total salaries for each JOB and DEPTNO combination:

  select deptno, job, sum(sal) sal   from emp  group by deptno, job DEPTNO JOB           SAL ------ --------- -------     10 CLERK        1300     10 MANAGER      2450     10 PRESIDENT    5000     20 CLERK        1900     20 ANALYST      6000     20 MANAGER      2975     30 CLERK         950     30 MANAGER      2850     30 SALESMAN     5600 

The next step is to create subtotals by JOB and DEPTNO along with the grand total for the whole table. Use the CUBE extension to the GROUP BY clause to perform aggregations on SAL by DEPTNO, JOB, and for the whole table:

  select deptno,        job,        sum(sal) sal   from emp  group by cube(deptno,job) DEPTNO JOB           SAL ------ --------- -------                    29025        CLERK        4150        ANALYST      6000        MANAGER      8275        SALESMAN     5600        PRESIDENT    5000     10              8750     10 CLERK        1300     10 MANAGER      2450     10 PRESIDENT    5000     20             10875     20 CLERK        1900     20 ANALYST      6000     20 MANAGER      2975     30              9400     30 CLERK         950     30 MANAGER      2850     30 SALESMAN     5600 

Next, use the GROUPING function in conjunction with CASE to format the results into more meaningful output. The value from GROUPING(JOB) will be 1 or 0 depending on whether or not the values for SAL are due to the GROUP BY or the CUBE. If the results are due to the CUBE, the value will be 1, otherwise it will be 0. The same goes for GROUPING(DEPTNO). Looking at the first step of the solution, you should see that grouping is done by DEPTNO and JOB. Thus, the expected values from the calls to GROUPING when a row represents a combination of both DEPTNO and JOB is 0. The query below confirms this:

  select deptno,        job,        grouping(deptno) is_deptno_subtotal,        grouping(job) is_job_subtotal,        sum(sal) sal   from emp  group by cube(deptno,job)  order by 3,4 DEPTNO JOB       IS_DEPTNO_SUBTOTAL IS_JOB_SUBTOTAL     SAL ------ --------- ------------------ --------------- -------     10 CLERK                      0               0    1300     10 MANAGER                    0               0    2450     10 PRESIDENT                  0               0    5000     20 CLERK                      0               0    1900     30 CLERK                      0               0     950     30 SALESMAN                   0               0    5600     30 MANAGER                    0               0    2850     20 MANAGER                    0               0    2975     20 ANALYST                    0               0    6000     10                            0               1    8750     20                            0               1   10875     30                            0               1    9400        CLERK                      1               0    4150        ANALYST                    1               0    6000        MANAGER                    1               0    8275        PRESIDENT                  1               0    5000        SALESMAN                   1               0    5600                                   1               1   29025 

The final step is to use a CASE expression to determine which category each row belongs to based on the values returned by GROUPING(JOB) and GROUPING(DEPTNO) concatenated:

  select deptno,        job,        case grouping(deptno)||grouping(job)             when '00' then 'TOTAL BY DEPT AND JOB'             when '10' then 'TOTAL BY JOB'             when '01' then 'TOTAL BY DEPT'             when '11' then 'GRAND TOTAL FOR TABLE'        end category,        sum(sal) sal   from emp  group by cube(deptno,job)  order by grouping(job),grouping(deptno) DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     30 CLERK     TOTAL BY DEPT AND JOB     950     30 SALESMAN  TOTAL BY DEPT AND JOB    5600     30 MANAGER   TOTAL BY DEPT AND JOB    2850     20 MANAGER   TOTAL BY DEPT AND JOB    2975     20 ANALYST   TOTAL BY DEPT AND JOB    6000        CLERK     TOTAL BY JOB             4150        ANALYST   TOTAL BY JOB             6000        MANAGER   TOTAL BY JOB             8275        PRESIDENT TOTAL BY JOB             5000        SALESMAN  TOTAL BY JOB             5600     10           TOTAL BY DEPT            8750     30           TOTAL BY DEPT            9400     20           TOTAL BY DEPT           10875                  GRAND TOTAL FOR TABLE   29025 

This Oracle solution implicitly converts the results from the GROUPING functions to a character type in preparation for concatenating the two values. DB2 and SQL Server users will need to explicitly CAST the results of the GROUPING functions to CHAR(1) as shown in the solution. In addition, SQL Server users must use the + operator, and not the || operator, to concatenate the results from the two GROUPING calls into one string.

For Oracle and DB2 users, there is an additional extension to GROUP BY called GROUPING SETS; this extension is extremely useful. For example, you can use GROUPING SETS to mimic the output created by CUBE as is done below (DB2 and SQL Server users will need to add explicit CASTS to the values returned by the GROUPING function just as in the CUBE solution):

  select deptno,        job,        case grouping(deptno)||grouping(job)             when '00' then 'TOTAL BY DEPT AND JOB'             when '10' then 'TOTAL BY JOB'             when '01' then 'TOTAL BY DEPT'             when '11' then 'GRAND TOTAL FOR TABLE'        end category,        sum(sal) sal   from emp  group by grouping sets ((deptno),(job),(deptno,job),( )) DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     20 CLERK     TOTAL BY DEPT AND JOB    1900     30 CLERK     TOTAL BY DEPT AND JOB     950     20 ANALYST   TOTAL BY DEPT AND JOB    6000     10 MANAGER   TOTAL BY DEPT AND JOB    2450     20 MANAGER   TOTAL BY DEPT AND JOB    2975     30 MANAGER   TOTAL BY DEPT AND JOB    2850     30 SALESMAN  TOTAL BY DEPT AND JOB    5600     10 PRESIDENT TOTAL BY DEPT AND JOB    5000        CLERK     TOTAL BY JOB             4150        ANALYST   TOTAL BY JOB             6000        MANAGER   TOTAL BY JOB             8275        SALESMAN  TOTAL BY JOB             5600        PRESIDENT TOTAL BY JOB             5000     10           TOTAL BY DEPT            8750     20           TOTAL BY DEPT           10875     30           TOTAL BY DEPT            9400                  GRAND TOTAL FOR TABLE   29025 

What's great about GROUPING SETS is that it allows you to define the groups. The GROUPING SETS clause in the preceding query causes groups to be created by DEPTNO, by JOB, by the combination of DEPTNO and JOB, and finally the empty parenthesis requests a grand total. GROUPING SETS gives you enormous flexibility for creating reports with different levels of aggregation; for example, if you wanted to modify the preceding example to exclude the GRAND TOTAL, simply modify the GROUPING SETS clause by excluding the empty parentheses:

 /* no grand total */  select deptno,        job,        case grouping(deptno)||grouping(job)             when '00' then 'TOTAL BY DEPT AND JOB'             when '10' then 'TOTAL BY JOB'             when '01' then 'TOTAL BY DEPT'             when '11' then 'GRAND TOTAL FOR TABLE'        end category,        sum(sal) sal   from emp  group by grouping sets ((deptno),(job),(deptno,job)) DEPTNO JOB       CATEGORY                    SAL ------ --------- --------------------- ----------     10 CLERK     TOTAL BY DEPT AND JOB       1300     20 CLERK     TOTAL BY DEPT AND JOB       1900     30 CLERK     TOTAL BY DEPT AND JOB        950     20 ANALYST   TOTAL BY DEPT AND JOB       6000     10 MANAGER   TOTAL BY DEPT AND JOB       2450     20 MANAGER   TOTAL BY DEPT AND JOB       2975     30 MANAGER   TOTAL BY DEPT AND JOB       2850     30 SALESMAN  TOTAL BY DEPT AND JOB       5600     10 PRESIDENT TOTAL BY DEPT AND JOB       5000        CLERK     TOTAL BY JOB                4150        ANALYST   TOTAL BY JOB                6000        ANAGER    TOTAL BY JOB                8275        SALESMAN  TOTAL BY JOB                5600        PRESIDENT TOTAL BY JOB                5000     10           TOTAL BY DEPT               8750     20           TOTAL BY DEPT              10875     30           TOTAL BY DEPT               9400 

You can also eliminate a subtotal, such as the one on DEPTNO, simply by omitting (DEPTNO) from the GROUPING SETS clause:

 /* no subtotals by DEPTNO */  select deptno,        job,        case grouping(deptno)||grouping(job)             when '00' then 'TOTAL BY DEPT AND JOB'             when '10' then 'TOTAL BY JOB'             when '01' then 'TOTAL BY DEPT'             when '11' then 'GRAND TOTAL FOR TABLE'        end category,        sum(sal) sal   from emp  group by grouping sets ((job),(deptno,job),( ))  order by 3 DEPTNO JOB       CATEGORY                     SAL ------ --------- --------------------- ----------                  GRAND TOTAL FOR TABLE      29025     10 CLERK     TOTAL BY DEPT AND JOB       1300     20 CLERK     TOTAL BY DEPT AND JOB       1900     30 CLERK     TOTAL BY DEPT AND JOB        950     20 ANALYST   TOTAL BY DEPT AND JOB       6000     20 MANAGER   TOTAL BY DEPT AND JOB       2975     30 MANAGER   TOTAL BY DEPT AND JOB       2850     30 SALESMAN  TOTAL BY DEPT AND JOB       5600     10 PRESIDENT TOTAL BY DEPT AND JOB       5000     10 MANAGER   TOTAL BY DEPT AND JOB       2450        CLERK     TOTAL BY JOB                4150        SALESMAN  TOTAL BY JOB                5600        PRESIDENT TOTAL BY JOB                5000        MANAGER   TOTAL BY JOB                8275        ANALYST   TOTAL BY JOB                6000 

As you can see, GROUPING SETS makes it very easy indeed to play around with totals and subtotals in order to look at your data from different angles.

PostgreSQL and MySQL

The first step is to use the aggregate function SUM and group by both DEPTNO and JOB:

  select deptno, job,        'TOTAL BY DEPT AND JOB' as category,        sum(sal) as sal   from emp  group by deptno, job DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     20 ANALYST   TOTAL BY DEPT AND JOB    6000     20 MANAGER   TOTAL BY DEPT AND JOB    2975     30 CLERK     TOTAL BY DEPT AND JOB     950     30 MANAGER   TOTAL BY DEPT AND JOB    2850     30 SALESMAN  TOTAL BY DEPT AND JOB    5600 

The next step is to UNION ALL the sum of all the salaries by JOB:

  select deptno, job,        'TOTAL BY DEPT AND JOB' as category,        sum(sal) as sal   from emp  group by deptno, job  union all select null, job, 'TOTAL BY JOB', sum(sal)   from emp  group by job DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     20 ANALYST   TOTAL BY DEPT AND JOB    6000     20 MANAGER   TOTAL BY DEPT AND JOB    2975     30 CLERK     TOTAL BY DEPT AND JOB     950     30 MANAGER   TOTAL BY DEPT AND JOB    2850     30 SALESMAN  TOTAL BY DEPT AND JOB    5600        ANALYST   TOTAL BY JOB             6000        CLERK     TOTAL BY JOB             4150        MANAGER   TOTAL BY JOB             8275        PRESIDENT TOTAL BY JOB             5000        SALESMAN  TOTAL BY JOB             5600 

The next step is to UNION ALL the sum of all the salaries by DEPTNO:

  select deptno, job,        'TOTAL BY DEPT AND JOB' as category,        sum(sal) as sal   from emp  group by deptno, job  union all select null, job, 'TOTAL BY JOB', sum(sal)   from emp  group by job  union all select deptno, null, 'TOTAL BY DEPT', sum(sal)   from emp    group by deptno DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     20 ANALYST   TOTAL BY DEPT AND JOB    6000     20 MANAGER   TOTAL BY DEPT AND JOB    2975     30 CLERK     TOTAL BY DEPT AND JOB     950     30 MANAGER   TOTAL BY DEPT AND JOB    2850     30 SALESMAN  TOTAL BY DEPT AND JOB    5600        ANALYST   TOTAL BY JOB             6000        CLERK     TOTAL BY JOB             4150        MANAGER   TOTAL BY JOB             8275        PRESIDENT TOTAL BY JOB             5000        SALESMAN  TOTAL BY JOB             5600     10           TOTAL BY DEPT            8750     20           TOTAL BY DEPT            10875     30           TOTAL BY DEPT             9400 

The final step is to UNION ALL the sum of all salaries in table EMP:

  select deptno, job,        'TOTAL BY DEPT AND JOB' as category,        sum(sal) as sal   from emp  group by deptno, job  union all select null, job, 'TOTAL BY JOB', sum(sal)   from emp  group by job  union all select deptno, null, 'TOTAL BY DEPT', sum(sal)   from emp  group by deptno  union all select null,null, 'GRAND TOTAL FOR TABLE', sum(sal)   from emp DEPTNO JOB       CATEGORY                  SAL ------ --------- --------------------- -------     10 CLERK     TOTAL BY DEPT AND JOB    1300     10 MANAGER   TOTAL BY DEPT AND JOB    2450     10 PRESIDENT TOTAL BY DEPT AND JOB    5000     20 CLERK     TOTAL BY DEPT AND JOB    1900     20 ANALYST   TOTAL BY DEPT AND JOB    6000     20 MANAGER   TOTAL BY DEPT AND JOB    2975     30 CLERK     TOTAL BY DEPT AND JOB     950     30 MANAGER   TOTAL BY DEPT AND JOB    2850     30 SALESMAN  TOTAL BY DEPT AND JOB    5600        ANALYST   TOTAL BY JOB             6000        CLERK     TOTAL BY JOB             4150        MANAGER   TOTAL BY JOB             8275        PRESIDENT TOTAL BY JOB             5000        SALESMAN  TOTAL BY JOB             5600     10           TOTAL BY DEPT            8750     20           TOTAL BY DEPT           10875     30           TOTAL BY DEPT            9400                  GRAND TOTAL FOR TABLE   29025 




SQL Cookbook
SQL Cookbook (Cookbooks (OReilly))
ISBN: 0596009763
EAN: 2147483647
Year: 2005
Pages: 235

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net