【问题标题】:SQL: selecting rows where column value changed from previous rowSQL:选择列值从上一行更改的行
【发布时间】:2011-07-02 22:44:41
【问题描述】:

假设我有这个(MySQL)数据库,按时间戳递增排序:

Timestamp   System StatusA StatusB 
2011-01-01     A      Ok     Ok      
2011-01-02     B      Ok     Ok     
2011-01-03     A     Fail   Fail     
2011-01-04     B      Ok    Fail     
2011-01-05     A     Fail    Ok      
2011-01-06     A      Ok     Ok      
2011-01-07     B     Fail   Fail    

对于该系统,如何选择 StatusA 从上一行更改的行? StatusB 无关紧要(我在这个问题中展示它只是为了说明每个系统可能有许多连续的行,而 StatusA 不会改变)。在上面的示例中,查询应返回 2011-01-03 行(对于 SystemA,StatusA 在 2011-01-01 和 2011-01-03 之间更改)、2011-01-06、2011-01-07。

查询应该在具有数万条记录的表中快速执行。

谢谢

【问题讨论】:

  • 为什么2011-01-07会被退回而2011-01-04不会?
  • @Andriy: 2011-01-04 未返回,因为状态 A 在 2011-01-02 和 2011-01-04 上都正常(都适用于系统 B)。
  • @Jonathan:当然!谢谢。

标签: mysql sql


【解决方案1】:
SELECT a.*
FROM tableX AS a
WHERE a.StatusA <>
      ( SELECT b.StatusA
        FROM tableX AS b
        WHERE a.System = b.System
          AND a.Timestamp > b.Timestamp
        ORDER BY b.Timestamp DESC
        LIMIT 1
      ) 

但是你也可以试试这个(在(System,Timestamp) 上有一个索引:

SELECT System, Timestamp, StatusA, StatusB
FROM
  ( SELECT (@statusPre <> statusA AND @systemPre=System) AS statusChanged
         , System, Timestamp, StatusA, StatusB
         , @statusPre := StatusA
         , @systemPre := System
    FROM tableX
       , (SELECT @statusPre:=NULL, @systemPre:=NULL) AS d
    ORDER BY System
           , Timestamp
  ) AS good
WHERE statusChanged ;

【讨论】:

  • 第一个查询在大约 13 秒内执行。数据库中的记录略少于 5000 条。
  • 如果 5K 记录不是瞬时的,则不能使用索引。您的索引是什么样的?
  • System、StatusA、TimeStamp 上有一个索引...当您问“索引是什么样的”时,您想知道什么。它们是紫色的,带有粉红色的圆点 :)
  • @ypercube:我不知道那些 @, := 是做什么的——所以我必须在尝试第二个之前弄清楚这一点:)
  • 甜蜜!第二个查询在 0.0172 秒内执行!
【解决方案2】:
select a.Timestamp, a.System, a.StatusA, a.StatusB
from tableX as a
cross join tableX as b
where a.System = b.System
and a.Timestamp > b.Timestamp
and not exists (select * 
    from tableX as c
    where a.System = c.System
    and a.Timestamp > c.Timestamp
    and c.Timestamp > b.Timestamp
)
and a.StatusA <> b.StatusA;

更新解决评论: 为什么不使用内部联接而不是交叉联接?

该问题要求提供 MySQL 解决方案。根据documentation

在 MySQL 中,CROSS JOIN 是一种句法 相当于 INNER JOIN(他们可以 互相替换)。在标准 SQL 中, 它们不等价。 INNER JOIN 是 与 ON 子句一起使用时,CROSS JOIN 是 以其他方式使用。

这意味着这些连接中的任何一个都可以工作。

与 ON 一起使用的 conditional_expr 是 形式的任何条件表达式 可以在 WHERE 子句中使用。 通常,您应该使用 ON 用于指定如何的条件的子句 连接表和 WHERE 子句 限制你想要的行 结果集。

条件 a.System = b.System 可能属于“如何连接表”类别,因此在这种情况下使用 INNER JOIN 会更好。

由于两者产生相同的结果,差异可能在于性能。要说哪个更快,我需要知道连接是如何在内部实现的——它们是使用索引还是散列来进行连接。

