【问题标题】:How to update an SQL table based on the previous data in the same table如何根据同一张表中以前的数据更新一个SQL表
【发布时间】:2023-03-20 20:09:01
【问题描述】:

我有一个表格可以衡量我的数据库中student 的学生表现,如下所示:

ID  TestDate    PerformanceStatus (PS)
1   15/03/2016  0
1   01/04/2016  2
1   05/05/2016  1
1   07/06/2016  1
2   15/03/2016  0
2   01/04/2016  2
2   05/05/2016  1
2   07/06/2016  3
2   23/08/2016  1

我想更新我的表以拥有一个新列 PreviousPerformanceStatus。 此 PreviousPerformanceStatus 是根据监控的 performanceStatus 计算得出的,如下所示: 注意:如果在TestDate之前没有记录performanceStatus,我想做PreviousPerformanceStatus = PerformanceStatus

ID  TestDate    PS  PreviousPerformanceStatus
1   15/03/2016  0   0
1   01/04/2016  2   0
1   05/05/2016  1   2
1   07/06/2016  1   1
2   15/03/2016  0   0
2   01/04/2016  2   0
2   05/05/2016  1   2
2   07/06/2016  3   1
2   23/08/2016  1   3

如何更新我的 SQL 表?我应该使用加入吗? 谢谢。

【问题讨论】:

  • 逻辑不清楚(或者您可能有错字)。你能解释一下规则是什么吗?
  • 这里有一个提示:首先,编写一个返回结果的查询(一个 SELECT 语句)。是的,JOIN 操作是实现结果的一种方法。或者,您可以使用相关子查询。一旦有了 SELECT 语句,就可以将其转换为 UPDATE 语句。
  • 我们以 ID=1 的学生为例。 previousPerformanceStatus 是根据“较早”测试日期的 PerformanceStatus 计算的。所以当 TestDate=01/04/2016 时,我想使用 TestDate=15/03/2016 的数据。但是,如果我找不到任何以前的数据,我将使用 PerformanceStatus 中的值默认 PreviousPerformanceStatus

标签: mysql date join


【解决方案1】:

假设 testdate 列是 DATE 数据类型(而不是 VARCHAR)

并假设 (id,testdate) 元组在 student 中是唯一的

我们可以在 SELECT 列表中使用相关子查询。举个例子:

 SELECT t.id
      , t.testdate
      , t.performancestatus
      , ( SELECT p.performancestatus
            FROM student p
           WHERE p.id = t.id
             AND p.testdate < t.testdate
           ORDER BY p.testdate DESC
           LIMIT 1
        ) AS previousperformancestatus
 FROM student t
ORDER BY t.id, t.testdate

一旦我们确认 SELECT 语句为我们提供了我们正在寻找的结果,我们就可以将其转换为 UPDATE 语句。可以作为内联视图,也可以直接使用相关子查询。

UPDATE student t
   SET t.previousperformancestatus
       = ( SELECT p.performancestatus
            FROM student p
           WHERE p.id = t.id
             AND p.testdate < t.testdate
           ORDER BY p.testdate DESC
           LIMIT 1
        )

如果testdate不是 DATE 数据类型,或者不是以规范格式存储,那么“小于”比较不能保证限制行到“较早”的测试日期。并且“order by”不保证首先返回最近的“较早”的测试日期。

对于“第一个”测试日期,当没有更早的测试日期时,子查询将返回 NULL。我们可以使用表达式将 NULL 值转换为 0。我们可以将子查询包装在一个函数中,IFNULL( &lt;subquery&gt; ,0)

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多