DEV Community

mohamed Tayel
mohamed Tayel

Posted on • Updated on

Harnessing SQL's PRECEDING and FOLLOWING: Advanced Data Analysis with Window Functions

Introduction

In the realm of data analysis, SQL stands as a cornerstone technology, enabling analysts to extract, transform, and analyze data efficiently. Among SQL's arsenal of features, window functions stand out for their ability to perform complex calculations over sets of rows related to the current row. This article dives deep into an example SQL query utilizing window functions to showcase their power and utility in real-world data analysis.

Section 1: The Basics of Window Functions

Window functions allow for advanced data analysis within SQL queries by performing calculations across sets of rows that are related to the current row. Unlike regular aggregate functions, window functions do not collapse rows but instead allow each row to retain its identity, facilitating intricate calculations like running totals, moving averages, and row-to-row comparisons.

Section 2: Exploring a Real-world Example

Consider a database containing an Orders table with sales data. We aim to analyze this data to extract insights such as total sales, sales per customer, running totals, and comparisons between consecutive orders. The following query exemplifies the use of window functions to achieve this:

SELECT CustomerKey, OrderKey, SalesAmount,
    SUM(SalesAmount) OVER() AS GrandTotal,
    SUM(SalesAmount) OVER(PARTITION BY CustomerKey) AS CustomerTotal,
    SUM(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CustomerRunningTotal,
    SalesAmount AS CurrentSale,
    MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PreviousSale,
    MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) AS NextSale
FROM Orders
ORDER BY CustomerKey, OrderKey;
Enter fullscreen mode Exit fullscreen mode

Section 3: Breaking Down the Example

This SQL query uses window functions to calculate aggregates and comparisons across different scopes of data within the Orders table. It's designed to give insights into sales amounts by customer and order, including totals and running totals, as well as adjacent values (previous and next sales amounts) for each order. Let's break down each part of the query:

  1. SELECT Clause: This is where the fields to be returned by the query are specified.

    • CustomerKey, OrderKey, SalesAmount: These are straightforward selections of the customer identifier, order identifier, and the sales amount for each order, respectively.
  2. SUM(SalesAmount) OVER() AS GrandTotal: This calculates the grand total sales amount across the entire dataset. The OVER() clause without a PARTITION BY means it sums up SalesAmount for all rows included in the result set.

  3. SUM(SalesAmount) OVER(PARTITION BY CustomerKey) AS CustomerGrandTotal: This calculates the total sales amount per customer. The PARTITION BY CustomerKey clause divides the dataset into partitions based on CustomerKey, and then the sum of SalesAmount is calculated for each partition.

  4. SUM(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CustomerRunningTotal: This calculates a running total of sales for each customer, ordered by OrderKey. The ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW clause specifies that for each row, the sum should include all rows from the start of the partition up to the current row.

  5. SalesAmount AS CurrentValue: This simply renames the SalesAmount column to CurrentValue in the result set, making it clearer that this column represents the sales amount of the current row/order.

  6. MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PreviousValue: This finds the previous sales amount for each order within the same customer. It looks exactly one row back (1 PRECEDING) to find the maximum sales amount, which, in this context, simply means the sales amount of the previous order because the range is restricted to exactly one row before the current one.

  7. MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) AS NextValue: Similar to the previous calculation, but this time it finds the next sales amount for each order within the same customer by looking exactly one row ahead (1 FOLLOWING).

  8. FROM Orders: This specifies the table from which to retrieve the data.

  9. ORDER BY CustomerKey, OrderKey: Finally, the result set is ordered by CustomerKey and then OrderKey to ensure that the data is presented in a meaningful sequence, first grouped by customer and then in the order sequence.

Section 4:understandeing ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

The clause ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is a part of window function syntax in SQL, specifically within the OVER clause, which defines a frame or window of rows for the function to operate on. Let's break it down:

ROWS

This keyword specifies that the window frame is defined in terms of physical rows. SQL also supports RANGE, which operates on the logical values of the ordering column, but ROWS is about the actual row positions.

BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

This part of the clause defines the start and end of the window frame:

  • UNBOUNDED PRECEDING: This means the window starts at the first row of the partition. "Unbounded" indicates there's no limit to how far back the window extends, so it goes all the way to the start.

  • CURRENT ROW: This means the window ends at the current row being processed.

How It Works

When a window function with ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is used, it calculates the function (e.g., SUM, MAX) over the rows from the start of the partition up to and including the current row.

For example, if you're using SUM(SalesAmount) OVER(...) with this frame, for each row, SQL sums SalesAmount from the first row in the partition (or the entire dataset if partitioning is not used) to the row it's currently on. This creates a running total that increases as you move down through the rows.

Practical Example

Imagine a simple dataset of orders:

OrderKey | CustomerKey | SalesAmount
---------|-------------|------------
1        | 100         | 10
2        | 100         | 20
3        | 100         | 30
Enter fullscreen mode Exit fullscreen mode

Using SUM(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), the calculated running total for each row would be:

OrderKey | CustomerKey | SalesAmount | RunningTotal
---------|-------------|-------------|-------------
1        | 100         | 10          | 10
2        | 100         | 20          | 30 (10 + 20)
3        | 100         | 30          | 60 (10 + 20 + 30)
Enter fullscreen mode Exit fullscreen mode

Here, for each row, the running total (RunningTotal) is the sum of SalesAmount from all preceding rows in the partition (including the current row), as ordered by OrderKey.

