使用 MySQL 中的用户定义变量从前几行获取值

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

我有一个名为 "t1" 的 table,我的 table 是-

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

我想获取所有行以及 col2 的先前值。 我使用的查询:-

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

我得到以下输出-

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

我原以为第一行只得到 NULL。但是我在所有行中都得到 NULL。 请解释为什么它在所有行中都给出 NULL。

你可以试试下面的-

DEMO

SET @prev=-1;
Select c1,@prev prev_col,@prev:=c2 cur_col
from t1 order by c1

输出:

c1  prev_col    cur_col
1     -1          12
2     12          13
3     13          14

您获得所有 NULL 值的原因是您没有初始化 @prev 变量。您可以使用 SET @prev := 0 语句初始化它,或者,如果您只需要通过单个查询获得此结果,则可以使用 CROSS JOIN 来初始化您的变量:

SELECT c1, @prev AS prev, @prev:=c2 AS c2
FROM t1
CROSS JOIN (SELECT @prev := 0) v;

输出:

c1  prev    c2
1   0       12
2   12      13
3   13      14

如果您没有使用变量,并且您的c1值是连续的,您也可以使用[=20=得到相同的结果]:

SELECT t1.c1, t1.c2, t2.c2 AS prev
FROM t1
LEFT JOIN t1 t2 ON t2.c1 = t1.c1 - 1
ORDER BY t1.c1

如果您的 c1 值可能不连续,上述 LEFT JOIN 将无法正常工作。在这种情况下,您需要 JOIN c1 的值,该值小于要 JOIN 编辑到的值的最大值:

SELECT t1.c1, t1.c2, t2.c2 AS prev
FROM t1
LEFT JOIN t1 t2 ON t2.c1 = (SELECT MAX(c1) 
                            FROM t1 t3 
                            WHERE t3.c1 < t1.c1)
ORDER BY t1.c1

在这两种情况下,输出是相同的:

c1  c2  prev
1   12  null
2   13  12
3   14  13

Demo on dbfiddle

您可以使用子查询获取之前的值:

    SET @def = 0;

    Select 
      t.c1,
      coalesce(
        (select c2 from tablename where c1 < t.c1 order by c1 desc limit 1), 
        @def) prev_col,
      t.c2 cur_col
    from tablename t order by t.c1;

查看 demo 或者:

SET @def = 0;

Select 
  t.c1,
  coalesce(
    (select max(c2) from tablename where c1 < t.c1), 
    @def) prev_col,
  t.c2 cur_col
from tablename t order by t.c1;

demo

记录 NULL 的原因 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.

由于@prev在第一次读取之前没有定义,我们甚至不能说它是什么类型。

现在您可以将其预设为某个值,例如 set @prev = 0;。但是 0 可能是一个有效值,因此您可能希望它的第一行为 NULL。 set @prev = null; 都不起作用。但您可以执行以下操作:

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

现在你查询应该return

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

您还可以在单​​个语句中定义类型并分配 NULL:

set @prev = cast(null as signed);

Demo

但是 - 当您在同一语句(SET 除外)中读取和写入用户变量时 - 行为未定义。在文档中您会发现以下语句:

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.

我把重要的部分用粗体标出来了,大家可以看到不建议这样使用变量。

较新的版本(MySQL 8.0 和 MariaDB 10.2)现在支持 window functions like LEAD() and LAG()。所以你可以这样写查询:

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

Demo

这不仅对未来的 MySQL 版本可靠。它也适用于许多(所有主要的)RDBM 系统。