Top Banner
Copyright © 2017 Oracle and/or its affiliates. All rights reserved. SQL window functions for MySQL Dag H. Wanvik Senior database engineer, Oracle
46

SQL window functions for MySQL

Mar 22, 2017

Download

Software

Dag H. Wanvik
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: SQL window functions for MySQL

Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SQL window functions for MySQL

Dag H. WanvikSenior database engineer,Oracle

Page 2: SQL window functions for MySQL

2Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Safe Harbor Statement

The following is intended to outline our general product direction. It is intended forinformation purposes only, and may not be incorporated into any contract. It is nota commitment to deliver any material, code, or functionality, and should not berelied upon in making purchasing decisions. The development, release, andtiming of any features or functionality described for Oracle’s products remains atthe sole discretion of Oracle.

Page 3: SQL window functions for MySQL

3Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

AgendaQuick intro to window functions

Types of window functions

The window specification

Evaluation and optimizations

More on non-aggregate wfs

Implicit and explicit windows

Q & A

1

2

3

4

5

6

7

Page 4: SQL window functions for MySQL

4Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Window functions: what are they?● A window function performs a calculation across a set of rows that are

related to the current row, similar to what can be done with an aggregatefunction.

● But unlike traditional aggregate functions, a window function does notcause rows to become grouped into a single output row.

● So, similar to normal function, but can access values of other rows “inthe vicinity” of the current row

Page 5: SQL window functions for MySQL

5Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id, salary,

SUM(salary) OVER (PARTITION BY department_id)

AS department_total

FROM employee

ORDER BY department_id, name;

Window function example, no frame

The OVER keywordsignals a window function

Page 6: SQL window functions for MySQL

6Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Partitions: 10, 20, 30

+---------+---------------+--------+------------------+| name | department_id | salary | department_total |+---------+---------------+--------+------------------+| Newt | NULL | 75000 | 75000 || Dag | 10 | NULL | 370000 || Ed | 10 | 100000 | 370000 || Fred | 10 | 60000 | 370000 || Jon | 10 | 60000 | 370000 || Michael | 10 | 70000 | 370000 || Newt | 10 | 80000 | 370000 || Lebedev | 20 | 65000 | 130000 || Pete | 20 | 65000 | 130000 || Jeff | 30 | 300000 | 370000 || Will | 30 | 70000 | 370000 |+---------+---------------+--------+------------------+

Partition == disjointset of rows in result set

Here: all rows in partition are peers

Page 7: SQL window functions for MySQL

7Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id, salary, SUM(salary) AS department_total FROM employee GROUP BY department_id ORDER BY department_id, name;

ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mysql.employee.name' which is not functionally dependenton columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

With GROUP BY

Page 8: SQL window functions for MySQL

8Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT /* name, */ department_id, /* salary,*/ SUM(salary) AS department_total FROM employee GROUP BY department_id ORDER BY department_id /*, name */;

+---------------+------------------+| department_id | department_total |+---------------+------------------+| NULL | 75000 || 10 | 370000 || 20 | 130000 || 30 | 370000 |+---------------+------------------+

With GROUP BY

Page 9: SQL window functions for MySQL

9Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id, salary,

SUM (salary) OVER (PARTITION BY department_id

ORDER BY name

ROWS 2 PRECEDING) total

FROM employee

ORDER BY department_id, name;

Window function example, frame

Page 10: SQL window functions for MySQL

10Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Partitions: 10, 20, 30

ORDER BY name

within each

partition

+---------+---------------+--------+--------+| name | department_id | salary | total |+---------+---------------+--------+--------+| Newt | NULL | 75000 | 75000 || Dag | 10 | NULL | NULL || Ed | 10 | 100000 | 100000 || Fred | 10 | 60000 | 160000 || Jon | 10 | 60000 | 220000 || Michael | 10 | 70000 | 190000 || Newt | 10 | 80000 | 210000 || Lebedev | 20 | 65000 | 65000 || Pete | 20 | 65000 | 130000 || Jeff | 30 | 300000 | 300000 || Will | 30 | 70000 | 370000 |+---------+---------------+--------+--------+

moving window frame:

SUM (salary)...ROWS 2 PRECEDING

