Technical writings of Shkrt

Home

Tags

Another post on PostgreSQL window functions

There are plenty of posts on web, describing PostgreSQL window functions, and I decided to make another one, to share my view and my approach to understanding them.

Queries using window functions usually look like this one:

SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;

  depname  | empno | salary |          avg
-----------+-------+--------+-----------------------
 develop   |    11 |   5200 | 5020.0000000000000000
 develop   |     7 |   4200 | 5020.0000000000000000
 develop   |     9 |   4500 | 5020.0000000000000000
 develop   |     8 |   6000 | 5020.0000000000000000
 develop   |    10 |   5200 | 5020.0000000000000000
 personnel |     5 |   3500 | 3700.0000000000000000
 personnel |     2 |   3900 | 3700.0000000000000000
 sales     |     3 |   4800 | 4866.6666666666666667
 sales     |     1 |   5000 | 4866.6666666666666667
 sales     |     4 |   4800 | 4866.6666666666666667
(10 rows)

copied from official documentation

To follow along, you can use the following setup:

createdb window_test --host=localhost --user=postgres

create table empsalary(
depname character varying,
empno integer,
salary integer);

insert into empsalary(depname, empno, salary) values
('develop', 11, 5200),
('develop', 7, 4200),
('develop', 9, 4500),
('develop', 8, 6000),
('develop', 10, 5200),
('personnel', 5, 3500),
('personnel', 2, 3900),
('sales', 3, 4800),
('sales', 1, 5000),
('sales', 4, 4800);

Window function part is starting from avg(salary). The window functions introduce the concepts of window frames In the above example, avg(salary) is the function, that is being applied to every row in the output. The frame is representing the relation that is available to avg(salary) on the current row. In the example, the frame is defined by PARTITION BY depname part. The whole relation available to window function initially, after filtering clauses, is being partitioned by depname column’s value. It results in

(5200 + 4200 + 4500 + 6000 + 5200)/5 = 5020.0

And we get the value of 5020.0 in every row related to develop depname. The same applies to personnel and sales depnames.

The frame is defined either by partition by or order by clauses, otherwise, the defaults are applied.

SELECT depname, empno, salary, avg(salary) OVER (ORDER BY empno) FROM empsalary;

  depname   empno  salary           avg
═══════════╪═══════╪════════╪═══════════════════════
 sales          1    5000  5000.0000000000000000
 personnel      2    3900  4450.0000000000000000
 sales          3    4800  4566.6666666666666667
 sales          4    4800  4625.0000000000000000
 personnel      5    3500  4400.0000000000000000
 develop        7    4200  4366.6666666666666667
 develop        8    6000  4600.0000000000000000
 develop        9    4500  4587.5000000000000000
 develop       10    5200  4655.5555555555555556
 develop       11    5200  4710.0000000000000000
(10 rows)

Here we see the illustration of frame scoping to all the rows up to the current row. We have ordered the relation by empno column value. On the first row, avg(salary) takes into account only first row, on the second row - first row + second row, and so on.

SELECT depname, empno, salary, avg(salary) OVER () FROM empsalary;
  depname   empno  salary           avg
═══════════╪═══════╪════════╪═══════════════════════
 develop       11    5200  4710.0000000000000000
 develop        7    4200  4710.0000000000000000
 develop        9    4500  4710.0000000000000000
 develop        8    6000  4710.0000000000000000
 develop       10    5200  4710.0000000000000000
 personnel      5    3500  4710.0000000000000000
 personnel      2    3900  4710.0000000000000000
 sales          3    4800  4710.0000000000000000
 sales          1    5000  4710.0000000000000000
 sales          4    4800  4710.0000000000000000

avg is now applied to the whole table.

The avg function used in the previous example is an aggregation function. Window functions can be used with aggregate functions, or with so-called built-in window functions. List of built-in window functions can be viewed here

postgres@window_test=#  SELECT depname, empno, salary, rank() OVER (partition by depname order by salary desc) FROM empsalary;
  depname   empno  salary  rank
═══════════╪═══════╪════════╪══════
 develop        8    6000     1
 develop       10    5200     2
 develop       11    5200     2
 develop        9    4500     4
 develop        7    4200     5
 personnel      2    3900     1
 personnel      5    3500     2
 sales          1    5000     1
 sales          3    4800     2
 sales          4    4800     2
(10 rows)

Another thing to note is that window functions are evaluated after filtering and grouping clauses, therefore we cannot use grouping and filtering over windowing results.

Suggested reading:

List of window functions

Window functions

Syntax

Window function processing

[ postgresql  ]