In SQL Server, LAG()
is a window function that enables us to access a value from a previous row in the same result set, without the need to perform a self-join.
We specify the previous row as an offset from the current row. An offset of 1
means the previous row, an offset of 2
means two rows back, and so on.
Syntax
The official syntax goes like this:
LAG (scalar_expression [ , offset ] [ , default ] ) [ IGNORE NULLS | RESPECT NULLS ]
OVER ( [ partition_by_clause ] order_by_clause )
So scalar_expression
can be the name of a column in the query, and we have the option of specifying the offset (offset
) and a default value (default
) to use in instances where offset
exceeds the scope of the partition and there’s no value to be returned.
We must also provide an OVER
clause, and that clause must have an ORDER BY
clause.
Example
Here’s a basic example to demonstrate:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice, 1 ) OVER (
ORDER BY ProductPrice
) AS LAG
FROM Products;
Result:
VendorId ProductName ProductPrice LAG -------- ------------------------------- ------------ ----- 1004 Bottomless Coffee Mugs (4 Pack) 9.99 null 1003 Hammock 10 9.99 1001 Long Weight (green) 11.99 10 1004 Tea Pot 12.45 11.99 1001 Long Weight (blue) 14.75 12.45 1001 Left handed screwdriver 25.99 14.75 1001 Right handed screwdriver 25.99 25.99 1002 Sledge Hammer 33.49 25.99 1003 Straw Dog Box 55.99 33.49 1003 Chainsaw 245 55.99
Here, I specified that the LAG()
function return its value from the ProductPrice
column, and I specified an offset of 1
.
We can see that the LAG
column returns its value from the previous row. On the first row however, it returns null
. That’s because there’s no previous row for which to retrieve a value.
Using the Default Offset
We can omit the second argument in order to use the default offset value of 1
:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice ) OVER (
ORDER BY ProductPrice
) AS LAG
FROM Products;
Result:
VendorId ProductName ProductPrice LAG -------- ------------------------------- ------------ ----- 1004 Bottomless Coffee Mugs (4 Pack) 9.99 null 1003 Hammock 10 9.99 1001 Long Weight (green) 11.99 10 1004 Tea Pot 12.45 11.99 1001 Long Weight (blue) 14.75 12.45 1001 Left handed screwdriver 25.99 14.75 1001 Right handed screwdriver 25.99 25.99 1002 Sledge Hammer 33.49 25.99 1003 Straw Dog Box 55.99 33.49 1003 Chainsaw 245 55.99
Same result as the previous example. The only difference is in the code we used to produce the result. In the previous example we explicitly specified an offset of 1
, whereas in this example we omitted the offset argument altogether in order to use the default offset of 1
.
Changing the Offset
Here’s an example of using a different offset:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice, 3 ) OVER (
ORDER BY ProductPrice
) AS LAG
FROM Products;
Result:
VendorId ProductName ProductPrice LAG -------- ------------------------------- ------------ ----- 1004 Bottomless Coffee Mugs (4 Pack) 9.99 null 1003 Hammock 10 null 1001 Long Weight (green) 11.99 null 1004 Tea Pot 12.45 9.99 1001 Long Weight (blue) 14.75 10 1001 Left handed screwdriver 25.99 11.99 1001 Right handed screwdriver 25.99 12.45 1002 Sledge Hammer 33.49 14.75 1003 Straw Dog Box 55.99 25.99 1003 Chainsaw 245 25.99
Here I specified an offset of 3
, and so we got the value from three rows back. We also got null
in the first three rows of the LAG
column as a result.
Change the Default Value
By default, LAG()
returns null
if the offset is beyond the scope of the partition and there’s no value to be returned (such as when we’re on the first row).
We can provide a third argument to change the default value:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice, 3, 0 ) OVER (
ORDER BY ProductPrice
) AS LAG
FROM Products;
Result:
VendorId ProductName ProductPrice LAG -------- ------------------------------- ------------ ----- 1004 Bottomless Coffee Mugs (4 Pack) 9.99 0 1003 Hammock 10 0 1001 Long Weight (green) 11.99 0 1004 Tea Pot 12.45 9.99 1001 Long Weight (blue) 14.75 10 1001 Left handed screwdriver 25.99 11.99 1001 Right handed screwdriver 25.99 12.45 1002 Sledge Hammer 33.49 14.75 1003 Straw Dog Box 55.99 25.99 1003 Chainsaw 245 25.99
In this case I specified a default value of 0
, and so the first three rows return 0
instead of null
.
Note that the third argument, if provided, must be type-compatible with the expression provided by the first argument.
Partitioning the Result Set
We can use a PARTITION BY
clause with window functions to partition the result set by a given column. For example, we can partition by vendor:
SELECT
v.VendorName,
p.ProductName,
p.ProductPrice,
LAG( ProductPrice ) OVER (
PARTITION BY p.VendorId
ORDER BY p.ProductPrice
) AS LAG
FROM Products p
INNER JOIN Vendors v
ON v.VendorId = p.VendorId;
Result:
VendorName ProductName ProductPrice LAG ------------- ------------------------------- ------------ ----- Mars Supplies Long Weight (green) 11.99 null Mars Supplies Long Weight (blue) 14.75 11.99 Mars Supplies Left handed screwdriver 25.99 14.75 Mars Supplies Right handed screwdriver 25.99 25.99 Randy Roofers Sledge Hammer 33.49 null Pedal Medals Hammock 10 null Pedal Medals Straw Dog Box 55.99 10 Pedal Medals Chainsaw 245 55.99 Katty Kittens Bottomless Coffee Mugs (4 Pack) 9.99 null Katty Kittens Tea Pot 12.45 9.99
This time our LAG
column resets with each new partition.
In this case I used a SQL join to join the Products
and Vendors
table so that we got the vendor names instead of just their IDs.
The OVER
Clause is Required
As mentioned, we must provide the OVER
clause when using window functions like LAG()
. Here’s what happens if we remove it:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice )
FROM Products;
Result:
Msg 10753, Level 15, State 1, Line 5 The function 'LAG' must have an OVER clause.
The ORDER BY
Clause is Required
And our OVER
clause must include an ORDER BY
clause. Here’s what happens when we use an OVER
clause without an ORDER BY
clause:
SELECT
VendorId,
ProductName,
ProductPrice,
LAG( ProductPrice ) OVER( )
FROM Products;
Result:
Msg 4112, Level 15, State 1, Line 5 The function 'LAG' must have an OVER clause with ORDER BY.
Documentation
For more information and examples, see Microsoft’s documentation for the LAG()
function.