MySQL has a bunch of functions that enable us to get values from another row in the same column. This makes it easy for us to do stuff like calculate the difference between a value in the current row and one in another row, even if it’s in the same column.
Here are five options for calculating the difference between a value in the current row and a value in a different row but in the same column.
Option 1: The LAG()
Function
We can use the LAG()
function to get the value from a previous row in the same column (or in another column if need be). Therefore, we can use this function to help us calculate the difference between a value in the current row with a value in a previous row.
Example:
SELECT
IdiotName,
IQ,
LAG( IQ, 1 ) OVER win AS "Lag Value",
IQ - LAG( IQ, 1 ) OVER win AS "Current Minus Previous",
LAG( IQ, 1 ) OVER win - IQ AS "Previous Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );
Result:
+-------------------+------+-----------+------------------------+------------------------+ | IdiotName | IQ | Lag Value | Current Minus Previous | Previous Minus Current | +-------------------+------+-----------+------------------------+------------------------+ | Dumbest | 30 | NULL | NULL | NULL | | Homer | 40 | 30 | 10 | -10 | | Patrick Star | 40 | 40 | 0 | 0 | | Ed | 40 | 40 | 0 | 0 | | Dumber | 50 | 40 | 10 | -10 | | Peter Griffin | 50 | 50 | 0 | 0 | | Cosmo | 55 | 50 | 5 | -5 | | Dumb | 60 | 55 | 5 | -5 | | Ralph Wiggum | 65 | 60 | 5 | -5 | | Richard Watterson | 70 | 65 | 5 | -5 | +-------------------+------+-----------+------------------------+------------------------+
Here, I used LAG( IQ, 1 )
for the lag value. The IQ
part means the IQ
column, and 1
means that we go back one row.
In the Current Minus Previous
column we subtracted the LAG()
value from the IQ
column (i.e. the value of the current row in that column). In the Previous Minus Current
column we did the opposite – we subtracted the IQ
column from the LAG()
output.
The lag value in the first row is NULL
because there’s no previous row for which to retrieve a value.
Option 2: The LEAD()
Function
The LEAD()
function works similar to LAG()
, except that it gets its value from a later row. For example, we can use it to get its value from the next row. By doing this, we can calculate the difference between the current row and the next row.
Example:
SELECT
IdiotName,
IQ,
LEAD( IQ, 1 ) OVER win AS "Lead Value",
IQ - LEAD( IQ, 1 ) OVER win AS "Current Minus Next",
LEAD( IQ, 1 ) OVER win - IQ AS "Next Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );
Result:
+-------------------+------+------------+--------------------+--------------------+ | IdiotName | IQ | Lead Value | Current Minus Next | Next Minus Current | +-------------------+------+------------+--------------------+--------------------+ | Dumbest | 30 | 40 | -10 | 10 | | Homer | 40 | 40 | 0 | 0 | | Patrick Star | 40 | 40 | 0 | 0 | | Ed | 40 | 50 | -10 | 10 | | Dumber | 50 | 50 | 0 | 0 | | Peter Griffin | 50 | 55 | -5 | 5 | | Cosmo | 55 | 60 | -5 | 5 | | Dumb | 60 | 65 | -5 | 5 | | Ralph Wiggum | 65 | 70 | -5 | 5 | | Richard Watterson | 70 | NULL | NULL | NULL | +-------------------+------+------------+--------------------+--------------------+
Here, I used LEAD( IQ, 1 )
for the lead value. The IQ
part means the IQ
column, and 1
means that we go forward one row.
In the Current Minus Next
column we subtracted the LEAD()
value from the IQ
column (i.e. the value of the current row in that column). In the Next Minus Current
column we did the opposite – we subtracted the IQ
column from the LEAD()
output.
The lead value in the last row is NULL
because there’s no subsequent row for which to retrieve a value.
In both of the above examples I used the WINDOW
clause to create a named window. This allowed me to refer to that window from various OVER
clauses in the SELECT
list. This saved me from having to replicate the same code multiple times.
Option 3: The NTH_VALUE()
Function
The NTH_VALUE()
function is similar to LAG()
and LEAD()
, except that it allows us to choose the row number of the value we want to return. This is an absolute row number, so it’s not relative to the current row like the other two functions are.
Example:
SELECT
IdiotName,
IQ,
NTH_VALUE( IQ, 3 ) OVER win AS "Nth Value",
IQ - NTH_VALUE( IQ, 3 ) OVER win AS "Current Minus Nth",
NTH_VALUE( IQ, 3 ) OVER win - IQ AS "Nth Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING );
Result:
+-------------------+------+-----------+-------------------+-------------------+ | IdiotName | IQ | Nth Value | Current Minus Nth | Nth Minus Current | +-------------------+------+-----------+-------------------+-------------------+ | Dumbest | 30 | 40 | -10 | 10 | | Homer | 40 | 40 | 0 | 0 | | Patrick Star | 40 | 40 | 0 | 0 | | Ed | 40 | 40 | 0 | 0 | | Dumber | 50 | 40 | 10 | -10 | | Peter Griffin | 50 | 40 | 10 | -10 | | Cosmo | 55 | 40 | 15 | -15 | | Dumb | 60 | 40 | 20 | -20 | | Ralph Wiggum | 65 | 40 | 25 | -25 | | Richard Watterson | 70 | 40 | 30 | -30 | +-------------------+------+-----------+-------------------+-------------------+
Option 4: The FIRST_VALUE()
Function
The FIRST_VALUE()
function is similar to the above functions, except that it gets its value from the first row in the window frame. If there are no partitions, then this is the first row in the result set. Otherwise it’s the first row in the partition.
Example:
SELECT
IdiotName,
IQ,
FIRST_VALUE( IQ ) OVER win AS "First Value",
IQ - FIRST_VALUE( IQ ) OVER win AS "Current Minus First",
FIRST_VALUE( IQ ) OVER win - IQ AS "First Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );
Result:
+-------------------+------+-------------+---------------------+---------------------+ | IdiotName | IQ | First Value | Current Minus First | First Minus Current | +-------------------+------+-------------+---------------------+---------------------+ | Dumbest | 30 | 30 | 0 | 0 | | Homer | 40 | 30 | 10 | -10 | | Patrick Star | 40 | 30 | 10 | -10 | | Ed | 40 | 30 | 10 | -10 | | Dumber | 50 | 30 | 20 | -20 | | Peter Griffin | 50 | 30 | 20 | -20 | | Cosmo | 55 | 30 | 25 | -25 | | Dumb | 60 | 30 | 30 | -30 | | Ralph Wiggum | 65 | 30 | 35 | -35 | | Richard Watterson | 70 | 30 | 40 | -40 | +-------------------+------+-------------+---------------------+---------------------+
Option 5: The LAST_VALUE()
Function
The LAST_VALUE()
function is similar to the FIRST_VALUE()
function, except that it gets its value from the last row in the window frame.
Example:
SELECT
IdiotName,
IQ,
LAST_VALUE( IQ ) OVER win AS "Last Value",
IQ - LAST_VALUE( IQ ) OVER win AS "Current Minus Last",
LAST_VALUE( IQ ) OVER win - IQ AS "Last Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING );
Result:
+-------------------+------+------------+--------------------+--------------------+ | IdiotName | IQ | Last Value | Current Minus Last | Last Minus Current | +-------------------+------+------------+--------------------+--------------------+ | Dumbest | 30 | 70 | -40 | 40 | | Homer | 40 | 70 | -30 | 30 | | Patrick Star | 40 | 70 | -30 | 30 | | Ed | 40 | 70 | -30 | 30 | | Dumber | 50 | 70 | -20 | 20 | | Peter Griffin | 50 | 70 | -20 | 20 | | Cosmo | 55 | 70 | -15 | 15 | | Dumb | 60 | 70 | -10 | 10 | | Ralph Wiggum | 65 | 70 | -5 | 5 | | Richard Watterson | 70 | 70 | 0 | 0 | +-------------------+------+------------+--------------------+--------------------+
Here I used ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
to expand the range to the end of the result set. Omitting this produces the following result:
SELECT
IdiotName,
IQ,
LAST_VALUE( IQ ) OVER win AS "Last Value",
IQ - LAST_VALUE( IQ ) OVER win AS "Current Minus Last",
LAST_VALUE( IQ ) OVER win - IQ AS "Last Minus Current"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );
Result:
+-------------------+------+------------+--------------------+--------------------+ | IdiotName | IQ | Last Value | Current Minus Last | Last Minus Current | +-------------------+------+------------+--------------------+--------------------+ | Dumbest | 30 | 30 | 0 | 0 | | Homer | 40 | 40 | 0 | 0 | | Patrick Star | 40 | 40 | 0 | 0 | | Ed | 40 | 40 | 0 | 0 | | Dumber | 50 | 50 | 0 | 0 | | Peter Griffin | 50 | 50 | 0 | 0 | | Cosmo | 55 | 55 | 0 | 0 | | Dumb | 60 | 60 | 0 | 0 | | Ralph Wiggum | 65 | 65 | 0 | 0 | | Richard Watterson | 70 | 70 | 0 | 0 | +-------------------+------+------------+--------------------+--------------------+