Oracle Skills Enhancement Part 11 Reporting and Data Warehouse Operations

1. row to column

The "row to column" statement (CASE WHEN END statement / PIVOT function) is often used when rewriting reports or statements.

(1) Usage of CASE WHEN END

SELECT job as position,
       case deptno when 10 then sal end as department_10_salary,
       case deptno when 20 then sal end as department_20_salary,
       case deptno when 30 then sal end as department_30_salary,
       sal as sum_salary
  FROM emp
 ORDER BY 1;

 Seeing the above picture, we can actually group and summarize according to the job. There will be an aggregate function in the "row-to-column" statement, the purpose is to convert similar books into one row for display. This makes the report appear more organized. In addition, the last column also aggregates and displays the salary. However, the PIVOT function cannot be implemented. The PIVOT function will only classify the data according to the same rules. The data of each column is independent of each other, and there will be no cross-repetition.

SELECT job as position,
       sum(case deptno when 10 then sal end) as department_10_salary,
       sum(case deptno when 20 then sal end) as department_20_salary,
       sum(case deptno when 30 then sal end) as department_30_salary,
       sum(sal) as sum_salary
  FROM emp
 GROUP BY job
 ORDER BY 1;

 (2) Usage of PIVOT function

SELECT *
  FROM (SELECT job, sal, deptno FROM emp)
pivot(SUM(sal) AS salary
   FOR deptno IN(10 AS department10, 20 AS department20, 30 AS department30))
 ORDER BY 1;

 

 For example, we also need to increase the return of bonus, using the PIVOT function only needs to add a field. However, using the CASE WHEN END statement requires three additional statements, as follows:


SELECT *
  FROM (SELECT job, sal,comm, deptno FROM emp)
pivot(SUM(sal) AS salary,SUM(comm) AS bonus
   FOR deptno IN(10 AS d10, 20 AS d20, 30 AS d30))
 ORDER BY 1;

 SELECT job as position,
       sum(case deptno when 10 then sal end) as d_10_salary,
       sum(case deptno when 20 then sal end) as d_20_salary,
       sum(case deptno when 30 then sal end) as d_30_salary,
       sum(case deptno when 10 then comm end) as d_10_bonus,
       sum(case deptno when 20 then comm end) as d_20_bonus,
       sum(case deptno when 30 then comm end) as d_30_bonus,
       sum(sal) as sum_salary
  FROM emp

 However, when the PIVOT function performs row-to-column conversion, it can only be converted according to one condition. If both job and department are converted into columns and summarized into a row, currently only the CASE WHEN END statement can be used to convert columns.

SELECT sum(case when deptno = 10 then sal end) AS d10_s,
sum(case when deptno = 20 then sal end) AS d20_s,
sum(case when deptno = 30 then sal end) AS d30_s,
sum(case when job = 'ANALYST' then sal end) AS ANALYST,
sum(case when job = 'CLERK' then sal end) AS CLERK,
sum(case when job = 'MANAGER' then sal end) AS MANAGER,
sum(case when job = 'PRESIDENT' then sal end) AS PRESIDENT,
sum(case when job = 'SALESMAN' then sal end) AS SALESMAN
FROM emp;

2. Column to row

test data as below:

CREATE OR REPLACE VIEW v AS
SELECT *
  FROM (SELECT deptno, sal FROM emp)
pivot(COUNT(*) AS ct, SUM(sal) AS s
   FOR deptno IN(10 AS deptno_10, 20 AS deptno_20, 30 AS deptno_30));

 The requirement is to display the trips in three departments on one column. In the past, UNION ALL was used in the face of such a requirement , but if there are multiple columns, coding and maintenance would be inconvenient, and now the UNPIVOT function can be used directly.

SELECT '10' AS department code, DEPTNO_10_CT AS person-time FROM v
UNION ALL
SELECT '20' AS department code, DEPTNO_20_CT AS person-time FROM v
UNION ALL
SELECT '30' AS department code, DEPTNO_30_CT AS person-time FROM v;

