1) SQL analytic function
1@@@@ basic example
@@@
@@@<1> over() for each record, or for each group
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
prompt
prompt Without OVER(), aggregate function
prompt one record for each group
SELECT COUNT(*)
, SUM(sal)
FROM emp
WHERE deptno = 30
/
SELECT deptno
, COUNT(*)
, SUM(sal)
FROM emp
GROUP BY deptno
/
prompt
prompt With OVER(), analytic function
prompt one result for each record in the dataset, no grouping
SELECT deptno, ename, sal
, COUNT(*) OVER ()
, SUM(sal) OVER ()
FROM emp
WHERE deptno = 30
/
SCOTT@ocm> @tmp.sql
Without OVER(), aggregate function
one record for each group
COUNT(*) SUM(SAL)
---------- ----------
6 9400
DEPTNO COUNT(*) SUM(SAL)
---------- ---------- ----------
30 6 9400
20 5 10875
10 3 8750
With OVER(), analytic function
one result for each record in the dataset, no grouping
DEPTNO ENAME SAL COUNT(*)OVER() SUM(SAL)OVER()
---------- ---------- ---------- -------------- --------------
30 ALLEN 1600 6 9400
30 WARD 1250 6 9400
30 MARTIN 1250 6 9400
30 BLAKE 2850 6 9400
30 TURNER 1500 6 9400
30 JAMES 950 6 9400
@@@
@@@<2> Group by OVER()
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
prompt @@@ Distinct versus Group By
prompt
prompt Using GROUP BY for each group
SELECT deptno
, COUNT(*) OVER() AS emp_counter
FROM scott.emp
GROUP BY deptno
/
prompt
prompt Without GROUP BY for each record
SELECT DISTINCT deptno
, COUNT(*) OVER() AS emp_counter
FROM scott.emp
/
SELECT deptno
, COUNT(*) OVER() AS emp_counter
FROM scott.emp
/
SELECT COUNT(*)
FROM scott.emp
/
SCOTT@ocm> @tmp.sql
@@@ Distinct versus Group By
Using GROUP BY for each group
DEPTNO EMP_COUNTER
---------- -----------
10 3
20 3
30 3
Without GROUP BY for each record
DEPTNO EMP_COUNTER
---------- -----------
30 14
10 14
20 14
DEPTNO EMP_COUNTER
---------- -----------
20 14
30 14
30 14
20 14
30 14
30 14
10 14
20 14
10 14
30 14
20 14
30 14
20 14
10 14
COUNT(*)
----------
14
2@@@@ partition by
Within the set of parentheses
Expressions telling the function to calculate differently
Three possible components
Partition
Order
Windowing
Some or all are optional, depending upon the function
Components must be in this order
@@@
@@@<1> partition by clause
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
prompt @@@ partition by
prompt calculated on a subset of the records
SELECT deptno, ename, sal, job
, COUNT(*) OVER(PARTITION BY job) job_count
, SUM(sal) OVER(PARTITION BY deptno) dept_sum_sal
FROM scott.emp
WHERE rownum < 5
/
SELECT deptno, ename, sal, job
, COUNT(*) OVER(PARTITION BY job) job_count
, SUM(sal) OVER(PARTITION BY deptno) dept_sum_sal
FROM scott.emp
WHERE rownum < 8
/
SELECT deptno, ename, sal, job
, COUNT(*) OVER(PARTITION BY job) job_count
, SUM(sal) OVER(PARTITION BY deptno) dept_sum_sal
FROM scott.emp
/
SCOTT@ocm> @tmp.sql
@@@ partition by
calculated on a subset of the records
DEPTNO ENAME SAL JOB JOB_COUNT DEPT_SUM_SAL
---------- ---------- ---------- --------- ---------- ------------
20 SMITH 800 CLERK 1 3775
20 JONES 2975 MANAGER 1 3775
30 WARD 1250 SALESMAN 2 2850
30 ALLEN 1600 SALESMAN 2 2850
DEPTNO ENAME SAL JOB JOB_COUNT DEPT_SUM_SAL
---------- ---------- ---------- --------- ---------- ------------
20 SMITH 800 CLERK 1 3775
30 BLAKE 2850 MANAGER 3 6950
20 JONES 2975 MANAGER 3 3775
10 CLARK 2450 MANAGER 3 2450
30 MARTIN 1250 SALESMAN 3 6950
30 WARD 1250 SALESMAN 3 6950
30 ALLEN 1600 SALESMAN 3 6950
DEPTNO ENAME SAL JOB JOB_COUNT DEPT_SUM_SAL
---------- ---------- ---------- --------- ---------- ------------
20 SCOTT 3000 ANALYST 2 10875
20 FORD 3000 ANALYST 2 10875
10 MILLER 1300 CLERK 4 8750
20 SMITH 800 CLERK 4 10875
30 JAMES 950 CLERK 4 9400
20 ADAMS 1100 CLERK 4 10875
10 CLARK 2450 MANAGER 3 8750
30 BLAKE 2850 MANAGER 3 9400
20 JONES 2975 MANAGER 3 10875
10 KING 5000 PRESIDENT 1 8750
30 MARTIN 1250 SALESMAN 4 9400
30 WARD 1250 SALESMAN 4 9400
30 TURNER 1500 SALESMAN 4 9400
30 ALLEN 1600 SALESMAN 4 9400
@@@Summary:
First, analytic function for each record.
Here, COUNT(*) OVER(PARTITION BY job), total number of the same job in
current results set.
Here, COUNT(*) OVER(PARTITION BY deptno), total salary of the department
in current record
@@@
@@@<2> use correlated scalar subqueries
@@@
SCOTT@ocm> !cat tmpx.sql
set echo off
set verify off
set feedback off
set autot off
prompt overwritted by normal sql
SELECT deptno, ename, sal, job
, (SELECT COUNT(*) FROM emp WHERE job=e.job) job_count
, (SELECT SUM(sal) FROM emp WHERE deptno=e.deptno) dept_sum_sal
FROM emp e
/
SCOTT@ocm> @tmpx.sql
overwritted by normal sql
DEPTNO ENAME SAL JOB JOB_COUNT DEPT_SUM_SAL
---------- ---------- ---------- --------- ---------- ------------
20 SMITH 800 CLERK 4 10875
30 ALLEN 1600 SALESMAN 4 9400
30 WARD 1250 SALESMAN 4 9400
20 JONES 2975 MANAGER 3 10875
30 MARTIN 1250 SALESMAN 4 9400
30 BLAKE 2850 MANAGER 3 9400
10 CLARK 2450 MANAGER 3 8750
20 SCOTT 3000 ANALYST 2 10875
10 KING 5000 PRESIDENT 1 8750
30 TURNER 1500 SALESMAN 4 9400
20 ADAMS 1100 CLERK 4 10875
30 JAMES 950 CLERK 4 9400
20 FORD 3000 ANALYST 2 10875
10 MILLER 1300 CLERK 4 8750
@@@
@@@<3> Observe the execution plan
@@@
SCOTT@ocm> !cat tmpx.sql
set echo off
set verify off
set feedback off
set autot traceonly explain
prompt observe the execution plan
SELECT deptno, ename, sal, job
, (SELECT COUNT(*) FROM emp WHERE job=e.job) job_count
, (SELECT SUM(sal) FROM emp WHERE deptno=e.deptno) dept_sum_sal
FROM emp e
/
SELECT deptno, ename, sal, job
, COUNT(*) OVER(PARTITION BY job) job_count
, SUM(sal) OVER(PARTITION BY deptno) dept_sum_sal
FROM emp e
/
SCOTT@ocm> @tmpx.sql
observe the execution plan
Execution Plan
----------------------------------------------------------
Plan hash value: 1174980467
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 294 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 8 | | |
|* 2 | TABLE ACCESS FULL| EMP | 3 | 24 | 3 (0)| 00:00:01 |
| 3 | SORT AGGREGATE | | 1 | 7 | | |
|* 4 | TABLE ACCESS FULL| EMP | 5 | 35 | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL | EMP | 14 | 294 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("JOB"=:B1)
4 - filter("DEPTNO"=:B1)
Execution Plan
----------------------------------------------------------
Plan hash value: 4086863039
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 294 | 5 (40)| 00:00:01 |
| 1 | WINDOW SORT | | 14 | 294 | 5 (40)| 00:00:01 |
| 2 | WINDOW SORT | | 14 | 294 | 5 (40)| 00:00:01 |
| 3 | TABLE ACCESS FULL| EMP | 14 | 294 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------
@@@Summary:
Traditional aggregate function occurs 3 pass (full table scan) over the table
Analytic SQL only 1 pass(full table scan).
3@@@@ LEAD and LAG
@@@
@@@<1> basic LAG and LEAD
@@@
Usage:
LAG/LEAD ( field_name, num_recs ) OVER ( )
Return the value from a field when looking one record (or more) behind/ahead
Using the order specified
ORDER BY is required
Does not have to be order used in the query
Optional second param to look more than one record
These functions are analytic only
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@LAG|LEAD order by hiredate, in the same record display the
prompt ~ ename which order is prior current record in queue order
prompt ~ by analytic function LAG. LEAD
SELECT deptno, ename, to_char(hiredate,'yyyy-mm-dd')
, LAG(ename) OVER(ORDER BY hiredate) prior_hire
FROM scott.emp
ORDER BY deptno, ename
/
SELECT deptno, ename, to_char(hiredate,'yyyy-mm-dd')
, LEAD(ename) OVER(ORDER BY hiredate) behind_hire
FROM scott.emp
ORDER BY deptno, ename
/
prompt
prompt @@@Compare below output, observe the ename and *_hire fields
prompt ~ above
SELECT ename, to_char(hiredate,'yyyy-mm-dd')
FROM scott.emp
ORDER BY hiredate
/
SCOTT@ocm> @tmp.sql
@@@LAG|LEAD order by hiredate, in the same record display the
~ ename which order is prior current record in queue order
~ by analytic function LAG. LEAD
DEPTNO ENAME TO_CHAR(HI PRIOR_HIRE
---------- ---------- ---------- ----------
10 CLARK 1981-06-09 BLAKE
10 KING 1981-11-17 MARTIN
10 MILLER 1982-01-23 FORD
20 ADAMS 1987-05-23 SCOTT
20 FORD 1981-12-03 JAMES
20 JONES 1981-04-02 WARD
20 SCOTT 1987-04-19 MILLER
20 SMITH 1980-12-17
30 ALLEN 1981-02-20 SMITH
30 BLAKE 1981-05-01 JONES
30 JAMES 1981-12-03 KING
30 MARTIN 1981-09-28 TURNER
30 TURNER 1981-09-08 CLARK
30 WARD 1981-02-22 ALLEN
DEPTNO ENAME TO_CHAR(HI BEHIND_HIR
---------- ---------- ---------- ----------
10 CLARK 1981-06-09 TURNER
10 KING 1981-11-17 JAMES
10 MILLER 1982-01-23 SCOTT
20 ADAMS 1987-05-23
20 FORD 1981-12-03 MILLER
20 JONES 1981-04-02 BLAKE
20 SCOTT 1987-04-19 ADAMS
20 SMITH 1980-12-17 ALLEN
30 ALLEN 1981-02-20 WARD
30 BLAKE 1981-05-01 CLARK
30 JAMES 1981-12-03 FORD
30 MARTIN 1981-09-28 KING
30 TURNER 1981-09-08 MARTIN
30 WARD 1981-02-22 JONES
@@@Compare below output, observe the ename and *_hire fields
~ above
ENAME TO_CHAR(HI
---------- ----------
SMITH 1980-12-17
ALLEN 1981-02-20
WARD 1981-02-22
JONES 1981-04-02
BLAKE 1981-05-01
CLARK 1981-06-09
TURNER 1981-09-08
MARTIN 1981-09-28
KING 1981-11-17
JAMES 1981-12-03
FORD 1981-12-03
MILLER 1982-01-23
SCOTT 1987-04-19
ADAMS 1987-05-23
@@@
@@@<2> ORDER BY variation
@@@
In this example,
LAG(ename,2) means it prior emp name with interval 2, absoultly, it would
be null.
LEAD(ename) with DESC, like negative negative = positive
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@ORDER BY variation
SELECT deptno, ename, sal
, LAG(ename) OVER(ORDER BY ename) f1
, LAG(ename,2) OVER(ORDER BY ename) f2
, LEAD(ename) OVER(ORDER BY ename DESC) f3
, LAG(sal) OVER(ORDER BY ename) f4
FROM scott.emp
ORDER BY deptno, ename
/
prompt
prompt @@@Compare below output, observe the ename and *_hire fields
prompt ~ above
SELECT ename
FROM scott.emp
ORDER BY ename
/
SCOTT@ocm> @tmp.sql
@@@ORDER BY variation
DEPTNO ENAME SAL F1 F2 F3 F4
---------- ---------- ---------- ---------- ---------- ---------- ----------
10 CLARK 2450 BLAKE ALLEN BLAKE 2850
10 KING 5000 JONES JAMES JONES 2975
10 MILLER 1300 MARTIN KING MARTIN 1250
20 ADAMS 1100
20 FORD 3000 CLARK BLAKE CLARK 2450
20 JONES 2975 JAMES FORD JAMES 950
20 SCOTT 3000 MILLER MARTIN MILLER 1300
20 SMITH 800 SCOTT MILLER SCOTT 3000
30 ALLEN 1600 ADAMS ADAMS 1100
30 BLAKE 2850 ALLEN ADAMS ALLEN 1600
30 JAMES 950 FORD CLARK FORD 3000
30 MARTIN 1250 KING JONES KING 5000
30 TURNER 1500 SMITH SCOTT SMITH 800
30 WARD 1250 TURNER SMITH TURNER 1500
@@@Compare below output, observe the ename and *_hire fields
~ above
ENAME
----------
ADAMS
ALLEN -----then this one is prior record with interval 2
BLAKE
CLARK -----if this one is current record
FORD
JAMES -----the same this one is behind record with interval 2
JONES
KING
MARTIN
MILLER
SCOTT
SMITH
TURNER
WARD
@@@
@@@<3> ORDER BY and PARTITION BY clause
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@ORDER BY and PARTITION BY variation
prompt ~ PARTITION BY deptno means treat a partition as a result sets
prompt ~ so the null would add.
SELECT deptno, ename, sal
, LAG(ename) OVER(ORDER BY ename) f1
, LAG(ename) OVER(PARTITION BY deptno ORDER BY ename) f2
, LAG(ename) OVER(PARTITION BY deptno ORDER BY sal DESC) f3
FROM scott.emp
ORDER BY deptno, ename
/
prompt
prompt @@@Compare below output, observe the ename and *_hire fields
prompt ~ above
SELECT deptno, ename
FROM scott.emp
ORDER BY deptno, ename
/
SELECT ename, deptno, sal
FROM scott.emp
ORDER BY sal
/
SCOTT@ocm> @tmp.sql
@@@ORDER BY and PARTITION BY variation
~ PARTITION BY deptno means treat a partition as a result sets
~ so the null would add.
DEPTNO ENAME SAL F1 F2 F3
---------- ---------- ---------- ---------- ---------- ----------
10 CLARK 2450 BLAKE KING
10 KING 5000 JONES CLARK
10 MILLER 1300 MARTIN KING CLARK
20 ADAMS 1100 JONES
20 FORD 3000 CLARK ADAMS SCOTT
20 JONES 2975 JAMES FORD FORD
20 SCOTT 3000 MILLER JONES
20 SMITH 800 SCOTT SCOTT ADAMS
30 ALLEN 1600 ADAMS BLAKE
30 BLAKE 2850 ALLEN ALLEN
30 JAMES 950 FORD BLAKE WARD
30 MARTIN 1250 KING JAMES TURNER
30 TURNER 1500 SMITH MARTIN ALLEN
30 WARD 1250 TURNER TURNER MARTIN
@@@Compare below output, observe the ename fields
~ above
DEPTNO ENAME
---------- ----------
10 CLARK
10 KING
10 MILLER
20 ADAMS
20 FORD
20 JONES
20 SCOTT
20 SMITH
30 ALLEN
30 BLAKE
30 JAMES
30 MARTIN
30 TURNER
30 WARD
ENAME DEPTNO SAL
---------- ---------- ----------
SMITH 20 800
JAMES 30 950
ADAMS 20 1100
WARD 30 1250
MARTIN 30 1250
MILLER 10 1300
TURNER 30 1500
ALLEN 30 1600
CLARK 10 2450
BLAKE 30 2850
JONES 20 2975
SCOTT 20 3000
FORD 20 3000
KING 10 5000
4@@@@Ordering ( Ranking ) functions: RANK, DENSE_RANK, ROW_NUMBER
Usage:
RANK() | DENSE_RANK | ROW_NUMBER OVER(ORDER BY field_name)
Where does this record fall, when the records are placed in a certain order?
Does not have to be order used in the query
All three functions return a number
Difference between functions is how they handle ties
These function are analytic only.
@@@
@@@<1> basic rank function
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@RANK() DENSE_RANK() ROW_NUMBER()
prompt ~ display current record ranking of order by ename.
prompt ~ When there are no ties, all three functions return
prompt ~ the same values.
SELECT deptno, ename, sal
, RANK() OVER(ORDER BY ename) f1
, DENSE_RANK() OVER(ORDER BY ename) f2
, ROW_NUMBER() OVER(ORDER BY ename) f3
FROM scott.emp
ORDER BY deptno, sal
/
prompt
prompt Observe the output, comparing with above
SELECT ename
FROM scott.emp
ORDER BY ename
/
SCOTT@ocm> @tmp.sql
@@@RANK() DENSE_RANK() ROW_NUMBER()
~ display current record ranking of order by ename.
~ When there are no ties, all three functions return
~ the same values.
DEPTNO ENAME SAL F1 F2 F3
---------- ---------- ---------- ---------- ---------- ----------
10 MILLER 1300 10 10 10
10 CLARK 2450 4 4 4
10 KING 5000 8 8 8
20 SMITH 800 12 12 12
20 ADAMS 1100 1 1 1
20 JONES 2975 7 7 7
20 FORD 3000 5 5 5
20 SCOTT 3000 11 11 11
30 JAMES 950 6 6 6
30 WARD 1250 14 14 14
30 MARTIN 1250 9 9 9
30 TURNER 1500 13 13 13
30 ALLEN 1600 2 2 2
30 BLAKE 2850 3 3 3
Observe the output, comparing with above
ENAME
----------
ADAMS
ALLEN
BLAKE
CLARK
FORD
JAMES
JONES
KING
MARTIN
MILLER
SCOTT
SMITH
TURNER
WARD
@@@
@@@<2> ranking functions with ties
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@RANK() DENSE_RANK() ROW_NUMBER()
prompt ~ display current record ranking of order by sal.
prompt ~ However, if you have the same salary, different function would
prompt ~ handle by different method.
prompt ~ If repeat 3 4 5 6 :
prompt ~ RANK() ===> 3 3 3 6 (repect the order)
prompt ~ DENSE_RANK() ===> 3 3 3 4 (densty)
prompt ~ ROW_NUMBER() ===> 3 4 5 6 (ranking by rownum)
SELECT deptno, ename, sal
, RANK() OVER(ORDER BY sal) f1
, DENSE_RANK() OVER(ORDER BY sal) f2
, ROW_NUMBER() OVER(ORDER BY sal) f3
FROM scott.emp
ORDER BY deptno, sal
/
prompt
prompt Observe the output, comparing with above
SELECT sal
FROM scott.emp
ORDER BY sal
/
SCOTT@ocm> @tmp.sql
@@@RANK() DENSE_RANK() ROW_NUMBER()
~ display current record ranking of order by sal.
~ However, if you have the same salary, different function would
~ handle by different method.
~ If repeat 3 4 5 6 :
~ RANK() ===> 3 3 3 6 (repect the order)
~ DENSE_RANK() ===> 3 3 3 4 (densty)
~ ROW_NUMBER() ===> 3 4 5 6 (ranking by rownum)
DEPTNO ENAME SAL F1 F2 F3
---------- ---------- ---------- ---------- ---------- ----------
10 MILLER 1300 6 5 6
10 CLARK 2450 9 8 9
10 KING 5000 14 12 14
20 SMITH 800 1 1 1
20 ADAMS 1100 3 3 3
20 JONES 2975 11 10 11
20 FORD 3000 12 11 13
20 SCOTT 3000 12 11 12
30 JAMES 950 2 2 2
30 WARD 1250 4 4 4
30 MARTIN 1250 4 4 5
30 TURNER 1500 7 6 7
30 ALLEN 1600 8 7 8
30 BLAKE 2850 10 9 10
Observe the output, comparing with above
SAL
----------
800
950
1100
1250
1250
1300
1500
1600
2450
2850
2975
3000
3000
5000
@@@
@@@<3> ROW_NUMBER() only display the row number
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@RANK() DENSE_RANK() ROW_NUMBER()
prompt ~ display current record ranking of order by sal.
prompt ~ However, if you have the same salary, different function would
prompt ~ handle by different method.
prompt ~ If repeat 3 4 5 6 :
prompt ~ RANK() ===> 3 3 3 6 (repect the order)
prompt ~ DENSE_RANK() ===> 3 3 3 4 (densty)
prompt ~ ROW_NUMBER() ===> 3 4 5 6 (ranking by rownum)
SELECT deptno, ename, sal
, ROW_NUMBER() OVER(ORDER BY sal DESC) f1
, ROW_NUMBER() OVER(PARTITION BY job ORDER BY sal) f2
FROM scott.emp
ORDER BY deptno, sal
/
prompt
prompt Observe the output, comparing with above
SELECT sal
FROM scott.emp
ORDER BY sal
/
SCOTT@ocm> @tmp.sql
@@@RANK() DENSE_RANK() ROW_NUMBER()
~ display current record ranking of order by sal.
~ However, if you have the same salary, different function would
~ handle by different method.
~ If repeat 3 4 5 6 :
~ RANK() ===> 3 3 3 6 (repect the order)
~ DENSE_RANK() ===> 3 3 3 4 (densty)
~ ROW_NUMBER() ===> 3 4 5 6 (ranking by rownum)
DEPTNO ENAME SAL F1 F2
---------- ---------- ---------- ---------- ----------
10 MILLER 1300 9 4
10 CLARK 2450 6 1
10 KING 5000 1 1
20 SMITH 800 14 1
20 ADAMS 1100 12 3
20 JONES 2975 4 3
20 FORD 3000 2 1
20 SCOTT 3000 3 2
30 JAMES 950 13 2
30 MARTIN 1250 10 1
30 WARD 1250 11 2
30 TURNER 1500 8 3
30 ALLEN 1600 7 4
30 BLAKE 2850 5 2
Observe the output, comparing with above
SAL
----------
800
950
1100
1250
1250
1300
1500
1600
2450
2850
2975
3000
3000
5000
@@@
@@@<4> Using ORDER BY change windows
@@@
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@SUM(),COUNT(),MAX(),MIN() with OVER()
prompt ~ use order by to change window
prompt ~ current record inculding upward records as the object of function
prompt ~ , when use order by only.
SELECT deptno, ename, sal
, SUM(sal) OVER(ORDER BY ename) sal
, COUNT(*) OVER(ORDER BY ename) cnt
, MIN(sal) OVER(ORDER BY ename) min
, MAX(sal) OVER(ORDER BY ename) max
FROM scott.emp
WHERE deptno=10
/
SELECT deptno, ename, sal
FROM scott.emp
WHERE deptno=10
/
SCOTT@ocm> @tmp.sql
@@@SUM(),COUNT(),MAX(),MIN() with OVER()
~ use order by to change window
~ current record inculding upward records as the object of function
~ , when use order by only.
DEPTNO ENAME SAL SAL CNT MIN MAX
---------- ---------- ---------- ---------- ---------- ---------- ----------
10 CLARK 2450 2450 1 2450 2450
10 KING 5000 7450 2 2450 5000
10 MILLER 1300 8750 3 1300 5000
DEPTNO ENAME SAL
---------- ---------- ----------
10 CLARK 2450
10 KING 5000
10 MILLER 1300
@@@Why?
This is the default behavior.
If you include an ORDER BY where one would not be necessary, Oracle assume it is
there for a reason.
1+3+5=9 and 3+5+1=9 the same.
Very powerful for running calculations, such as MTD:
SUM(sales) OVER(ORDER BY week_number) AS Month To Date
result like:
Week Number Sales Month To Date
----------- ------- --------------
1 11,000 11,000
2 15,000 26,000
3 12,000 38,000
4 16,000 54,000
@@@
@@@<5> Scope of Windowing, ROWS and RANGE
@@@
Example01, ROWS:
Demonstration of default windowing, with and without ORDER BY
^
| ^
--- | ^
-------- |
------------ meaning between unbounded preceding and current row
^ ^ ^
| | |
| | |
| | | meaning between unbounded preceding and unbounded following
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@Control Scope of Windowsing
prompt ~ unbounded preceding and unbounded following == all area
prompt ~ unbounded preceding and current row == order by xxx
SELECT deptno, ename, sal
, SUM(sal) OVER() sum1
, SUM(sal) OVER(ORDER BY ename) sum2
, SUM(sal) OVER(ORDER BY ename
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) sum3
, SUM(sal) OVER(ORDER BY ename
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum4
FROM scott.emp
WHERE deptno=10
/
SCOTT@ocm> @tmp.sql
@@@Control Scope of Windowsing
~ unbounded preceding and unbounded following == all area
~ unbounded preceding and current row == order by xxx
DEPTNO ENAME SAL SUM1 SUM2 SUM3 SUM4
---------- ---------- ---------- ---------- ---------- ---------- ----------
10 CLARK 2450 8750 2450 8750 2450
10 KING 5000 8750 7450 8750 7450
10 MILLER 1300 8750 8750 8750 8750
Example02, ROWS:
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@Control Scope of Windowing
prompt ~ the same up one, down one and current one, plus threes
prompt ~ equal to current sum value.
SELECT deptno, ename, sal
, SUM(sal) OVER(ORDER BY ename
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) sum1
FROM scott.emp
/
SELECT deptno, ename, sal
, SUM(sal) OVER(PARTITION BY deptno ORDER BY ename
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) sum2
FROM scott.emp
ORDER BY deptno
/
SCOTT@ocm> @tmp.sql
@@@Control Scope of Windowing
~ the same up one, down one and current one, plus threes
~ equal to current sum value.
DEPTNO ENAME SAL SUM1
---------- ---------- ---------- ----------
20 ADAMS 1100 2700 -- -----0+1100+1600=2700
30 ALLEN 1600 5550 --
30 BLAKE 2850 6900
10 CLARK 2450 8300
20 FORD 3000 6400
30 JAMES 950 6925 --
20 JONES 2975 8925 -- -----950+2975+5000=8925
10 KING 5000 9225 --
30 MARTIN 1250 7550
10 MILLER 1300 5550
20 SCOTT 3000 5100
20 SMITH 800 5300
30 TURNER 1500 3550 --
30 WARD 1250 2750 -- -----1500+1250+0=2750
DEPTNO ENAME SAL SUM2
---------- ---------- ---------- ----------
10 CLARK 2450 7450
10 KING 5000 8750
10 MILLER 1300 6300 -------
20 ADAMS 1100 4100
20 FORD 3000 7075
20 JONES 2975 8975
20 SCOTT 3000 6775
20 SMITH 800 3800 -------
30 ALLEN 1600 4450
30 BLAKE 2850 5400
30 JAMES 950 5050
30 MARTIN 1250 3700
30 TURNER 1500 4000
30 WARD 1250 2750 ------
Example03, RANGE:
like:
RANGE BETWEEN INTERVAL '10' DAY PRECEDING
AND INTERVAL '10' DAY FOLLOWING
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
prompt @@@Control Scope of Windowing
prompt ~ Range mean the value of operational field, could be date
prompt ~ number.
SELECT ename, sal
, COUNT(*) OVER(ORDER BY sal
RANGE BETWEEN 200 PRECEDING AND 200 FOLLOWING) emps_200_sal
FROM scott.emp
/
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd'
/
SELECT ename, hiredate
, COUNT(*) OVER(ORDER BY hiredate
RANGE BETWEEN INTERVAL '1' YEAR + INTERVAL '2' MONTH PRECEDING
AND INTERVAL '1' YEAR + INTERVAL '2' MONTH FOLLOWING)
emp_year_month
, COUNT(*) OVER(ORDER BY hiredate
RANGE BETWEEN INTERVAL '3' MONTH PRECEDING
AND INTERVAL '3' MONTH FOLLOWING)
emp_day
FROM scott.emp
/
SCOTT@ocm> @tmp.sql
@@@Control Scope of Windowing
~ Range mean the value of operational field, could be date
~ number.
ENAME SAL EMPS_200_SAL
---------- ---------- ------------
SMITH 800 2
JAMES 950 3
ADAMS 1100 5
WARD 1250 4
MARTIN 1250 4
MILLER 1300 5
TURNER 1500 3
ALLEN 1600 2
CLARK 2450 1
BLAKE 2850 4
JONES 2975 4
SCOTT 3000 4
FORD 3000 4
KING 5000 1
ENAME HIREDATE EMP_YEAR_MONTH EMP_DAY
---------- ---------- -------------- ----------
SMITH 1980-12-17 12 3
ALLEN 1981-02-20 12 5
WARD 1981-02-22 12 5
JONES 1981-04-02 12 5
BLAKE 1981-05-01 12 5
CLARK 1981-06-09 12 4
TURNER 1981-09-08 12 6
MARTIN 1981-09-28 12 5
KING 1981-11-17 12 6
JAMES 1981-12-03 12 6
FORD 1981-12-03 12 6
MILLER 1982-01-23 12 4
SCOTT 1987-04-19 2 2
ADAMS 1987-05-23 2 2
Example04, RANGE:
like
INTERVAL '7' HOUR
INTERVAL '7:45' HOUR TO MINUTE
INTERVAL '7:45' MINUTE TO SECOND
INTERVAL '7:45:00' HOUR TO SECOND
INTERVAL '3 7:45:00' DAY TO SECOND
INTERVAL '3 7:45' DAY TO MINUTE
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
set lines 120
prompt @@@Control Scope of Windowing
prompt ~ Range mean the value of operational field, could be date
prompt ~ number.
prompt ~ Here is two following, the same meaing way.
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd'
/
SELECT empno, ename, hiredate
, COUNT(*) OVER(ORDER BY hiredate
RANGE BETWEEN INTERVAL '1 3' DAY TO HOUR FOLLOWING
AND INTERVAL '1-6' YEAR TO MONTH FOLLOWING)
example
FROM scott.emp
/
col interv_1 for a20
col interv_2 for a20
col interv_3 for a20
col interv_4 for a20
SELECT INTERVAL '3' DAY AS interv_1
, INTERVAL '3 00:00:00' DAY TO SECOND AS interv_2
, INTERVAL '72' HOUR AS interv_3
, INTERVAL '4320' MINUTE AS interv_4
FROM dual
/
SELECT INTERVAL '7' SECOND AS interv_1
, INTERVAL '09:09:09' HOUR TO SECOND AS interv_2
, INTERVAL '06:09' HOUR TO MINUTE AS interv_3
, INTERVAL '04:20' MINUTE TO SECOND AS interv_4
FROM dual
/
SCOTT@ocm> @tmp.sql
@@@Control Scope of Windowing
~ Range mean the value of operational field, could be date
~ number.
~ Here is two following, the same meaing way.
EMPNO ENAME HIREDATE EXAMPLE
---------- ---------- ---------- ----------
7369 SMITH 1980-12-17 11
7499 ALLEN 1981-02-20 10
7521 WARD 1981-02-22 9
7566 JONES 1981-04-02 8
7698 BLAKE 1981-05-01 7
7782 CLARK 1981-06-09 6
7844 TURNER 1981-09-08 5
7654 MARTIN 1981-09-28 4
7839 KING 1981-11-17 3
7900 JAMES 1981-12-03 1
7902 FORD 1981-12-03 1
7934 MILLER 1982-01-23 0
7788 SCOTT 1987-04-19 1
7876 ADAMS 1987-05-23 0
INTERV_1 INTERV_2 INTERV_3 INTERV_4
-------------------- -------------------- -------------------- --------------------
+03 00:00:00 +03 00:00:00.000000 +03 00:00:00 +03 00:00:00
INTERV_1 INTERV_2 INTERV_3 INTERV_4
-------------------- -------------------- -------------------- --------------------
+00 00:00:07.000000 +00 09:09:09.000000 +00 06:09:00 +00 00:04:20.000000
Example05, RANGE:
SCOTT@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
set lines 120
prompt @@@Control Scope of Windowing
prompt ~ Range mean the value of operational field, could be date
prompt ~ number.
prompt ~ Here is two following, the same meaing way.
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd'
/
SELECT empno, ename, hiredate
, COUNT(*) OVER(ORDER BY hiredate
RANGE BETWEEN INTERVAL '2' MONTH FOLLOWING
AND INTERVAL '6' MONTH FOLLOWING)
emp_after_2_6
, COUNT(*) OVER(ORDER BY hiredate
RANGE BETWEEN CURRENT ROW
AND INTERVAL '6' MONTH FOLLOWING)
emp_after_cur_6
FROM scott.emp
/
SCOTT@ocm> @tmp.sql
@@@Control Scope of Windowing
~ Range mean the value of operational field, could be date
~ number.
~ Here is two following, the same meaing way.
EMPNO ENAME HIREDATE EMP_AFTER_2_6 EMP_AFTER_CUR_6
---------- ---------- ---------- ------------- ---------------
7369 SMITH 1980-12-17 5 6
7499 ALLEN 1981-02-20 2 5
7521 WARD 1981-02-22 2 4
7566 JONES 1981-04-02 3 5
7698 BLAKE 1981-05-01 2 4
7782 CLARK 1981-06-09 5 6
7844 TURNER 1981-09-08 4 6
7654 MARTIN 1981-09-28 3 5
7839 KING 1981-11-17 1 4
7900 JAMES 1981-12-03 0 3
7902 FORD 1981-12-03 0 3
7934 MILLER 1982-01-23 0 1
7788 SCOTT 1987-04-19 0 2
7876 ADAMS 1987-05-23 0 1
5@@@@Conprehensive Example
@@@
@@@<1> Example01 , partition by order by
@@@
OE@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
set lines 120
prompt @@@Application Example
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd'
/
SELECT customer_id
, TRUNC(order_date) AS order_date
, order_total
, LEAD( TRUNC(order_date) )
OVER( PARTITION BY customer_id ORDER BY order_date)
AS next_order_date
FROM oe.orders
WHERE customer_id IN ( 103, 105 )
ORDER BY 1,2
/
OE@ocm> @tmp.sql
@@@Application Example
CUSTOMER_ID ORDER_DATE ORDER_TOTAL NEXT_ORDER
----------- ---------- ----------- ----------
103 1997-03-30 310 1998-09-01
103 1998-09-01 13550 1999-09-14
103 1999-09-14 78 1999-10-03
103 1999-10-03 6653.4
105 1999-03-21 1926.6 1999-09-01
105 1999-09-01 22150.1 2000-01-09
105 2000-01-09 7826 2000-01-27
105 2000-01-27 29473.8
@@@
@@@<2> Example02, compare this_plus_next and sum_this_next
@@@
OE@ocm> !cat tmp.sql
set echo off
set feedback off
set verify off
set autot off
set lines 120
prompt @@@Application Example
prompt ~ NULL + order_total = NULL ?
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd'
/
SELECT customer_id
, TRUNC(order_date) AS order_date
, order_total
, LEAD( TRUNC(order_date) ) OVER
(PARTITION BY customer_id ORDER BY order_date) AS next_order
, order_total + LEAD(order_total) OVER
(PARTITION BY customer_id ORDER BY order_date) AS this_plus_next
, SUM(order_total) OVER
(PARTITION BY customer_id ORDER BY order_date
ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS sum_this_next
FROM oe.orders
WHERE customer_id IN ( 103, 105 )
ORDER BY 1,2
/
OE@ocm> @tmp.sql
@@@Application Example
~ NULL + order_total = NULL ?
CUSTOMER_ID ORDER_DATE ORDER_TOTAL NEXT_ORDER THIS_PLUS_NEXT SUM_THIS_NEXT
----------- ---------- ----------- ---------- -------------- -------------
103 1997-03-30 310 1998-09-01 13860 13860
103 1998-09-01 13550 1999-09-14 13628 13628
103 1999-09-14 78 1999-10-03 6731.4 6731.4
103 1999-10-03 6653.4 6653.4
105 1999-03-21 1926.6 1999-09-01 24076.7 24076.7
105 1999-09-01 22150.1 2000-01-09 29976.1 29976.1
105 2000-01-09 7826 2000-01-27 37299.8 37299.8
105 2000-01-27 29473.8 29473.8
@@@
@@@<3> Shutcut for Scope of Windowing
@@@
ROWS UNBOUNDED PRECEDING == ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ROWS 10 PRECEDING == ROWS BETWEEN 10 PRECEDING AND CURRENT ROW
ROWS CURRENT ROW == ROWS BETWEEN CURRENT ROW AND CURRENT ROW