【讨论】:

  • A cross join B where A.x = B.x?为什么不A inner join B on A.x = B.x?不然真的很好看! (+1)
  • 我确信 MySQL 足够聪明,能够注意到 CROSS JOIN + WHERE join_conditionINNER JOIN … ON join_condition 之间的相似性。 INNER JOIN 对我来说似乎更自然,然后是我的问题。否则,我认为您的原始版本与修订后的版本一样正确和有效。感谢您不厌其烦地将此扩展解释添加到您的答案中!
  • @Andriy:我认为句法等价意味着CROSS JOIN ... WHERE join_condition 将产生与JOIN ... WHERE join_condition 相同的计划。而CROSS JOIN ... ON join_condition(非标准SQL)和JOIN ... ON join_condition一样。我同意让所有 4 个版本产生相同的计划是足够聪明的。
  • 嗯,我也无法完成此查询;结果与 le dorfier 的建议相同。在 phpmyadmin 中输入它只会导致漫长的等待,最终 phpmyadmin 会返回主屏幕。这是在 Host Gator 帐户上,通常很快。
  • 我不知道为什么这个答案会得到如此多的支持——ypercube 的答案(带有 @ 变量)是迄今为止最快的,至少根据我的测试。
【解决方案3】:

使用行号

我在 20000 行上有 0.05 秒

select a1.*
  from (select rownum R_NUM, TIMESTAMP, System, StatusA from TableX) a1 
  join (select rownum R_NUM, TIMESTAMP, SYSTEM, STATUSA from TABLEX) a2 
    on a1.R_NUM = a2.R_NUM+1 
 where a1.system = a2.system 
   and a1.StatusA != a2.StatusA

【讨论】:

  • 问题标记为mysql,rownum 仅适用于Oracle Dbs。有没有相当于这个的mysql?
  • a1.R_NUM = a2.R_NUM+1 听起来好像无法捕获最后一行,以及恰好只有一行的情况。
【解决方案4】:

这是一个略短的版本,具有类似的逻辑。我经常对此进行测试,我确信它是有效的;主要是因为它消除了相关的子查询(WHERE NOT EXISIS)。

“c”在那里确保 b 直接低于 a - 它说 c(在它们之间)无法找到(通过 NULL 测试)。

SELECT a.Timestamp, a.System, a.StatusA, a.StatusB
FROM tableX AS a
JOIN tableX AS b
    ON a.System = b.System
    AND a.Timestamp > b.Timestamp
LEFT JOIN tableX AS c
    ON a.System = b.System
    AND a.Timestamp > c.Timestamp
    AND b.Timestamp < c.Timestamp
WHERE c.System IS NULL
    AND a.StatusA <> b.StatusA;

【讨论】:

  • dorfier:你不是说:LEFT JOIN tableX AS c ON a.System = c.System AND a.Timestamp &gt; c.Timestamp AND c.Timestamp &gt; b.Timestamp 吗?
  • 嗯,我似乎无法完成这个查询——在 phpmyadmin 中输入它只会导致漫长的等待,最终 phpmyadmin 会返回主屏幕。我按照 ypercube 的建议修改了查询。
  • @le dorfier:我喜欢这种方法:需要哪些索引才能获得良好的性能?我在我的机器上尝试了各种没有好的结果。当行数变为 2 倍时,时间大约变为 4 倍(我的第一个查询和 Jiri 的查询也是如此)。
  • @le dorfier:另一个想法。您之前是否在 MySQL 中使用过它或仅在其他系统中使用过它?也许 MySQL 优化器不够聪明,无法为此找到一个好的计划。
  • 不,我指的是 MySQL。如果您只有时间戳索引,它将直接索引到记录,几乎没有重复值的机会。可能的最高基数 - 您根本不需要索引中的任何其他字段。
【解决方案5】:

Egor 的回答在 MSSQL 中对我有用,只需稍作改动。必须将 ROWNUM 语句替换为:

select row_number () over (order by TIMESTAMP) as R_NUM, ...

【讨论】:

    【解决方案6】:
    SELECT   a.*
    FROM    (select row_number() over (partition by System order by Timestamp asc) as aRow, Timestamp, System, StatusA, StatusB from tableX) as a
    left join (select row_number() over (partition by System order by Timestamp asc) as bRow, Timestamp, System, StatusA, StatusB from tableX) as b on a.aRow = b.bRow + 1 and a.System = b.System 
    where (a.StatusA != b.StatusA or b.StatusA is null)
    

    它将返回第一行和值不同的行。

    【讨论】:

      猜你喜欢
      • 2019-10-13
      • 2012-06-23
      • 2020-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 2021-04-23
      相关资源
      最近更新 更多