Section 5:Understading MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PreviousValue

Let's clarify the MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PreviousValue clause, focusing on how it works and what it's used for.

This SQL clause is a part of a window function that calculates the maximum SalesAmount from a specific range (or "window") of rows relative to the current row, for each customer. The clause can be broken down into several key components:

PARTITION BY CustomerKey

This partitions (or groups) the data by CustomerKey, so the function will calculate the maximum value within each customer's set of orders separately. Each customer's data is treated as a distinct block for calculation.

ORDER BY OrderKey

This orders the rows within each partition (customer's orders) by OrderKey. The order is essential for determining which row is "preceding" or "following" another.

ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING

This is the window frame specification, and it might be the trickiest part to understand:

  • 1 PRECEDING: This refers to the row that is one step before the current row, according to the order defined by ORDER BY OrderKey.

  • ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING: This frame specifies a very narrow window that includes only one row: the one immediately before the current row. It essentially tells the database to look at the single row just before the current one, in the context of the specified ordering.

MAX(SalesAmount)

This function then finds the maximum SalesAmount within the defined window. Since the window specified by ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING includes only one row (the immediate predecessor of the current row), the "maximum" in this context simply means the SalesAmount of that previous row.

AS PreviousValue

The result of this window function is then labeled as PreviousValue. For each row in your result set, PreviousValue will contain the SalesAmount from the immediately preceding row (per customer, as ordered by OrderKey).

Practical Example

Given a simplified dataset like this:

OrderKey | CustomerKey | SalesAmount
---------|-------------|------------
1        | 100         | 10
2        | 100         | 20
3        | 100         | 15
Enter fullscreen mode Exit fullscreen mode

The PreviousValue for each row, calculated by this window function, would be:

OrderKey | CustomerKey | SalesAmount | PreviousValue
---------|-------------|-------------|--------------
1        | 100         | 10          | NULL (no preceding row)
2        | 100         | 20          | 10   (from row 1)
3        | 100         | 15          | 20   (from row 2)
Enter fullscreen mode Exit fullscreen mode

For the first row in each partition, since there is no preceding row, the PreviousValue would be NULL. For subsequent rows, it captures the SalesAmount of the row immediately before it, thanks to the specified window frame.

Section 6:understanding MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING)

The clause you're asking about, MAX(SalesAmount) OVER(PARTITION BY CustomerKey ORDER BY OrderKey ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING), involves a window function similar to the one for calculating the previous value, but it's designed to look ahead to the next row rather than back to the previous one. Here's a breakdown of what this clause means and how it works:

PARTITION BY CustomerKey

This part of the clause means that the data will be divided into groups based on CustomerKey. Each group (or partition) will contain all rows with the same CustomerKey, and the function will be applied within these partitions. Essentially, calculations are done separately for each customer.

ORDER BY OrderKey

This orders the rows within each partition according to OrderKey. The order is crucial for determining which row is considered "next" in the sequence.

ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING

This is the window frame specification and perhaps the most critical part to understand:

  • 1 FOLLOWING: This specifies the row that is one step after the current row, based on the ordering defined by ORDER BY OrderKey.

  • ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING: By setting both the start and end of the window frame to "1 following," the window is defined to include only the single row immediately after the current row. It's a narrow window that looks exactly one row ahead.

MAX(SalesAmount)

Within the window frame defined above, this function finds the maximum SalesAmount. Since the window includes only one row (the one immediately following the current row), the "maximum" here simply refers to the SalesAmount of that next row.

Practical Example

Consider the following simplified dataset:

OrderKey | CustomerKey | SalesAmount
---------|-------------|------------
1        | 100         | 10
2        | 100         | 20
3        | 100         | 15
Enter fullscreen mode Exit fullscreen mode

The calculation for each row, based on the clause you've provided, would yield:

OrderKey | CustomerKey | SalesAmount | NextValue
---------|-------------|-------------|----------
1        | 100         | 10          | 20   (from row 2)
2        | 100         | 20          | 15   (from row 3)
3        | 100         | 15          | NULL (no following row)
Enter fullscreen mode Exit fullscreen mode

For each row, NextValue will contain the SalesAmount from the row immediately following it, as defined by the window frame ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING. For the last row in each partition, since there is no following row, the NextValue would be NULL.

Section 7: Practical Applications and Benefits

This query exemplifies the nuanced analysis possible with SQL window functions, from evaluating overall sales performance to understanding customer-specific trends and order-by-order dynamics. Such insights can inform strategic business decisions, like identifying valuable customers, adjusting sales strategies, and optimizing inventory management.

Section 8: Tips for Effective Use

While powerful, window functions require careful handling to optimize performance and ensure accurate results. Key considerations include:

  • Defining precise window frames for calculations.
  • Being mindful of performance on large datasets and utilizing indexing where appropriate.
  • Testing queries thoroughly to confirm accuracy.

Conclusion

Window functions enrich SQL's analytical capabilities, allowing for sophisticated data analysis that would be complex or inefficient with standard aggregate functions. By understanding and applying these functions, data analysts and database professionals can unlock deeper insights into their data, driving informed decision-making and strategic planning.


Top comments (1)

Collapse
 
moh_moh701 profile image
mohamed Tayel

you can download sample of data from this link |
drive.google.com/file/d/1C5LkkatT1...