WINDOW clause Oracle SQL oralenoir

The WINDOW Clause in Oracle SQL

Background

One of the best resources for anyone who’s into oracle tech is this site called the devgym. It contains games, quizzes, classes and workouts all designed to help build developers’ Oracle Database, SQL and PL/SQL muscles for FREE! I visit this site often to play the speed SQL and SQUIZL games and sometimes the occasional other random quiz. The exercises are short, fun and help me learn a lot.

Recently, one of the SQUIZLs was tough and I only managed to figure it out after 5 attempts.

As a result, I learned about the WINDOW clause. It’s all fair because the whole point of visiting the devgym is to learn new things. In this post I will talk about the WINDOW clause and see some examples of its use.

Introduction

The window clause (introduced in Oracle database 21c) can be used within a window function to define a reusable window that can be used in the over clause. In addition, it helps eliminate redundant code when performing multiple analytics on the same data subset.

If you’ve ever written several analytic functions in the same query, you’ve probably repeated the same PARTITION BY and ORDER BY clauses over and over. The WINDOW clause lets you define that specification once, give it a name, and reuse it.

In order to see how this works let’s use it to solve a fictional problem.

Example

Consider the oracle sample HR schema. Imagine HR wants to analyze salaries and hiring trends over time. They need a report showing each employee’s salary in order of their hire date alongside two departmental metrics:

  1. A running total of employee salaries.
  2. A moving average of employee salaries.

for departments 30, 60 and 100

Pre-21c Syntax

Prior to Oracle 21c, we would have had to repeat the identical PARTITION BY and ORDER BY specifications inside every OVER() clause as shown below.

SELECT
     department_id
    ,first_name
    ,hire_date
    ,salary
    ,SUM(salary)       OVER (PARTITION BY department_id ORDER BY hire_date)    AS running_total_sal_dept
    ,ROUND(AVG(salary) OVER (PARTITION BY department_id ORDER BY hire_date))   AS running_avg_sal_dept
FROM
    employees
WHERE department_id IN (30,60,100)
order by 1, 3;

   DEPARTMENT_ID FIRST_NAME     HIRE_DATE       SALARY    RUNNING_TOTAL_SAL_DEPT    RUNNING_AVG_SAL_DEPT
________________ ______________ ____________ _________ _________________________ _______________________
              30 Den            07-DEC-02        11000                     11000                   11000
              30 Alexander      18-MAY-03         3100                     14100                    7050
              30 Sigal          24-JUL-05         2800                     16900                    5633
              30 Shelli         24-DEC-05         2900                     19800                    4950
              30 Guy            15-NOV-06         2600                     22400                    4480
              30 Karen          10-AUG-07         2500                     24900                    4150
              60 David          25-JUN-05         4800                      4800                    4800
              60 Alexander      03-JAN-06         9000                     13800                    6900
              60 Valli          05-FEB-06         4800                     18600                    6200
              60 Diana          07-FEB-07         4200                     22800                    5700
              60 Bruce          21-MAY-07         6000                     28800                    5760
             100 Daniel         16-AUG-02         9000                      9000                    9000
             100 Nancy          17-AUG-02        12008                     21008                   10504
             100 John           28-SEP-05         8200                     29208                    9736
             100 Ismael         30-SEP-05         7700                     36908                    9227
             100 Jose Manuel    07-MAR-06         7800                     44708                    8942
             100 Luis           07-DEC-07         6900                     51608                    8601

17 rows selected.

21c + Syntax

The WINDOW clause allows us to define a named window at the bottom of the query and just re-use that name in the over clause, seamlessly specifying our windows. The above query could be re-written as :

SELECT 
     department_id
    ,first_name
    ,hire_date
    ,salary
    ,SUM(salary)        OVER w   AS running_total_sal_dept
    ,ROUND(AVG(salary)  OVER w)  AS running_avg_sal_dept
FROM employees
WHERE department_id IN (30,60,100)
WINDOW w AS (PARTITION BY department_id ORDER BY hire_date)
order by 1, 3
;
   DEPARTMENT_ID FIRST_NAME     HIRE_DATE       SALARY    RUNNING_TOTAL_SAL_DEPT    RUNNING_AVG_SAL_DEPT
________________ ______________ ____________ _________ _________________________ _______________________
              30 Den            07-DEC-02        11000                     11000                   11000
              30 Alexander      18-MAY-03         3100                     14100                    7050
              30 Sigal          24-JUL-05         2800                     16900                    5633
              30 Shelli         24-DEC-05         2900                     19800                    4950
              30 Guy            15-NOV-06         2600                     22400                    4480
              30 Karen          10-AUG-07         2500                     24900                    4150
              60 David          25-JUN-05         4800                      4800                    4800
              60 Alexander      03-JAN-06         9000                     13800                    6900
              60 Valli          05-FEB-06         4800                     18600                    6200
              60 Diana          07-FEB-07         4200                     22800                    5700
              60 Bruce          21-MAY-07         6000                     28800                    5760
             100 Daniel         16-AUG-02         9000                      9000                    9000
             100 Nancy          17-AUG-02        12008                     21008                   10504
             100 John           28-SEP-05         8200                     29208                    9736
             100 Ismael         30-SEP-05         7700                     36908                    9227
             100 Jose Manuel    07-MAR-06         7800                     44708                    8942
             100 Luis           07-DEC-07         6900                     51608                    8601

17 rows selected.

As shown above, we get the same results with cleaner syntax. We define the window once and can re-use it for multiple different computations.

Note: The WINDOW clause must come before the ORDER BY clause.

