5 Ways to Get a Value from a Different Row in the Same Column in MySQL

MySQL includes some nonaggregate window functions that allow us to get a value from a specific row. We can use such functions to do things like, compare the value in the specified row with the value in the current row, even if both values are in the same column.

Below are five functions that we can use to do this.

Option 1: The LAG() Function

We can use the LAG() function to get the value from a previous row:

SELECT 
    IdiotName,
    IQ,
    LAG( IQ ) OVER win AS "Lag Value"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+-----------+
| IdiotName         | IQ   | Lag Value |
+-------------------+------+-----------+
| Dumbest           |   30 |      NULL |
| Homer             |   40 |        30 |
| Patrick Star      |   40 |        40 |
| Ed                |   40 |        40 |
| Dumber            |   50 |        40 |
| Peter Griffin     |   50 |        50 |
| Cosmo             |   55 |        50 |
| Dumb              |   60 |        55 |
| Ralph Wiggum      |   65 |        60 |
| Richard Watterson |   70 |        65 |
+-------------------+------+-----------+

By default, LAG() goes back one row. We can see this in the above example. The Lag Value column contains the value from the previous row in the IQ column. The first row is NULL because there’s no previous row in that case.

We can provide a second argument to specify how many rows back to go:

SELECT 
    IdiotName,
    IQ,
    LAG( IQ, 3 ) OVER win AS "Lag Value"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+-----------+
| IdiotName         | IQ   | Lag Value |
+-------------------+------+-----------+
| Dumbest           |   30 |      NULL |
| Homer             |   40 |      NULL |
| Patrick Star      |   40 |      NULL |
| Ed                |   40 |        30 |
| Dumber            |   50 |        40 |
| Peter Griffin     |   50 |        40 |
| Cosmo             |   55 |        40 |
| Dumb              |   60 |        50 |
| Ralph Wiggum      |   65 |        50 |
| Richard Watterson |   70 |        55 |
+-------------------+------+-----------+

Here, I used LAG( IQ, 3 ), which caused it to go back three rows.

Option 2: The LEAD() Function

The LEAD() function works similar to LAG(), except that it gets its value from a subsequent row:

SELECT 
    IdiotName,
    IQ,
    LEAD( IQ ) OVER win AS "Lead Value"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+------------+
| IdiotName         | IQ   | Lead Value |
+-------------------+------+------------+
| Dumbest           |   30 |         40 |
| Homer             |   40 |         40 |
| Patrick Star      |   40 |         40 |
| Ed                |   40 |         50 |
| Dumber            |   50 |         50 |
| Peter Griffin     |   50 |         55 |
| Cosmo             |   55 |         60 |
| Dumb              |   60 |         65 |
| Ralph Wiggum      |   65 |         70 |
| Richard Watterson |   70 |       NULL |
+-------------------+------+------------+

This time we can see that it takes the value from the next row. Similarly to LAG() we can provide a second argument to specify how many rows forward to take its value from:

SELECT 
    IdiotName,
    IQ,
    LEAD( IQ, 3 ) OVER win AS "Lead Value"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+------------+
| IdiotName         | IQ   | Lead Value |
+-------------------+------+------------+
| Dumbest           |   30 |         40 |
| Homer             |   40 |         50 |
| Patrick Star      |   40 |         50 |
| Ed                |   40 |         55 |
| Dumber            |   50 |         60 |
| Peter Griffin     |   50 |         65 |
| Cosmo             |   55 |         70 |
| Dumb              |   60 |       NULL |
| Ralph Wiggum      |   65 |       NULL |
| Richard Watterson |   70 |       NULL |
+-------------------+------+------------+

Of course, this results in the last three rows being NULL, due to the fact that there are no more rows to get a value from.

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:

SELECT 
    IdiotName,
    IQ,
    NTH_VALUE( IQ, 3 ) OVER win AS "Nth Value"
FROM Idiots
WINDOW win AS ( 
    ORDER BY IQ 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    );

Result:

+-------------------+------+-----------+
| IdiotName         | IQ   | Nth Value |
+-------------------+------+-----------+
| Dumbest           |   30 |        40 |
| Homer             |   40 |        40 |
| Patrick Star      |   40 |        40 |
| Ed                |   40 |        40 |
| Dumber            |   50 |        40 |
| Peter Griffin     |   50 |        40 |
| Cosmo             |   55 |        40 |
| Dumb              |   60 |        40 |
| Ralph Wiggum      |   65 |        40 |
| Richard Watterson |   70 |        40 |
+-------------------+------+-----------+

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.

Example:

SELECT 
    IdiotName,
    IQ,
    FIRST_VALUE( IQ ) OVER win AS "First Value"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+-------------+
| IdiotName         | IQ   | First Value |
+-------------------+------+-------------+
| Dumbest           |   30 |          30 |
| Homer             |   40 |          30 |
| Patrick Star      |   40 |          30 |
| Ed                |   40 |          30 |
| Dumber            |   50 |          30 |
| Peter Griffin     |   50 |          30 |
| Cosmo             |   55 |          30 |
| Dumb              |   60 |          30 |
| Ralph Wiggum      |   65 |          30 |
| Richard Watterson |   70 |          30 |
+-------------------+------+-------------+

If we partition the results, this will use the first row in each partition.

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"
FROM Idiots
WINDOW win AS ( 
    ORDER BY IQ 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 
    );

Result:

+-------------------+------+------------+
| IdiotName         | IQ   | Last Value |
+-------------------+------+------------+
| Dumbest           |   30 |         70 |
| Homer             |   40 |         70 |
| Patrick Star      |   40 |         70 |
| Ed                |   40 |         70 |
| Dumber            |   50 |         70 |
| Peter Griffin     |   50 |         70 |
| Cosmo             |   55 |         70 |
| Dumb              |   60 |         70 |
| Ralph Wiggum      |   65 |         70 |
| Richard Watterson |   70 |         70 |
+-------------------+------+------------+

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"
FROM Idiots
WINDOW win AS ( ORDER BY IQ );

Result:

+-------------------+------+------------+
| IdiotName         | IQ   | Last Value |
+-------------------+------+------------+
| Dumbest           |   30 |         30 |
| Homer             |   40 |         40 |
| Patrick Star      |   40 |         40 |
| Ed                |   40 |         40 |
| Dumber            |   50 |         50 |
| Peter Griffin     |   50 |         50 |
| Cosmo             |   55 |         55 |
| Dumb              |   60 |         60 |
| Ralph Wiggum      |   65 |         65 |
| Richard Watterson |   70 |         70 |
+-------------------+------+------------+