Based on the SQLD exam syllabusReport an error
PIVOT (aggregate FOR column IN (value AS alias, …)) turns the values of one column into column headers. Each value in the IN list becomes one result column.valuealias_aggregatealias. With two or more aggregates, aliases are practically required.UNPIVOT (value_column FOR name_column IN (column, …)) does the opposite and turns columns into rows. By default it drops NULL rows; add INCLUDE NULLS to keep them.SUM(CASE WHEN … THEN … END) or SUM(DECODE(…)) with a GROUP BY produces the same table.If you total salary by job and department with GROUP BY, you get one row per (job, department) combination. You have to scan 9 rows top to bottom. Put the departments across as columns and you get a 5-row table (one row per job) that is easy to compare at a glance.
GROUP BY JOB, DEPTNO PIVOT result
JOB DEPTNO SUM JOB 10 20 30
CLERK 10 1300 ──▶ CLERK 1300 1900 950
CLERK 20 1900 MANAGER 2450 2975 2850
CLERK 30 950 …
…SELECT *
FROM (inline view with only the columns to pass in)
PIVOT (
aggregate_function(value column) [AS aggregate_alias]
FOR column to spread IN (value1 [AS alias1], value2 [AS alias2], …)
)
[ORDER BY …];| Part | What it does |
|---|---|
| Aggregate function | Computes the value that goes into each cell. SUM, COUNT, MAX, AVG, etc. |
| FOR column | The column whose values become column headers |
| IN (…) | The list of columns to create in the result. Values not in the list are dropped from the result |
| Remaining columns | Implicitly grouped by. They determine the rows of the result |
Salary totals by job and department.
SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) FOR DEPTNO IN (10, 20, 30)) ORDER BY JOB;JOB 10 20 30
--------- ---------- ---------- ----------
ANALYST 6000
CLERK 1300 1900 950
MANAGER 2450 2975 2850
PRESIDENT 5000
SALESMAN 560010, 20, 30. Because they start with a digit, you must use double quotes such as "10" to refer to them from the outside.Naming columns with value AS alias makes them easier to work with in the outer query.
SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30)) WHERE D30 IS NOT NULL ORDER BY JOB;JOB D10 D20 D30
--------- ---------- ---------- ----------
CLERK 1300 1900 950
MANAGER 2450 2975 2850
SALESMAN 5600IN (SELECT DEPTNO FROM DEPT) in the practice container, it raised ORA-00936 (missing expression). Subqueries and ANY can be used only with PIVOT XML.IN (10, 20, 30, 40) a column 40 is created and every cell in it is NULL.When spreading character values, wrap them in quotes. Here is the headcount by job for each department.
SELECT * FROM (SELECT DEPTNO, JOB FROM EMP) PIVOT (COUNT(*) FOR JOB IN ('CLERK' AS CLERK, 'MANAGER' AS MGR, 'SALESMAN' AS SALES)) ORDER BY DEPTNO; DEPTNO CLERK MGR SALES
---------- ---------- ---------- ----------
10 1 1 0
20 2 1 0
30 1 1 4IN ('CLERK', 'MANAGER') without aliases, the column names came out with the quotes included, like 'CLERK' and 'MANAGER'.PIVOT has no separate GROUP BY. Instead it uses every column not used in the aggregate or in FOR as the grouping key. If you pass EMP as is, columns such as EMPNO, ENAME, and HIREDATE also become grouping keys.
SELECT * FROM EMP PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30)) ORDER BY EMPNO FETCH FIRST 4 ROWS ONLY; EMPNO ENAME JOB MGR HIREDATE COMM D10 D20 D30
---------- ---------- --------- ---------- ---------- ---------- ---------- ---------- ----------
7369 SMITH CLERK 7902 1980-12-17 800
7499 ALLEN SALESMAN 7698 1981-02-20 300 1600
7521 WARD SALESMAN 7698 1981-02-22 500 1250
7566 JONES MANAGER 7839 1981-04-02 2975SELECT COUNT(*) FROM EMP PIVOT (…) also returned 14. If you expected 5 rows (one per job), that result is wrong.SELECT * FROM (SELECT DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30)); D10 D20 D30
---------- ---------- ----------
8750 10875 9400| Columns passed in | Grouping key (remaining columns) | Result rows |
|---|---|---|
| All of EMP | EMPNO, ENAME, JOB, MGR, HIREDATE, COMM | 14 |
| JOB, DEPTNO, SAL | JOB | 5 |
| DEPTNO, SAL | None | 1 |
⚠️ Trap: The number of rows in a PIVOT result is decided not by the IN list but by the columns left over from the ones you passed in. Even if an answer choice says "the result comes out by job," if FROM contains just a table name, every column of EMP becomes a grouping key.
If you list aggregate functions separated by commas, each value in the IN list produces as many columns as there are aggregates. The column name is valuealias_aggregatealias.
SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) AS S, COUNT(*) AS C FOR DEPTNO IN (10 AS D10, 20 AS D20)) ORDER BY JOB;JOB D10_S D10_C D20_S D20_C
--------- ---------- ---------- ---------- ----------
ANALYST 0 6000 2
CLERK 1300 1 1900 2
MANAGER 2450 1 2975 1
PRESIDENT 5000 1 0
SALESMAN 0 0SUM(SAL) AS TOT FOR DEPTNO IN (10 AS D10, 20), the column names were D10_TOT and 20_TOT.UNPIVOT gathers values scattered across several columns into one column. Which column each value originally came from is recorded in a new name column.
SELECT *
FROM table
UNPIVOT [INCLUDE NULLS | EXCLUDE NULLS] (
new column to hold the values
FOR new column to hold the original column names IN (column1 [AS value1], column2 [AS value2], …)
);Turn the SAL and COMM of employees in department 30 into (item, value) rows.
SELECT EMPNO, ENAME, ITEM, VAL FROM (SELECT EMPNO, ENAME, SAL, COMM FROM EMP WHERE DEPTNO = 30) UNPIVOT (VAL FOR ITEM IN (SAL, COMM)) ORDER BY EMPNO, ITEM; EMPNO ENAME ITEM VAL
---------- ---------- ---- ----------
7499 ALLEN COMM 300
7499 ALLEN SAL 1600
7521 WARD COMM 500
7521 WARD SAL 1250
7654 MARTIN COMM 1400
7654 MARTIN SAL 1250
7698 BLAKE SAL 2850
7844 TURNER COMM 0
7844 TURNER SAL 1500
7900 JAMES SAL 950EXCLUDE NULLS. TURNER's COMM of 0 is not NULL, so it stays.IN (SAL AS 'SAL', COMM AS 'COMM').IN (SAL, ENAME), raised ORA-01790.SELECT EMPNO, ENAME, ITEM, VAL FROM (SELECT EMPNO, ENAME, SAL, COMM FROM EMP WHERE DEPTNO = 30) UNPIVOT INCLUDE NULLS (VAL FOR ITEM IN (SAL, COMM)) ORDER BY EMPNO, ITEM; EMPNO ENAME ITEM VAL
---------- ---------- ---- ----------
7499 ALLEN COMM 300
7499 ALLEN SAL 1600
7521 WARD COMM 500
7521 WARD SAL 1250
7654 MARTIN COMM 1400
7654 MARTIN SAL 1250
7698 BLAKE COMM
7698 BLAKE SAL 2850
7844 TURNER COMM 0
7844 TURNER SAL 1500
7900 JAMES COMM
7900 JAMES SAL 950INCLUDE NULLS goes right after the UNPIVOT keyword, before the parenthesis.Put the PIVOT result above into a WITH clause and UNPIVOT it, and you get back the tall shape. Use D10 AS 10 so the original department numbers go in instead of the column names.
WITH P AS (SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30))) SELECT * FROM P UNPIVOT (SAL FOR DEPTNO IN (D10 AS 10, D20 AS 20, D30 AS 30)) ORDER BY JOB, DEPTNO;JOB DEPTNO SAL
--------- ---------- ----------
ANALYST 20 6000
CLERK 10 1300
CLERK 20 1900
CLERK 30 950
MANAGER 10 2450
MANAGER 20 2975
MANAGER 30 2850
PRESIDENT 10 5000
SALESMAN 30 5600GROUP BY JOB, DEPTNO. But you do not get back the original rows of the 14 employees. Information that was reduced by aggregation cannot be restored by UNPIVOT.Before the PIVOT clause existed, people put a condition on each column with CASE or DECODE and wrapped the whole thing in an aggregate function. It is still an easy approach to port to other DBMSs.
SELECT JOB, SUM(CASE WHEN DEPTNO = 10 THEN SAL END) AS D10, SUM(CASE WHEN DEPTNO = 20 THEN SAL END) AS D20, SUM(CASE WHEN DEPTNO = 30 THEN SAL END) AS D30 FROM EMP GROUP BY JOB ORDER BY JOB;JOB D10 D20 D30
--------- ---------- ---------- ----------
ANALYST 6000
CLERK 1300 1900 950
MANAGER 2450 2975 2850
PRESIDENT 5000
SALESMAN 5600CASE WHEN DEPTNO = 10 THEN SAL END yields SAL only on rows of department 10; on all other rows there is no ELSE, so it yields NULL. SUM skips NULLs, so only the department 10 total remains.GROUP BY JOB. That is why you can use EMP directly in FROM.SUM(DECODE(DEPTNO, 10, SAL)). When nothing matches, the default is NULL, so the result is the same as with CASE. DECODE is an Oracle-only function.| Method | Grouping key | Column names | Other DBMSs |
|---|---|---|---|
| PIVOT clause | Leftover columns from those passed in (implicit) | IN list values or aliases | Syntax differs by DBMS |
SUM(CASE …) + GROUP BY | Columns written in GROUP BY | AS alias | Standard SQL, so it mostly works as is |
SUM(DECODE(…)) + GROUP BY | Columns written in GROUP BY | AS alias | Oracle only |
Tomorrow we cover the regular expression functions (REGEXP_LIKE, REGEXP_SUBSTR, and others) that find and replace strings by pattern.
DECODE(expression, value, result, …), that picks a result when the value matches. If no value matches, it returns NULL.Click a choice to reveal the answer and explanation.
Question 1
What is the result of SELECT COUNT(*) FROM EMP PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30));? (EMP has 14 employees and 5 distinct jobs.)
Question 2
Choose the result column names, in order, for SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) AS TOT FOR DEPTNO IN (10 AS D10, 20));.
Question 3
What is the result of WITH P AS (SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30))) SELECT COUNT(*) FROM P UNPIVOT (SAL FOR DEPTNO IN (D10, D20, D30));? (P has 5 rows × 3 department columns, and 6 of those cells are NULL.)
Question 4
You want to spread the salary totals of departments 10, 20, and 30 across columns by job, without a PIVOT clause. Which method is appropriate?
Question 5
In SELECT * FROM (SELECT DEPTNO, JOB FROM EMP) PIVOT (COUNT(*) FOR JOB IN ('SALESMAN' AS S)) ORDER BY DEPTNO;, what is the value of S on the row for department 10? (Department 10 has no SALESMAN.)