Multiple Windows

Multiple different windows can also be defined in the same query. For example; Imagine the request changed from wanting to see the average salary for all employees in the department to Wanting to see a moving average of the last 3 hired employees’ salaries in that department. This can be written in Oracle SQL as below:

SELECT
     department_id
    ,first_name
    ,hire_date
    ,salary
    ,SUM(salary)  OVER w1  AS running_total_sal_dept
    ,AVG(salary)  OVER w2  AS running_avg_sal_dept
FROM employees
WHERE department_id IN (30,60,100)
WINDOW w1 AS (PARTITION BY department_id ORDER BY hire_date),
       w2 AS (PARTITION BY department_id ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW )
order by 1, 3
;
DEPARTMENT_ID FIRST_NAME           HIRE_DATE     SALARY RUNNING_TOTAL_SAL_DEPT RUNNING_AVG_SAL_DEPT
------------- -------------------- --------- ---------- ---------------------- --------------------
           30 Den                  07-DEC-02      11000                  11000                11000
           30 Alexander            18-MAY-03       3100                  14100                 7050
           30 Sigal                24-JUL-05       2800                  16900           5633.33333
           30 Shelli               24-DEC-05       2900                  19800           2933.33333
           30 Guy                  15-NOV-06       2600                  22400           2766.66667
           30 Karen                10-AUG-07       2500                  24900           2666.66667
           60 David                25-JUN-05       4800                   4800                 4800
           60 Alexander            03-JAN-06       9000                  13800                 6900
           60 Valli                05-FEB-06       4800                  18600                 6200
           60 Diana                07-FEB-07       4200                  22800                 6000
           60 Bruce                21-MAY-07       6000                  28800                 5000

DEPARTMENT_ID FIRST_NAME           HIRE_DATE     SALARY RUNNING_TOTAL_SAL_DEPT RUNNING_AVG_SAL_DEPT
------------- -------------------- --------- ---------- ---------------------- --------------------
          100 Daniel               16-AUG-02       9000                   9000                 9000
          100 Nancy                17-AUG-02      12008                  21008                10504
          100 John                 28-SEP-05       8200                  29208                 9736
          100 Ismael               30-SEP-05       7700                  36908           9302.66667
          100 Jose Manuel          07-MAR-06       7800                  44708                 7900
          100 Luis                 07-DEC-07       6900                  51608           7466.66667

17 rows selected.

Notice that the windows are defined differently as w1 and w2 , with different parameters.

Note: When the window name is specified with a windowing clause(such as w2 above), it can only be referenced directly (without parentheses). More info.

Extending the Windows

Oracle allows one named window to serve as a base definition. We can add Additional clauses such as ROWS BETWEEN in individual analytic functions without repeating the PARTITION BY and ORDER BY clauses.

Consider the query above, it could be re-written as:

SELECT
     department_id
    ,first_name
    ,hire_date
    ,salary
    ,SUM(salary)  OVER w1                                             AS running_total_sal_dept
    ,AVG(salary)  OVER (w1 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)  AS running_avg_sal_dept
FROM employees
WHERE department_id IN (30,60,100)
WINDOW w1 AS (PARTITION BY department_id ORDER BY hire_date)
order by 1, 3
/

DEPARTMENT_ID FIRST_NAME           HIRE_DATE     SALARY RUNNING_TOTAL_SAL_DEPT RUNNING_AVG_SAL_DEPT
------------- -------------------- --------- ---------- ---------------------- --------------------
           30 Den                  07-DEC-02      11000                  11000                11000
           30 Alexander            18-MAY-03       3100                  14100                 7050
           30 Sigal                24-JUL-05       2800                  16900           5633.33333
           30 Shelli               24-DEC-05       2900                  19800           2933.33333
           30 Guy                  15-NOV-06       2600                  22400           2766.66667
           30 Karen                10-AUG-07       2500                  24900           2666.66667
           60 David                25-JUN-05       4800                   4800                 4800
           60 Alexander            03-JAN-06       9000                  13800                 6900
           60 Valli                05-FEB-06       4800                  18600                 6200
           60 Diana                07-FEB-07       4200                  22800                 6000
           60 Bruce                21-MAY-07       6000                  28800                 5000

DEPARTMENT_ID FIRST_NAME           HIRE_DATE     SALARY RUNNING_TOTAL_SAL_DEPT RUNNING_AVG_SAL_DEPT
------------- -------------------- --------- ---------- ---------------------- --------------------
          100 Daniel               16-AUG-02       9000                   9000                 9000
          100 Nancy                17-AUG-02      12008                  21008                10504
          100 John                 28-SEP-05       8200                  29208                 9736
          100 Ismael               30-SEP-05       7700                  36908           9302.66667
          100 Jose Manuel          07-MAR-06       7800                  44708                 7900
          100 Luis                 07-DEC-07       6900                  51608           7466.66667

17 rows selected.

Notice how the window w1 is defined once but extended in the second analytic function call adding parameters to the base specified window.

Conclusion

The window clause allows us to define a base window at the bottom for reuse in different analytic function calls within a query. While it doesn’t introduce new analytic capabilities, it makes complex queries significantly easier to read and maintain. If you regularly work with analytics, this is a great feature to add to your toolbox. Also, if you’re new to Analytics and want to learn all about it, check out the Analytic SQL for Developers course to get up to speed. Oh and did I mention it’s FREE?

Thanks to everyone at the Devgym for helping me sharpen my SQL skills.

Cheers, Harris.

my devgym profile
More posts on SQL

Leave a Comment

Your email address will not be published. Required fields are marked *