【问题标题】:Simulating LAG in MySQL during an update在更新期间在 MySQL 中模拟 LAG
【发布时间】:2017-07-06 19:46:42
【问题描述】:

昨天有人问interesting question,需要使用 LAG 更新 MySQL 表。考虑以下输入表(左)和所需的输出(右):

**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

有人告诉我这个答案是不稳定的,但我想知道是否有人可以解释为什么可能会出错,在这种情况下会发生什么,最后我们可以做些什么来使用用户变量在这里模拟 LAG。

据我了解,以下SELECT 查询完全没有问题:

SELECT PerformanceStatus,
       @lag AS PreviousPerformanceStatus,
       @lag:=PerformanceStatus
FROM yourTable
ORDER BY ID, TestDate

但是,在执行UPDATE 时,还需要考虑其他因素。

【问题讨论】:

  • 更新语句抛出 1064 错误,所以这个问题有点没有实际意义。
  • @P.Salmon 有什么办法可以解决这个错误吗?
  • 错误开启,@lag:=PerformanceStatus,我认为您不能在更新语句中设置变量 - 所以不能。
  • @P.Salmon 如果您想将其发布为答案,我很乐意将其标记为正确。如果是这样,那么这将关闭尝试在更新时使用变量模拟 MySQL 中的分析函数。

标签: mysql lag


【解决方案1】:

我认为您不能在更新语句中设置变量。 这是我的推理- 鉴于此

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 声明“SET 子句指示要修改的列”,我对此的看法是您不能在更新语句中设置变量,因此无法使用此方法模拟延迟.

【讨论】:

  • 感谢您的研究。如果您预计对具有 DML 查询的分析功能有强烈的需求,这可能是避开 MySQL 的一个原因。
【解决方案2】:

该线程中接受的答案是错误的。我发现 ant 测试的最佳方法是使用 CTE(WITH 子句)并从 CTE 结果集中更新表。

由 spencer7593 提出的 SELECT 可以工作,但效率很低(在我的例子中,更新三列需要一分钟)。 UPDATE 将失败并显示一个错误,告诉您无法更新您在 SET 目标的 FROM 子句中使用的表。

另一种更有效的方法是使用带有 LAG() 的 CTE 并从中获取更新的值。但是,如果没有其他简单或复合唯一键,您将需要一个唯一键用作 CTE 和您的表之间的连接键。

-- 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 而不是零。 我发现这个解决方案是其他几个工作解决方案中最有效的。

【讨论】:

    猜你喜欢
    • 2020-06-14
    • 1970-01-01
    • 2016-12-15
    • 2017-02-16
    • 2012-03-24
    • 1970-01-01
    • 2012-07-11
    • 2016-05-16
    相关资源
    最近更新 更多