在更新期间模拟 MySQL 中的 LAG
Simulating LAG in MySQL during an update
昨天有人询问 需要使用 LAG 更新 MySQL table。考虑以下输入 table(左)和所需的输出(右):
**INPUT** **OUTPUT**
ID TestDate PerformanceStatus (PS) ID TestDate PS PreviousPerformanceStatus
1 15/03/2016 0 1 15/03/2016 0 0
1 01/04/2016 2 1 01/04/2016 2 0
1 05/05/2016 1 1 05/05/2016 1 2
1 07/06/2016 1 1 07/06/2016 1 1
2 15/03/2016 0 2 15/03/2016 0 1
2 01/04/2016 2 2 01/04/2016 2 0
2 05/05/2016 1 2 05/05/2016 1 2
2 07/06/2016 3 2 07/06/2016 3 1
2 23/08/2016 1 2 23/08/2016 1 3
换句话说,目标是将之前记录中存在的值分配给PreviousPerformanceStatus
,顺序为ID
然后TestDate
。
@spencer7593 给出的已接受答案使用了相关子查询。然而,我首先想到的是使用用户变量。这是我的回答:
SET @lag = 0;
UPDATE yourTable
SET PreviousPerformanceStatus = @lag,
@lag:=PerformanceStatus
ORDER BY ID, TestDate
有人告诉我这个答案是 unstable,但我想知道是否有人可以解释 为什么 可能会出错,在那种情况下会发生什么,最后我们可以做些什么来在这里使用用户变量来模拟 LAG。
据我了解,以下 SELECT
查询完全没有问题:
SELECT PerformanceStatus,
@lag AS PreviousPerformanceStatus,
@lag:=PerformanceStatus
FROM yourTable
ORDER BY ID, TestDate
但是,在执行 UPDATE
时还有其他注意事项需要考虑。
我认为您不能在更新语句中设置变量。
这是我的推理-
鉴于此
drop table if exists t;
create table t (ID int, TestDate date, PerformanceStatus int, previousperformancestatus int);
insert into t values
(1 , '2016-03-15' , 0, null),
(1 , '2016-04-01' , 2, null),
(1 , '2016-05-05' , 1, null),
(1 , '2016-06-07' , 1, null),
(2 , '2016-03-15' , 0, null),
(2 , '2016-04-01' , 2, null),
(2 , '2016-05-05' , 1, null),
(2 , '2016-06-07' , 3, null),
(2 , '2016-08-23' , 1, null)
;
此代码失败
MariaDB [sandbox]> SET @lag = 0;
Query OK, 0 rows affected (0.00 sec)
MariaDB [sandbox]> UPDATE T
-> SET previousPerformanceStatus = @lag ,
-> @lag:=PerformanceStatus
-> ORDER BY ID, TestDate;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '@lag:=PerformanceStatus
ORDER BY ID, TestDate' at line 3
注释掉@lag:=PerformanceStatus
此代码运行
MariaDB [sandbox]> SET @lag = 0;
Query OK, 0 rows affected (0.00 sec)
MariaDB [sandbox]> UPDATE T
-> SET previousPerformanceStatus = @lag
-> #,@lag:=PerformanceStatus
-> ORDER BY ID, TestDate;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 9 Changed: 0 Warnings: 0
由于代码至少可以无错误地运行并且手册 https://dev.mysql.com/doc/refman/5.7/en/update.html 声明 "The SET clause indicates which columns to modify " 我对此的看法是您不能在更新语句中设置变量,因此使用此方法无法模拟滞后。
该线程中接受的答案是错误的。我发现 ant 测试的最好方法是使用 CTE(WITH 子句)并从 CTE 结果集中更新 table。
spencer7593 提出的 SELECT 可以工作,但效率很低(在我的例子中,更新三列花了一分钟)。 UPDATE 将失败并出现错误,告诉您无法更新您在 SET 目标的 FROM 子句中使用的 table。
另一种更有效的方法是将 CTE 与 LAG() 结合使用并从中获取更新的值。但是,如果没有其他简单或复合唯一键,您将需要一个唯一键用作 CTE 和您的 table 之间的连接键。
-- Create the table as per question
drop table if exists student;
create table if not exists student (
pk int auto_increment,
id int not null,
TestDate date not null,
PerformanceStatus int not null,
PreviousPerformanceStatus int null default null,
primary key (pk)
) engine=innodb;
insert into student(id, TestDate, PerformanceStatus, PreviousPerformanceStatus)
values (1, '2016-03-15', 0, null),
(1, '2016-04-01', 2, null),
(1, '2016-05-05', 1, null),
(1, '2016-06-07', 1, null),
(2, '2016-03-15', 0, null),
(2, '2016-04-01', 2, null),
(2, '2016-05-05', 1, null),
(2, '2016-06-07', 3, null),
(2, '2016-08-23', 1, null);
-- Update PreviousPerformanceStatus using lag()
with p as
(
select pk, id, testdate, performancestatus,
LAG(performancestatus, 1, 0) OVER (
PARTITION BY id
ORDER BY id, testdate asc
) as PreviousPerformanceStatus
from student
)
update student t
inner join p
on p.pk = t.pk
set
t.PreviousPerformanceStatus = p.PreviousPerformanceStatus;
您可以将 LAG() 的第三个参数替换为 null 而不是零。
我发现此解决方案是其他几个有效解决方案中最有效的。
昨天有人询问
**INPUT** **OUTPUT**
ID TestDate PerformanceStatus (PS) ID TestDate PS PreviousPerformanceStatus
1 15/03/2016 0 1 15/03/2016 0 0
1 01/04/2016 2 1 01/04/2016 2 0
1 05/05/2016 1 1 05/05/2016 1 2
1 07/06/2016 1 1 07/06/2016 1 1
2 15/03/2016 0 2 15/03/2016 0 1
2 01/04/2016 2 2 01/04/2016 2 0
2 05/05/2016 1 2 05/05/2016 1 2
2 07/06/2016 3 2 07/06/2016 3 1
2 23/08/2016 1 2 23/08/2016 1 3
换句话说,目标是将之前记录中存在的值分配给PreviousPerformanceStatus
,顺序为ID
然后TestDate
。
@spencer7593 给出的已接受答案使用了相关子查询。然而,我首先想到的是使用用户变量。这是我的回答:
SET @lag = 0;
UPDATE yourTable
SET PreviousPerformanceStatus = @lag,
@lag:=PerformanceStatus
ORDER BY ID, TestDate
有人告诉我这个答案是 unstable,但我想知道是否有人可以解释 为什么 可能会出错,在那种情况下会发生什么,最后我们可以做些什么来在这里使用用户变量来模拟 LAG。
据我了解,以下 SELECT
查询完全没有问题:
SELECT PerformanceStatus,
@lag AS PreviousPerformanceStatus,
@lag:=PerformanceStatus
FROM yourTable
ORDER BY ID, TestDate
但是,在执行 UPDATE
时还有其他注意事项需要考虑。
我认为您不能在更新语句中设置变量。 这是我的推理- 鉴于此
drop table if exists t;
create table t (ID int, TestDate date, PerformanceStatus int, previousperformancestatus int);
insert into t values
(1 , '2016-03-15' , 0, null),
(1 , '2016-04-01' , 2, null),
(1 , '2016-05-05' , 1, null),
(1 , '2016-06-07' , 1, null),
(2 , '2016-03-15' , 0, null),
(2 , '2016-04-01' , 2, null),
(2 , '2016-05-05' , 1, null),
(2 , '2016-06-07' , 3, null),
(2 , '2016-08-23' , 1, null)
;
此代码失败
MariaDB [sandbox]> SET @lag = 0;
Query OK, 0 rows affected (0.00 sec)
MariaDB [sandbox]> UPDATE T
-> SET previousPerformanceStatus = @lag ,
-> @lag:=PerformanceStatus
-> ORDER BY ID, TestDate;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '@lag:=PerformanceStatus
ORDER BY ID, TestDate' at line 3
注释掉@lag:=PerformanceStatus 此代码运行
MariaDB [sandbox]> SET @lag = 0;
Query OK, 0 rows affected (0.00 sec)
MariaDB [sandbox]> UPDATE T
-> SET previousPerformanceStatus = @lag
-> #,@lag:=PerformanceStatus
-> ORDER BY ID, TestDate;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 9 Changed: 0 Warnings: 0
由于代码至少可以无错误地运行并且手册 https://dev.mysql.com/doc/refman/5.7/en/update.html 声明 "The SET clause indicates which columns to modify " 我对此的看法是您不能在更新语句中设置变量,因此使用此方法无法模拟滞后。
该线程中接受的答案是错误的。我发现 ant 测试的最好方法是使用 CTE(WITH 子句)并从 CTE 结果集中更新 table。
spencer7593 提出的 SELECT 可以工作,但效率很低(在我的例子中,更新三列花了一分钟)。 UPDATE 将失败并出现错误,告诉您无法更新您在 SET 目标的 FROM 子句中使用的 table。
另一种更有效的方法是将 CTE 与 LAG() 结合使用并从中获取更新的值。但是,如果没有其他简单或复合唯一键,您将需要一个唯一键用作 CTE 和您的 table 之间的连接键。
-- Create the table as per question
drop table if exists student;
create table if not exists student (
pk int auto_increment,
id int not null,
TestDate date not null,
PerformanceStatus int not null,
PreviousPerformanceStatus int null default null,
primary key (pk)
) engine=innodb;
insert into student(id, TestDate, PerformanceStatus, PreviousPerformanceStatus)
values (1, '2016-03-15', 0, null),
(1, '2016-04-01', 2, null),
(1, '2016-05-05', 1, null),
(1, '2016-06-07', 1, null),
(2, '2016-03-15', 0, null),
(2, '2016-04-01', 2, null),
(2, '2016-05-05', 1, null),
(2, '2016-06-07', 3, null),
(2, '2016-08-23', 1, null);
-- Update PreviousPerformanceStatus using lag()
with p as
(
select pk, id, testdate, performancestatus,
LAG(performancestatus, 1, 0) OVER (
PARTITION BY id
ORDER BY id, testdate asc
) as PreviousPerformanceStatus
from student
)
update student t
inner join p
on p.pk = t.pk
set
t.PreviousPerformanceStatus = p.PreviousPerformanceStatus;
您可以将 LAG() 的第三个参数替换为 null 而不是零。 我发现此解决方案是其他几个有效解决方案中最有效的。