SELECT deptno AS column name, substr(deptno, -5, 2) AS department code, person
  -time FROM v unpivot(person-time FOR deptno IN(deptno_10_CT, deptno_20_CT, deptno_30_CT));

 

 The column to be converted can be maintained in the IN list. However, when the total number of people and wages are to be converted at the same time, the UNPIVOT function will be limited , and it needs to be converted separately and then JOIN can be implemented. ( The parameter INCLUDE NULLS is used this time for the consistency of data results, even if the data is empty, the row will be displayed ) as follows:

SELECT a. column name, a. department code, a. person-time, b. salary
  FROM (SELECT substr(deptno, 1, 9) AS column name,
               substr(deptno, -5, 2) AS department code ,
               person
          -time FROM v unpivot include NULLS(person-time FOR deptno IN(deptno_10_ct,
                                                        deptno_20_ct,
                                                        deptno_30_ct))) a
 INNER JOIN (SELECT substr(deptno, 1, 9) AS column name, salary
               FROM v unpivot include NULLS(salary FOR deptno IN(deptno_10_s,
                                                             deptno_20_s,
                                                             deptno_30_s ))) b
    ON (b.column_name = a.column_name);

 

 

 3. Reverse transpose the result set into a column

Sometimes the requirement requires the data to be displayed vertically, with spaces between each line. Implemented using the UNPIVOT function as follows:

SELECT EMPS
  FROM (SELECT ENAME, JOB, TO_CHAR(SAL) AS SAL, NULL AS T_COL /*Add this column to display empty rows*/
          FROM EMP) UNPIVOT INCLUDE NULLS /*Add this parameter to convert null values ​​to a row*/ (EMPS FOR COL IN(ENAME,
                                                                             JOB,
                                                                             SAL,
                                                                             T_COL));

 4. Suppress duplicate values ​​in the result set.

The data returned by the query often has heavy values . However, these data are required to be displayed combined. Usually it is processed by the front desk. In special cases, the query result only needs to return the first row of data, so how to deal with it? However, it is good to use LAG to judge.

SELECT CASE
       /*When the department category is sorted by name, it will not be displayed when it is the same as the previous one*/
         WHEN lag(deptno) over(ORDER BY deptno, ename) = deptno THEN
          NULL
         ELSE
          deptno
       END AS deptno,
       ename
  FROM emp
 ORDER BY emp .deptno, ename;

5. Use "row to column" for calculation

In addition to being used for report display, "row to column" can also be used for calculation by using the characteristics of the converted values ​​on the same row. Calculate the total wage difference between departments 20 and 10 and between departments 20 and 30 as follows.

SELECT d10_sal,
       d20_sal,
       d30_sal,
       d20_sal - d10_sal AS d20_10_diff,
       d20_sal - d30_sal AS d20_30_diff
  FROM (SELECT SUM(CASE
                     WHEN deptno = 10 THEN
                      sal
                   END) AS d10_sal,
               SUM(CASE
                     WHEN deptno = 20 THEN
                      sal
                   END) AS d20_sal,
               SUM(CASE
                     WHEN deptno = 30 THEN
                      sal
                   END) AS d30_sal
          FROM emp) totals_by_dept;

6. Group the data

The requirement is that a company needs to carry out activities, and employees need to be grouped into groups of 5. How to use query statements to achieve this requirement?

/* Then divide the number by 5, and use ceil to return the next integer not less than the current value. */
SELECT ceil(rn / 5) AS group, empno AS code, ename AS name
  FROM (
        /* first we have to give each bit Employee generates a number */
        SELECT row_number() over(ORDER BY empno) AS rn , empno, ename
          FROM emp);

7. Create a horizontal histogram

SELECT deptno AS department number, lpad('*', COUNT(*), '*') AS employee distribution FROM emp GROUP BY deptno;

 8. Create a vertical histogram

SELECT *
  FROM (SELECT '*' AS ename,
               deptno,
               /*The sub-departments are sorted by name and take the serial number as the basis for grouping*/
               row_number() over(PARTITION BY deptno ORDER BY ename) AS sn
          FROM emp)
pivot(MAX( ename)
   FOR deptno IN(10 AS deptno_10, 20 AS deptno_20, 30 AS deptno_30))
 ORDER BY sn DESC;

 9. Identify the maximum and minimum values