a frame is a subset of apartition

Page 11: SQL window functions for MySQL

11Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id, salary, AVG(salary) OVER w AS `avg`, salary - AVG(salary) OVER w AS diff FROM employee WINDOW w as (PARTITION BY department_id) ORDER BY diff DESC;

Window function example

i.e. find the employees with the largest difference between their wageand that of the department average

Note: explicit window definition of “w”

Page 12: SQL window functions for MySQL

12Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Partitions: 10, 20, 30

+---------+---------------+--------+-----------+------------+| name | department_id | salary | avg | diff |+---------+---------------+--------+-----------+------------+| Jeff | 30 | 300000 | 185000.00 | 115000.00 || Ed | 10 | 100000 | 74000.00 | 26000.00 || Newt | 10 | 80000 | 74000.00 | 6000.00 || Newt | NULL | 75000 | 75000.00 | 0.00 || Pete | 20 | 65000 | 65000.00 | 0.00 || Lebedev | 20 | 65000 | 65000.00 | 0.00 || Michael | 10 | 70000 | 74000.00 | -4000.00 || Jon | 10 | 60000 | 74000.00 | -14000.00 || Fred | 10 | 60000 | 74000.00 | -14000.00 || Will | 30 | 70000 | 185000.00 | -115000.00 || Dag | 10 | NULL | 74000.00 | NULL |+---------+---------------+--------+-----------+------------+

Page 13: SQL window functions for MySQL

13Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Types of window functions

● Aggregates

● Ranking

● Analytical

COUNT, SUM, AVG + more to come

RANK, DENSE_RANK, PERCENT_RANK,

CUME_DIST, ROW_NUMBER

NTILE, LEAD, LAG, NTH, FIRST_VALUE,

LAST_VALUE

Blue ones use frames, all obey partitions

Page 14: SQL window functions for MySQL

14Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Anatomy of a window specificationwindow specification ::=

[ existing window name ]

[PARTITION BY expr-1, ... ]

[ORDER BY expr-1, ... [DESC] ]

[ frame clause ]

Page 15: SQL window functions for MySQL

15Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

frame clause boundpartition

CURRENT ROW

UNBOUNDEDPRECEDING

UNBOUNDEDFOLLOWING

n PRECEDING

m PRECEDING

Page 16: SQL window functions for MySQL

16Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

frame clause frame clause ::= { ROWS | RANGE } { start | between }

start ::= { CURRENT ROW | UNBOUNDED PRECEDING | n PRECEDING}

between ::= BETWEEN bound-1 AND bound-2

bound ::= start | UNBOUNDED FOLLOWING | n FOLLOWING

● “start” form implies upper is CURRENT ROW (or its peer iff RANGE)● An empty OVER () specification, says that all rows in the partition are

peers and included in frame.● An ORDER BY without frame implies frame:

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

● Limitation: frame exclusion not supported

Page 17: SQL window functions for MySQL

17Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

frame clause● ROWS or RANGE (GROUPS unit not supported)● ROWS: bounds are physical row offsets● RANGE: logical offset based on current row's valueEx: RANGE BETWEEN INTERVAL 1 WEEK PRECEDING AND CURRENT ROW

● RANGE requires ORDER BY on one numeric expression● Limitation: bounds must be static

Page 18: SQL window functions for MySQL

18Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT dat, amount, SUM(amount) OVER w FROM payments WINDOW w AS (ORDER BY dat RANGE BETWEEN INTERVAL 1 WEEK PRECEDING AND CURRENT ROW) ORDER BY dat;

RANGE frame example

i.e. find the sum of payments within the last 7 days

Page 19: SQL window functions for MySQL

19Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

RANGE frame example

+------------+--------+--------+| dat | amount | sum |+------------+--------+--------+| 2017-01-01 | 100.50 | 300.50 || 2017-01-01 | 200.00 | 300.50 || 2017-01-02 | 200.00 | 500.50 || 2017-01-03 | 200.00 | 700.50 || 2017-01-05 | 200.00 | 900.50 || 2017-01-10 | 200.00 | 700.00 || 2017-01-10 | 100.00 | 700.00 || 2017-01-11 | 200.00 | 700.00 |+------------+--------+--------+

