ROWS and RANGE: Windowing clause defaults – learning by example in MySQL.

The more I dive into Window Functions, at least 2 things are very apparent to me: 1) They are incredibly powerful, 2) I have a long ways to go with wrapping my head around them. I recently wrote, The PARTITION BY clause of a Window Function – with an example in MySQL where I built upon a base Window Function query with the PARTITION BY clause. However, in this post, I want to return to and explore, some defaults going on behind the scenes in that query in regards to the windowing portion.

Photo by Julia Sabiniarz on Unsplash

Note: All data, names or naming found within the database presented in this post, are strictly used for practice, learning, instruction, and testing purposes. It by no means depicts actual data belonging to or being used by any party or organization.

OS and DB used:


  • Xubuntu Linux 16.04.5 LTS (Xenial Xerus)
  • MySQL 8.0.15

Self-Promotion:

If you enjoy the content written here, by all means, share this blog and your favorite post(s) with others who may benefit from or like it as well. Since coffee is my favorite drink, you can even buy me one if you would like!


To be perfectly honest, the credit is not all mine alone. In fact, none of it is. I happened upon this fantastic SQL Server video on YouTube, Difference between rows and range, that really put into perspective some default behavior in the windowing portion of the OVER() clause. I’m merely passing on what I learned from that resource.

In the OVER() clause, you can specify 1 to 3 different optional clauses. They are:

1
OVER(partitioning clause, ordering clause, windowing clause)

Let’s revisit this query and result set from the blog post referenced in the opener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
mysql> SELECT day_walked, burned_calories,
    -> SUM(burned_calories) OVER(ORDER BY burned_calories ASC) AS amt_per_trail,
    -> trail_hiked FROM hiking_stats WHERE MONTHNAME(day_walked) = 'July';
+------------+-----------------+---------------+------------------------+
| day_walked | burned_calories | amt_per_trail | trail_hiked            |
+------------+-----------------+---------------+------------------------+
| 2018-07-21 |            93.1 |          93.1 | Yard Mowing            |
| 2018-07-23 |           322.9 |         416.0 | West Boundary          |
| 2018-07-03 |           323.7 |         739.7 | West Boundary          |
| 2018-07-12 |           325.9 |        1065.6 | West Boundary          |
| 2018-07-09 |           336.0 |        1401.6 | West Boundary          |
| 2018-07-28 |           337.4 |        1739.0 | Sandy Trail-Drive      |
| 2018-07-19 |           339.2 |        2078.2 | West Boundary          |
| 2018-07-17 |           339.4 |        2417.6 | West Boundary          |
| 2018-07-04 |           342.8 |        2760.4 | West Boundary          |
| 2018-07-07 |           347.6 |        3108.0 | Sandy Trail-Drive      |
| 2018-07-29 |           348.7 |        3456.7 | West Boundary          |
| 2018-07-08 |           351.6 |        3808.3 | West Boundary          |
| 2018-07-31 |           359.9 |        4168.2 | West Boundary          |
| 2018-07-30 |           361.6 |        4529.8 | West Boundary          |
| 2018-07-18 |           368.1 |        4897.9 | West Boundary          |
| 2018-07-16 |           368.6 |        5266.5 | West Boundary          |
| 2018-07-11 |           375.2 |        5641.7 | West Boundary          |
| 2018-07-06 |           375.7 |        6017.4 | West Boundary          |
| 2018-07-22 |           378.3 |        6774.0 | West Boundary          |
| 2018-07-27 |           378.3 |        6774.0 | West Boundary          |
| 2018-07-02 |           379.5 |        7153.5 | Yard Mowing            |
| 2018-07-25 |           379.9 |        7533.4 | West Boundary          |
| 2018-07-15 |           382.9 |        7916.3 | House-Power Line Route |
| 2018-07-24 |           386.4 |        8302.7 | West Boundary          |
| 2018-07-13 |           416.2 |        8718.9 | Yard Mowing            |
+------------+-----------------+---------------+------------------------+
25 rows in set (0.00 sec)