When querying a report, it is sometimes required to identify the maximum and minimum values. In the past, we often used the method of sub-query association, which required one more scan of the table. It 's much easier with the analysis function , we can use the analysis function to come up with the corresponding value, and then compare it in the returned data.

SELECT deptno,
       ename,
       job,
       sal,
       CASE
         WHEN sal = max_by_dept THEN
          'The highest salary in the department'

         WHEN sal = min_by_dept THEN
          'The lowest salary in the department'
       END dept_status,
       CASE
         WHEN sal = max_by_job THEN
          'The highest salary in the job'
         WHEN sal = min_by_job THEN
          'Minimum wage within job'
       END job_status
  FROM (SELECT deptno,
               ename,
               job,
               sal,
               MAX(sal) over(PARTITION BY deptno) max_by_dept,
               MAX(sal) over(PARTITION BY job) max_by_job,
               MIN(sal) over(PARTITION BY deptno) min_by_dept,
               MIN(sal) over(PARTITION BY job) min_by_job

          FROM emp) emp_sals
 WHERE sal IN (max_by_dept, max_by_job, min_by_dept, min_by_job);

 10. Calculate Simple Subtotals

It is often necessary to add a total when generating report data, must UNION ALL be used to do it? No, we can achieve this goal with rollup .

SELECT CASE GROUPING(job)
         WHEN 0 THEN
          job
         ELSE
          'Total total'
       END AS job,
       SUM(sal) AS wage subtotal
  FROM emp
 GROUP BY ROLLUP(job);

 ROLLUP, an extension of the GROUP BY clause, returns subtotal records for each grouping and total records for all groupings. In order to facilitate understanding, let's make an example compared with UNION ALL

SELECT deptno AS department code,
       job AS job title,
       to_char(hiredate, 'yyyy') AS year,
       SUM(sal)
  FROM emp
 GROUP BY ROLLUP(deptno, job, to_char(hiredate, 'yyyy'));

 equivalence

SELECT deptno AS department code, job AS job title, to_char(hiredate, 'yyyy') AS year, SUM(sal)
FROM emp
GROUP BY deptno , job, to_char(hiredate, 'yyyy')
UNION ALL
SELECT deptno AS department code, job AS job, NULL/*job brief*/ AS year, SUM(sal)
FROM emp
GROUP BY deptno, job
UNION ALL
SELECT deptno AS department code, NULL/*department subtotal*/ AS job, NULL AS year, SUM( sal)
FROM emp
GROUP BY deptno
UNION ALL
SELECT NULL /*Total total*/, '' AS position, '' AS y, SUM(sal)
FROM emp; 

 Many people may have used this method, so if there is a way to deal with the sum of the two columns by department and position plus the total? We can put the two columns of department and position as a whole and put them in parentheses.

SELECT CASE GROUPING(deptno)
         WHEN 0 THEN
          job
         ELSE
          'Total total'
       END AS department code,
       job title,
       SUM(sal) AS wage subtotal
  FROM emp
 GROUP BY ROLLUP((deptno, job));

 11. Windowing explanation of common analysis functions

After sorting by salary, take the minimum value in the range from the first row to the current row.

SELECT ENAME,
       SAL,
       /*Because it is sorted by salary, so this statement takes out the minimum value of all rows*/
       MIN(sal) over(ORDER BY sal) AS min_11,
       /*The default parameter window of the above statement is as follows, in the plan You can see */
       MIN(sal) over(ORDER BY sal RANGE BETWEEN unbounded preceding AND CURRENT ROW) AS min_12,
       /* in this case rows are the same as RANGE returns */
       MIN(SAL) OVER(ORDER BY SAL ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS min_13,
       /* Take the minimum value of all rows for comparison*/
       MIN(SAL) OVER() AS min_14,
       /* If the range of min_14 is explicitly written, it is */
       MIN(SAL) OVER( ORDER BY sal RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS min_15,
       /*In this case rows is the same as RANGE return data*/
       MIN(SAL) OVER(ORDER BY sal RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS min_16
  FROM emp
 WHERE deptno = 30;

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324194880&siteId=291194637