SELECT dat, amount, SUM(amount) OVER w AS `sum` FROM payments WINDOW w AS (ORDER BY dat RANGE BETWEEN INTERVAL 1 WEEK PRECEDING AND CURRENT ROW) ORDER BY dat;

Current row's date is the 10th, so first row in range is the 3rd .Frame cardinality is 4 due to peer in next row.

For Jan 5, the frame cardinality is 5, and sum is 900.50.

Page 20: SQL window functions for MySQL

20Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

When are they evaluated?● after GROUP BY/ HAVING● before final ORDER BY, DISTINCT, LIMIT● you can have several window functions and several different

windows● To filter on wf's value, use a subquery, e.g.

SELECT * FROM (SELECT SUM(salary) OVER (PARTITION BY department_id) `sum` FROM employee) AS s WHERE `sum` < 100000;+-------+| sum |+-------+| 75000 |+-------+

Page 21: SQL window functions for MySQL

21Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Logical flow

JOIN GROUPBY

WINDOW1

WINDOWn

ORDER BY/DISTINCT/

LIMIT

Sort forPARTITION BY

and ORDER BY

● Tmp file between each windowing step(in-mem if result set can fit †)

● Streamable wfs vs buffered● Depends on wf and frame● Buffered: re-read rows ● O(rows * frame size) optimize● Move frame for SUM 1 row: invert by

subtraction, add new row.

† cf. variables tmp_table_size, max_heap_table_size

Page 22: SQL window functions for MySQL

22Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Streamable evaluationSELECT name, department_id, salary, SUM(salary) OVER (PARTITION BY department_id ORDER BY name ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `sum` FROM employee;

+---------+---------------+--------+--------+| name | department_id | salary | sum |+---------+---------------+--------+--------+| Newt | NULL | 75000 | 75000 || Dag | 10 | NULL | NULL || Ed | 10 | 100000 | 100000 | Just accumulate as we see rows| Fred | 10 | 60000 | 160000 || Jon | 10 | 60000 | 220000 || Michael | 10 | 70000 | 290000 || Newt | 10 | 80000 | 370000 || Lebedev | 20 | 65000 | 65000 || Pete | 20 | 65000 | 130000 || Jeff | 30 | 300000 | 300000 || Will | 30 | 70000 | 370000 |+---------+---------------+--------+--------+

Page 23: SQL window functions for MySQL

23Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Logical flow

JOIN GROUPBY

WINDOW1

WINDOWn

ORDER BY/DISTINCT/

LIMIT

Row addressable

buffer

in-mem: overflows to disk

Permits re-readingrows when framemoves

Page 24: SQL window functions for MySQL

24Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Non-streamable evaluationSELECT name, department_id, salary, SUM(salary) OVER (PARTITION BY department_id ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS `sum` FROM employee;

+---------+---------------+--------+--------+| name | department_id | salary | sum |+---------+---------------+--------+--------+| Newt | NULL | 75000 | 75000 || Dag | 10 | NULL | NULL || Ed | 10 | 100000 | 100000 || Fred | 10 | 60000 | 160000 || Jon | 10 | 60000 | 220000 || Michael | 10 | 70000 | 190000 || Newt | 10 | 80000 | 210000 || Lebedev | 20 | 65000 | 65000 || Pete | 20 | 65000 | 130000 || Jeff | 30 | 300000 | 300000 || Will | 30 | 70000 | 370000 |+---------+---------------+--------+--------+

When eval'ing Michael, subtract Ed'scontribution, add Michael

or just evaluate entire frame over again(non-optimized). In both cases we needre-visit rows.

Page 25: SQL window functions for MySQL

25Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Optimizations● Accumulate frames by inversion (discussed; done) ¹● Avoid materializing each step in temporary file if possible (done)● Sort only once for windows with the same PARTITION/ORDER BY

requirements (done)● Sort only once for windows with subset relation on above (not yet)● Eliminate sorts by using indexes (not yet)

¹not done by default for floats due to possible under/-overflow errors, but can be enabled using a variable.Enabling it is crucial for performance for large frames due to the combinatorial hardness.

Page 26: SQL window functions for MySQL

26Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Explain, last query EXPLAIN FORMAT=JSON SELECT name, department_id, salary, SUM(salary) OVER (PARTITION BY department_id ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS `sum` FROM employee;

... "windows": [ {"name": "<unnamed window>", "evalated as #": 1, "using sorting": true, "using temporary file": false, "uses frame buffer": true, "optimized frame evaluation": true, "functions used": [ "sum" ]} ], "buffer_result": { :

Page 27: SQL window functions for MySQL

27Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

More on non-aggregate wfs● RANK, DENSE_RANK, PERCENT_RANK, CUME_DIST, ROW_NUMBER● LEAD, LAG, FIRST_VALUE, LAST_VALUE, NTH_VALUE

Page 28: SQL window functions for MySQL

28Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

RANKSELECT name, department_id AS dept, salary, RANK() OVER w AS `rank` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+| name | dept | salary | rank |+---------+------+--------+------+| Newt | NULL | 75000 | 1 || Ed | 10 | 100000 | 1 || Newt | 10 | 80000 | 2 || Fred | 10 | 70000 | 3 || Michael | 10 | 70000 | 3 || Jon | 10 | 60000 | 5 || Dag | 10 | NULL | 6 || Pete | 20 | 65000 | 1 || Lebedev | 20 | 65000 | 1 || Jeff | 30 | 300000 | 1 || Will | 30 | 70000 | 2 |+---------+------+--------+------+

Page 29: SQL window functions for MySQL

29Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

DENSE_RANKSELECT name, department_id AS dept, salary, RANK() OVER w AS `rank`, DENSE_RANK() OVER w AS dense FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+-------+| name | dept | salary | rank | dense |+---------+------+--------+------+-------+| Newt | NULL | 75000 | 1 | 1 || Ed | 10 | 100000 | 1 | 1 || Newt | 10 | 80000 | 2 | 2 || Fred | 10 | 70000 | 3 | 3 || Michael | 10 | 70000 | 3 | 3 || Jon | 10 | 60000 | 5 | 4 || Dag | 10 | NULL | 6 | 5 || Pete | 20 | 65000 | 1 | 1 || Lebedev | 20 | 65000 | 1 | 1 || Jeff | 30 | 300000 | 1 | 1 || Will | 30 | 70000 | 2 | 2 |+---------+------+--------+------+-------+

DENSE_RANK doesn't skip

Page 30: SQL window functions for MySQL

30Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

ROW_NUMBERSELECT name, department_id AS dept, salary, RANK() OVER w AS `rank`, DENSE_RANK() OVER w AS dense, ROW_NUMBER() OVER w AS `#` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+-------+---+| name | dept | salary | rank | dense | # |+---------+------+--------+------+-------+---+| Newt | NULL | 75000 | 1 | 1 | 1 || Ed | 10 | 100000 | 1 | 1 | 1 || Newt | 10 | 80000 | 2 | 2 | 2 || Fred | 10 | 70000 | 3 | 3 | 3 || Michael | 10 | 70000 | 3 | 3 | 4 || Jon | 10 | 60000 | 5 | 4 | 5 || Dag | 10 | NULL | 6 | 5 | 6 || Pete | 20 | 65000 | 1 | 1 | 1 || Lebedev | 20 | 65000 | 1 | 1 | 2 || Jeff | 30 | 300000 | 1 | 1 | 1 || Will | 30 | 70000 | 2 | 2 | 2 |+---------+------+--------+------+-------+---+

Page 31: SQL window functions for MySQL

31Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

CUME_DISTSELECT name, department_id AS dept, salary, RANK() OVER w AS `rank`, DENSE_RANK() OVER w AS dense, ROW_NUMBER() OVER w AS `#`, CUME_DIST() OVER w AS cume FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+-------+---+---------------------+| name | dept | salary | rank | dense | # | cume |+---------+------+--------+------+-------+---+---------------------+| Newt | NULL | 75000 | 1 | 2 | 1 | 1 || Ed | 10 | 100000 | 1 | 2 | 1 | 0.16666666666666666 || Newt | 10 | 80000 | 2 | 3 | 2 | 0.3333333333333333 || Fred | 10 | 70000 | 3 | 4 | 3 | 0.6666666666666666 || Michael | 10 | 70000 | 3 | 4 | 4 | 0.6666666666666666 || Jon | 10 | 60000 | 5 | 5 | 5 | 0.8333333333333334 || Dag | 10 | NULL | 6 | 6 | 6 | 1 || Pete | 20 | 65000 | 1 | 2 | 1 | 1 || Lebedev | 20 | 65000 | 1 | 2 | 2 | 1 || Jeff | 30 | 300000 | 1 | 2 | 1 | 0.5 || Will | 30 | 70000 | 2 | 3 | 2 | 1 |+---------+------+--------+------+-------+---+---------------------+

Cumulative distribution

“For a row R, if we assume ascending ordering, CUME_DIST of R is the number of rows with values <= the value of R, divided by the number of rows evaluated in the partition. “

Page 32: SQL window functions for MySQL

32Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

PERCENT_RANKSELECT name, department_id AS dept, salary, RANK() OVER w AS `rank`, DENSE_RANK() OVER w AS dense, ROW_NUMBER() OVER w AS `#`, CUME_DIST() OVER w AS cume, PERCENT_RANK() OVER w AS p_r FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+-------+---+---------------------+-----+| name | dept | salary | rank | dense | # | cume | p_r |+---------+------+--------+------+-------+---+---------------------+-----+| Newt | NULL | 75000 | 1 | 2 | 1 | 1 | 0 || Ed | 10 | 100000 | 1 | 2 | 1 | 0.16666666666666666 | 0 || Newt | 10 | 80000 | 2 | 3 | 2 | 0.3333333333333333 | 0.2 || Fred | 10 | 70000 | 3 | 4 | 3 | 0.6666666666666666 | 0.4 || Michael | 10 | 70000 | 3 | 4 | 4 | 0.6666666666666666 | 0.4 || Jon | 10 | 60000 | 5 | 5 | 5 | 0.8333333333333334 | 0.8 || Dag | 10 | NULL | 6 | 6 | 6 | 1 | 1 || Pete | 20 | 65000 | 1 | 2 | 1 | 1 | 0 || Lebedev | 20 | 65000 | 1 | 2 | 2 | 1 | 0 || Jeff | 30 | 300000 | 1 | 2 | 1 | 0.5 | 0 || Will | 30 | 70000 | 2 | 3 | 2 | 1 | 1 |+---------+------+--------+------+-------+---+---------------------+-----+

(rank - 1) / (total rows - 1)

Page 33: SQL window functions for MySQL

33Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

NTILESELECT name, department_id AS dept, salary, ... PERCENT_RANK() OVER w AS p_r, NTILE(3) OVER w AS `ntile` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

+---------+------+--------+------+-------+---+---------------------+-----+-------+| name | dept | salary | rank | dense | # | cume | p_r | ntile |+---------+------+--------+------+-------+---+---------------------+-----+-------+| Newt | NULL | 75000 | 1 | 2 | 1 | 1 | 0 | 1 || Ed | 10 | 100000 | 1 | 2 | 1 | 0.16666666666666666 | 0 | 1 || Newt | 10 | 80000 | 2 | 3 | 2 | 0.3333333333333333 | 0.2 | 1 || Fred | 10 | 70000 | 3 | 4 | 3 | 0.6666666666666666 | 0.4 | 2 || Michael | 10 | 70000 | 3 | 4 | 4 | 0.6666666666666666 | 0.4 | 2 || Jon | 10 | 60000 | 5 | 5 | 5 | 0.8333333333333334 | 0.8 | 3 || Dag | 10 | NULL | 6 | 6 | 6 | 1 | 1 | 3 || Pete | 20 | 65000 | 1 | 2 | 1 | 1 | 0 | 1 || Lebedev | 20 | 65000 | 1 | 2 | 2 | 1 | 0 | 2 || Jeff | 30 | 300000 | 1 | 2 | 1 | 0.5 | 0 | 1 || Will | 30 | 70000 | 2 | 3 | 2 | 1 | 1 | 2 |+---------+------+--------+------+-------+---+---------------------+-----+-------+

Divides an orderedpartition into aspecified number ofgroups aka bucketsand assigns a bucketnumber to each row inthe partition.

Page 34: SQL window functions for MySQL

34Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

LEAD, LAGReturns value evaluated at the row that is offset rows after/before the current row within thepartition; if there is no such row, instead return default (which must be of the same type asvalue).

Both offset and default are evaluated with respect to the current row. If omitted, offsetdefaults to 1 and default to null

lead or lag function ::= { LEAD | LAG } ( expr [ , offset [ , default expression>] ] ) [ RESPECT NULLS ]

Note: “IGNORE NULLS” not supported, RESPECT NULLS is default but can be specified.

Page 35: SQL window functions for MySQL

35Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

+---------+------+--------+-------+| name | dept | salary | lead |+---------+------+--------+-------+| Newt | NULL | 75000 | NULL || Ed | 10 | 100000 | 80000 || Newt | 10 | 80000 | 70000 || Fred | 10 | 70000 | 70000 || Michael | 10 | 70000 | 60000 || Jon | 10 | 60000 | NULL || Dag | 10 | NULL | NULL || Pete | 20 | 65000 | 65000 || Lebedev | 20 | 65000 | NULL || Jeff | 30 | 300000 | 70000 || Will | 30 | 70000 | NULL |+---------+------+--------+-------+

LEADSELECT name, department_id AS dept, salary, LEAD(salary, 1) OVER w AS `lead` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

Page 36: SQL window functions for MySQL

36Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

+---------+------+--------+--------+-------+| name | dept | salary | lag | lead |+---------+------+--------+--------+-------+| Newt | NULL | 75000 | NULL | NULL || Ed | 10 | 100000 | NULL | 80000 || Newt | 10 | 80000 | 100000 | 70000 || Fred | 10 | 70000 | 80000 | 70000 || Michael | 10 | 70000 | 70000 | 60000 || Jon | 10 | 60000 | 70000 | NULL || Dag | 10 | NULL | 60000 | NULL || Pete | 20 | 65000 | NULL | 65000 || Lebedev | 20 | 65000 | 65000 | NULL || Jeff | 30 | 300000 | NULL | 70000 || Will | 30 | 70000 | 300000 | NULL |+---------+------+--------+--------+-------+

LAGSELECT name, department_id AS dept, salary, LAG(salary, 1) OVER w AS `lag`, LEAD(salary, 1) OVER w AS `lead` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY salary DESC);

Page 37: SQL window functions for MySQL

37Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

FIRST_VALUE, LAST_VALUE, NTH_VALUEReturns value evaluated at the first, last, nth in the frame of the current rowwithin the partition; if there is no nth row (frame is too small), the NTH_VALUEreturns NULL.

first or last value ::= { FIRST_VALUE | LAST_VALUE } ( expr ) [ RESPECT NULLS ]

nth_value ::= NTH_VALUE ( expr, nth-row ) [FROM FIRST] [ RESPECT NULLS ]

Note: “IGNORE NULLS” is not supported, RESPECT NULLS is used but canbe specified.Note: For NTH_VALUE, “FROM LAST” is not supported, FROM FIRST is usedbut can be specified

Page 38: SQL window functions for MySQL

38Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id AS dept, salary, SUM(salary) OVER w AS `sum`, FIRST_VALUE(salary) OVER w AS `first` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) +---------+------+--------+--------+--------+| name | dept | salary | sum | first |+---------+------+--------+--------+--------+| Newt | NULL | 75000 | 75000 | 75000 || Dag | 10 | NULL | NULL | NULL || Ed | 10 | 100000 | 100000 | NULL || Fred | 10 | 60000 | 160000 | NULL || Jon | 10 | 60000 | 220000 | 100000 || Michael | 10 | 70000 | 190000 | 60000 || Newt | 10 | 80000 | 210000 | 60000 || Lebedev | 20 | 65000 | 65000 | 65000 || Pete | 20 | 65000 | 130000 | 65000 || Jeff | 30 | 300000 | 300000 | 300000 || Will | 30 | 70000 | 370000 | 300000 |+---------+------+--------+--------+--------+

FIRST_VALUE “in frame”

Current row: Jon

FIRST_VALUE in frame is: Ed

Page 39: SQL window functions for MySQL

39Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id AS dept, salary, SUM(salary) OVER w AS `sum`, FIRST_VALUE(salary) OVER w AS `first`, LAST_VALUE(salary) OVER w AS `last` FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) +---------+------+--------+--------+--------+--------+| name | dept | salary | sum | first | last |+---------+------+--------+--------+--------+--------+| Newt | NULL | 75000 | 75000 | 75000 | 75000 || Dag | 10 | NULL | NULL | NULL | NULL || Ed | 10 | 100000 | 100000 | NULL | 100000 || Fred | 10 | 60000 | 160000 | NULL | 60000 || Jon | 10 | 60000 | 220000 | 100000 | 60000 || Michael | 10 | 70000 | 190000 | 60000 | 70000 || Newt | 10 | 80000 | 210000 | 60000 | 80000 || Lebedev | 20 | 65000 | 65000 | 65000 | 65000 || Pete | 20 | 65000 | 130000 | 65000 | 65000 || Jeff | 30 | 300000 | 300000 | 300000 | 300000 || Will | 30 | 70000 | 370000 | 300000 | 70000 |+---------+------+--------+--------+--------+--------+

LAST_VALUE “in frame”

Current row: Jon

LAST_VALUE in frame is: Jon

Page 40: SQL window functions for MySQL

40Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

SELECT name, department_id AS dept, salary, SUM(salary) OVER w AS `sum`, NTH_VALUE(salary, 2) OVER w AS nth FROM employee WINDOW w AS (PARTITION BY department_id ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) +---------+------+--------+--------+--------+| name | dept | salary | sum | nth |+---------+------+--------+--------+--------+| Newt | NULL | 75000 | 75000 | NULL || Dag | 10 | NULL | NULL | NULL || Ed | 10 | 100000 | 100000 | 100000 || Fred | 10 | 60000 | 160000 | 100000 || Jon | 10 | 60000 | 220000 | 60000 || Michael | 10 | 70000 | 190000 | 60000 || Newt | 10 | 80000 | 210000 | 70000 || Lebedev | 20 | 65000 | 65000 | NULL || Pete | 20 | 65000 | 130000 | 65000 || Jeff | 30 | 300000 | 300000 | NULL || Will | 30 | 70000 | 370000 | 70000 |+---------+------+--------+--------+--------+

NTH_VALUE “in frame”

Current row: Jon

NTH_VALUE(.., 2) in frame is: Fred

Page 41: SQL window functions for MySQL

41Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Implicit and explicit windows● Windows can be implicit and unnamed:

COUNT(*) OVER (PARTITION BY DEPARTMENT_ID)● Windows can be defined and named via the windows clause clause:

SELECT COUNT(*) OVER w FROM t WINDOW w as (PARTITION BY department_id)

● This allows easy sharing of windows between several window functions

and also avoids redundant windowing steps since more functions can be

evaluated in the same step. ● Limitation: equivalent windows are not yet merged

Page 42: SQL window functions for MySQL

42Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Implicit and explicit windows● A window definition can inherit from another window definition in its

specification, adding detail (no override)

SELECT name, department_id, COUNT(*) OVER w1 AS cnt1, COUNT(*) over w2 AS cnt2 FROM employee WINDOW w1 AS (PARTITION BY department_id), w2 AS (w1 ORDER BY name) ORDER BY department_id, name;

+---------+---------------+------+------+| name | department_id | cnt1 | cnt2 |+---------+---------------+------+------+| Newt | NULL | 1 | 1 || Dag | 10 | 6 | 1 || Ed | 10 | 6 | 2 || Fred | 10 | 6 | 3 || Jon | 10 | 6 | 4 || Michael | 10 | 6 | 5 || Newt | 10 | 6 | 6 || Lebedev | 20 | 2 | 1 || Pete | 20 | 2 | 2 || Jeff | 30 | 2 | 1 || Will | 30 | 2 | 2 |+---------+---------------+------+------+

Page 43: SQL window functions for MySQL

43Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Time-line● Work in progress, no due date yet

Page 44: SQL window functions for MySQL

44Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Q & A

Page 45: SQL window functions for MySQL

45Copyright © 2017 Oracle and/or its affiliates. All rights reserved.

Page 46: SQL window functions for MySQL