Search code examples
mysqlsqlvariablesuser-defined

Getting values from previous rows using user-defined variable in MySQL


I have a table named as "t1", my table is-

+------+------+
| c1   |  c2  |
+------+------+
|  1   |   12 |
|  2   |   13 |
|  3   |   14 |
+------+------+

I want to get all rows along with the previous values of col2. The query I use:-

Select c1,@prev,@prev:=c2 from t1;

I get following output-

+------+---------+------------+
| c1   |  @prev  | @prev:=c2  |
+------+---------+------------+
|  1   |   NULL  |    12      |
|  2   |   NULL  |    13      |
|  3   |   NULL  |    14      |
+------+---------+------------+

I was expecting to get only NULL in the first row. But I am getting NULL in all rows. Please explain why it's giving NULL in all rows.


Solution

  • The reason for the NULLs is documented here:

    Another issue with assigning a value to a variable and reading the value within the same non-SET statement is that the default result type of a variable is based on its type at the start of the statement.

    Since @prev is not defined before it is read for the first time, we cannot even say, what type it has.

    Now you could preset it to some value like set @prev = 0;. But 0 might be a valid value, so you probably want it to be NULL for the first row. set @prev = null; will neither work. But you can do the following:

    set @prev = -1;   -- define type as SIGNED
    set @prev = null; -- set NULL
    

    Now you query should return

    c1  @prev   @prev:=c2
    1   null    12
    2   12      13
    3   13      14
    

    You can also define the type and assign NULL in a single statement:

    set @prev = cast(null as signed);
    

    See Demo

    However - When you read and write a user variable in the same statement (other than SET) - the behavior is not defined. In the documentation you will find the following statements:

    It is also possible to assign a value to a user variable in statements other than SET. (This functionality is deprecated in MySQL 8.0 and subject to removal in a subsequent release.)

    ...

    As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement.

    ...

    For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed.

    I've marked the important parts in bold, so you can see that it's not recommended to use the variables in that way.

    Newer versions (MySQL 8.0 and MariaDB 10.2) now support window functions like LEAD() and LAG(). So you can write the query the following way:

    Select c1, c2, lag(c2) over (order by c1) as prev
    from t1
    

    Demo

    This is not only reliable for future MySQL versions. It will also work on many (all major) RDBM-systems.