You can see that the ‘amt_per_trail’ column is a nice running total (think SUM()) of the ‘burned_calories’ values from the current row, in addition to, those previous rows above.

For better understanding, let’s see the comparison between the original query without the explicit windowing RANGE specification and one with it. Note: I removed the ‘day_walked’ column for better readability on-screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SELECT
burned_calories,
SUM(burned_calories) OVER(ORDER BY burned_calories ASC) AS out_of_the_box,
SUM(burned_calories) OVER(ORDER BY burned_calories ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS explicit,
trail_hiked
FROM hiking_stats
WHERE MONTHNAME(day_walked) = 'July';
+-----------------+----------------+----------+------------------------+
| burned_calories | out_of_the_box | explicit | trail_hiked            |
+-----------------+----------------+----------+------------------------+
|            93.1 |           93.1 |     93.1 | Yard Mowing            |
|           322.9 |          416.0 |    416.0 | West Boundary          |
|           323.7 |          739.7 |    739.7 | West Boundary          |
|           325.9 |         1065.6 |   1065.6 | West Boundary          |
|           336.0 |         1401.6 |   1401.6 | West Boundary          |
|           337.4 |         1739.0 |   1739.0 | Sandy Trail-Drive      |
|           339.2 |         2078.2 |   2078.2 | West Boundary          |
|           339.4 |         2417.6 |   2417.6 | West Boundary          |
|           342.8 |         2760.4 |   2760.4 | West Boundary          |
|           347.6 |         3108.0 |   3108.0 | Sandy Trail-Drive      |
|           348.7 |         3456.7 |   3456.7 | West Boundary          |
|           351.6 |         3808.3 |   3808.3 | West Boundary          |
|           359.9 |         4168.2 |   4168.2 | West Boundary          |
|           361.6 |         4529.8 |   4529.8 | West Boundary          |
|           368.1 |         4897.9 |   4897.9 | West Boundary          |
|           368.6 |         5266.5 |   5266.5 | West Boundary          |
|           375.2 |         5641.7 |   5641.7 | West Boundary          |
|           375.7 |         6017.4 |   6017.4 | West Boundary          |
|           378.3 |         6774.0 |   6774.0 | West Boundary          |
|           378.3 |         6774.0 |   6774.0 | West Boundary          |
|           379.5 |         7153.5 |   7153.5 | Yard Mowing            |
|           379.9 |         7533.4 |   7533.4 | West Boundary          |
|           382.9 |         7916.3 |   7916.3 | House-Power Line Route |
|           386.4 |         8302.7 |   8302.7 | West Boundary          |
|           416.2 |         8718.9 |   8718.9 | Yard Mowing            |
+-----------------+----------------+----------+------------------------+
25 rows in set (0.01 sec)

As seen above, both versions of the Window Function return the exact same results.

In this particular query, the RANGE clause, RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW means: Execute whatever operation we are doing, (in this case, totaling with SUM()) on the current row, as well as all preceding rows (i.e., above, before, prior, etc…).

Yet, RANGE is not the only option for we can specify ROWS instead. Notice the similarities – or differences, if any – between them in the below results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
SELECT burned_calories,
SUM(burned_calories) OVER(ORDER BY burned_calories ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS using_range,
SUM(burned_calories) OVER(ORDER BY burned_calories ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS using_rows,
trail_hiked
FROM hiking_stats
WHERE MONTHNAME(day_walked) = 'July';
+-----------------+-------------+------------+------------------------+
| burned_calories | using_range | using_rows | trail_hiked            |
+-----------------+-------------+------------+------------------------+
|            93.1 |        93.1 |       93.1 | Yard Mowing            |
|           322.9 |       416.0 |      416.0 | West Boundary          |
|           323.7 |       739.7 |      739.7 | West Boundary          |
|           325.9 |      1065.6 |     1065.6 | West Boundary          |
|           336.0 |      1401.6 |     1401.6 | West Boundary          |
|           337.4 |      1739.0 |     1739.0 | Sandy Trail-Drive      |
|           339.2 |      2078.2 |     2078.2 | West Boundary          |
|           339.4 |      2417.6 |     2417.6 | West Boundary          |
|           342.8 |      2760.4 |     2760.4 | West Boundary          |
|           347.6 |      3108.0 |     3108.0 | Sandy Trail-Drive      |
|           348.7 |      3456.7 |     3456.7 | West Boundary          |
|           351.6 |      3808.3 |     3808.3 | West Boundary          |
|           359.9 |      4168.2 |     4168.2 | West Boundary          |
|           361.6 |      4529.8 |     4529.8 | West Boundary          |
|           368.1 |      4897.9 |     4897.9 | West Boundary          |
|           368.6 |      5266.5 |     5266.5 | West Boundary          |
|           375.2 |      5641.7 |     5641.7 | West Boundary          |
|           375.7 |      6017.4 |     6017.4 | West Boundary          |
|           378.3 |      6774.0 |     6395.7 | West Boundary          |
|           378.3 |      6774.0 |     6774.0 | West Boundary          |
|           379.5 |      7153.5 |     7153.5 | Yard Mowing            |
|           379.9 |      7533.4 |     7533.4 | West Boundary          |
|           382.9 |      7916.3 |     7916.3 | House-Power Line Route |
|           386.4 |      8302.7 |     8302.7 | West Boundary          |
|           416.2 |      8718.9 |     8718.9 | Yard Mowing            |
+-----------------+-------------+------------+------------------------+
25 rows in set (0.00 sec)

So they are the same right? Both columns’ results match up and all.

So why are there 2 different options that provide seemingly identical functionality? Are you sure they produce the exact same results?

Astute readers will have noticed these particular rows:

1
2
|           378.3 |      6774.0 |     6395.7 | West Boundary          |
|           378.3 |      6774.0 |     6774.0 | West Boundary          |

Maybe you are wondering, why does ROWS do that?

In fact, it’s not ROWS at all. It is in how RANGE treats those duplicate values of 378.3 in the ‘burned_calories’ column. RANGE treats duplicates as a single entity where ROWS does not.

Here is a break down: In the RANGE column, the value in the row above the duplicates, 6017.4, is added to 378.3 twice (E.g., 6017.4 + 378.3 + 378.3) and that equals 6774.0 which is displayed in both following rows.

Whereas for ROW, those duplicate values are added to the previous row, individually per row. Therefore, displaying a true running total throughout the result set.

I hope this blog post has provided you with an overview of default behavior, as well as underlying nuances with both the RANGE and ROW clauses. I know I sure have learned a great deal in writing it.

Like what you have read? See anything incorrect? Please comment below and thanks for reading!!!

Explore the official MySQL 8.0 Online Manual for more information.

A Call To Action!

Thank you for taking the time to read this post. I truly hope you discovered something interesting and enlightening. Please share your findings here, with someone else you know who would get the same value out of it as well.

Visit the Portfolio-Projects page to see blog post/technical writing I have completed for clients.

Have I mentioned how much I love a cup of coffee?!?!

To receive email notifications (Never Spam) from this blog (“Digital Owl’s Prose”) for the latest blog posts as they are published, please subscribe (of your own volition) by clicking the ‘Click To Subscribe!’ button in the sidebar on the homepage! (Feel free at any time to review the Digital Owl’s Prose Privacy Policy Page for any questions you may have about: email updates, opt-in, opt-out, contact forms, etc…)

Be sure and visit the “Best Of” page for a collection of my best blog posts.


Josh Otwell has a passion to study and grow as a SQL Developer and blogger. Other favorite activities find him with his nose buried in a good book, article, or the Linux command line. Among those, he shares a love of tabletop RPG games, reading fantasy novels, and spending time with his wife and two daughters.

Disclaimer: The examples presented in this post are hypothetical ideas of how to achieve similar types of results. They are not the utmost best solution(s). The majority, if not all, of the examples provided, is performed on a personal development/learning workstation-environment and should not be considered production quality or ready. Your particular goals and needs may vary. Use those practices that best benefit your needs and goals. Opinions are my own.

Advertisements

Hey thanks for commenting! Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.