使用 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。
你可以试试下面的-
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
您可以使用子查询获取之前的值:
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
这不仅对未来的 MySQL 版本可靠。它也适用于许多(所有主要的)RDBM 系统。
我有一个名为 "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。
你可以试试下面的-
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
您可以使用子查询获取之前的值:
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
这不仅对未来的 MySQL 版本可靠。它也适用于许多(所有主要的)RDBM 系统。