Hello! Welcome to the final lesson in our module on advanced window functions.
In our last session, you learned how to use window frames with functions like SUM() and AVG() to calculate running totals and moving averages. These functions aggregate data over a set of rows. Today, we'll explore two powerful functions, LAG() and LEAD(), that operate differently. Instead of aggregating a group, they allow you to "peek" back or forward to access data from a single, specific row at a defined offset.
This lesson directly addresses the learning outcome: Use LAG() and LEAD() to access data from preceding or succeeding rows within a partition. This is a fundamental technique for performing period-over-period comparisons, such as calculating month-over-month growth or finding the time between events—common tasks in technical support and development roles.
A New Tool in the Window Function Toolbox
LAG() and LEAD() are positional window functions.
LAG(): "Lags" behind the current row, allowing you to access data from a previous row.LEAD(): "Leads" the current row, allowing you to access data from a succeeding row.
These functions provide a highly efficient way to compare values across rows. Given your interest in low-level systems and "how things work," it's worth noting that these functions were introduced to SQL as a more performant and readable alternative to older, more cumbersome techniques like self-joins, which can be computationally expensive.
1. The Core Concepts and Syntax of LAG() and LEAD()
Let's start with a clear, visual explanation of what these functions do and how to write them.
SQL Value Window Functions | LEAD, LAG, FIRST_VALUE, LAST_VALUE | #SQL Course 25
This video from 'Data with Baraa' provides an excellent conceptual introduction to LAG() and LEAD(), followed by a detailed breakdown of their syntax and a step-by-step walkthrough of how SQL processes them.
Watch the video from the beginning to 10:52. Focus on: The core idea: accessing a value from a previous row (LAG) or a next row (LEAD). The three arguments: expression (what value to get), offset (how many rows to look back/forward), and default (what to use if no row is found). The row-by-row processing example. This shows exactly how the function is evaluated for each row in the window.
As the video explained, the general syntax for LAG() is:LAG(expression, offset, default_value) OVER (ORDER BY ...)
And for LEAD():LEAD(expression, offset, default_value) OVER (ORDER BY ...)
Let's break down the components:
expression: The column whose value you want to retrieve. This is the only mandatory argument.offset: (Optional) An integer specifying how many rows to look backward (LAG) or forward (LEAD). The default is1.default_value: (Optional) The value to return if the target row is outside the partition's bounds (e.g., there's no previous row for the first row). The default isNULL.OVER (ORDER BY ...): This is mandatory. It defines the sequence of rows, establishing what "previous" and "next" mean.
2. Putting it all Together with PARTITION BY
The true analytical power of LAG() and LEAD() is unlocked when you use them with PARTITION BY, which you're already familiar with. This clause confines the LAG() or LEAD() operation to a specific group of rows. When the query processor moves to a new partition, the function "resets".
For example, if you want to find the previous order date for each customer, you would partition by the customer ID. This ensures you don't incorrectly pull the last order from a different customer.
Lead/Lag Window Analytical functions in SQL | Advance SQL concepts
Let's watch another practical demonstration. This video from Ankit Bansal shows how PARTITION BY works with LAG() to calculate year-over-year sales per region.
Watch the section from 07:00 to 09:18. Observe how adding PARTITION BY region causes the LAG() calculation to restart for each new region. This prevents data from the 'Central' region from incorrectly being used as the 'previous' value for the 'East' region.
The full syntax combines everything you've learned:
SELECT
customer_id,
order_date,
LAG(order_date, 1) OVER (PARTITION BY customer_id ORDER BY order_date) AS previous_order_date
FROM orders;
In this query, for each row, SQL will:
- Look only at rows with the same
customer_id(the partition). - Within that partition, find the row that comes just before the current one, based on
order_date. - Return the
order_datefrom that previous row.
3. Practical Use Case: Customer Retention Analysis
Now, let's tie this to a realistic business problem. A common task for an analyst or developer is to analyze customer behavior. How loyal are your customers? How frequently do they place orders? To answer this, you need to calculate the time between their orders. LEAD() is perfect for this.
SQL Value Window Functions | LEAD, LAG, FIRST_VALUE, LAST_VALUE | #SQL Course 25
Let's return to the 'Data with Baraa' video for a more complex, job-relevant example: analyzing customer loyalty by calculating the average time between their orders.
Watch the section from 20:49 to 29:46. This is a multi-step problem that shows how LEAD() is a key building block in a larger analysis: First, LEAD(order_date) OVER (PARTITION BY customer_id ...) is used to get the next order date for each of a customer's orders. Then, a date difference function is used to calculate the time gap between the current and next order. Finally, this result is used in a subquery to calculate the average gap and rank customers by loyalty. This is an excellent example of how window functions are used in practice.
This use case demonstrates a critical point: window functions are often the first step in a more complex query, creating intermediate data that is then aggregated or filtered further.
Test your understanding!
You work for a company that monitors API performance. You have a table, api_logs, that records the response time for every request to various API endpoints.
Table: api_logs
| log_time | endpoint_id | response_ms |
|---|---|---|
| 2023-11-01 12:00:00 | /users | 55 |
| 2023-11-01 12:01:00 | /orders | 120 |
| 2023-11-01 12:01:00 | /users | 150 |
| 2023-11-01 12:02:00 | /users | 60 |
| 2023-11-01 12:02:00 | /orders | 110 |
Challenge: Write a query that shows each request's response time alongside the response time of the previous request for the same endpoint. Name the new column previous_response_ms.
Show answer
SELECT
log_time,
endpoint_id,
response_ms,
LAG(response_ms, 1) OVER (
PARTITION BY endpoint_id
ORDER BY log_time
) AS previous_response_ms
FROM api_logs;
Expected Output:
| log_time | endpoint_id | response_ms | previous_response_ms |
|---|---|---|---|
| 2023-11-01 12:01:00 | /orders | 120 | NULL |
| 2023-11-01 12:02:00 | /orders | 110 | 120 |
| 2023-11-01 12:00:00 | /users | 55 | NULL |
| 2023-11-01 12:01:00 | /users | 150 | 55 |
| 2023-11-01 12:02:00 | /users | 60 | 150 |
Notice that the first log for each endpoint_id has a NULL for previous_response_ms because there is no preceding row within its partition.
Conclusion
Today you've added two of the most practical window functions to your SQL toolkit. They provide an elegant and efficient way to handle a wide range of analytical tasks that involve comparing a row to its neighbors.
Key Takeaways:
LAG()looks at a preceding row, whileLEAD()looks at a succeeding row.- The full syntax is
LAG(expression, offset, default_value) OVER (PARTITION BY ... ORDER BY ...). - The
ORDER BYclause is mandatory because it defines the sequence of rows. - The
PARTITION BYclause is essential for real-world analysis, as it isolates the calculation within distinct groups of data. - These functions are invaluable for calculating period-over-period differences, time gaps between events, and other sequential analyses.
Preview of the Next Lesson:
This lesson concludes our module on advanced SQL analysis. You now have a powerful set of tools for complex querying. We will now shift gears significantly. In the next module, we will be introduced to NoSQL databases. We'll compare the relational model you've been mastering with non-relational data models and explore scenarios where a NoSQL database like a document or key-value store might be a better choice.