SQL:选择列值从上一行更改的行

时间:2011-07-02 22:44:42

标签: mysql sql

假设我有这个(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(2011A-01和2011-01-03之间针对SystemA更改的StatusA),2011-01-06,2011-01-07。

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

由于

6 个答案:

答案 0 :(得分:33)

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 ;

答案 1 :(得分:10)

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解决方案。根据{{​​3}}:

  

在MySQL中,CROSS JOIN是一种语法   相当于INNER JOIN(他们可以   互相替换)。在标准SQL中   他们不等同。 INNER JOIN是   与ON子句一起使用,CROSS JOIN是   否则使用。

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

  

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

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

由于两者产生相同的结果,因此差异可能在于性能。要说哪个更快,我需要知道如何在内部实现连接 - 无论是使用索引还是哈希来进行连接。

答案 2 :(得分:6)

使用rownum

我在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

答案 3 :(得分:1)

这是一个略短的版本,具有类似的逻辑。我经常测试这个,我确信它很有效;主要是因为它消除了相关的子查询(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;

答案 4 :(得分:0)

Egor的回答对我来说在MSSQL中有一个很小的改变。必须用以下代码替换ROWNUM语句:

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

答案 5 :(得分:0)

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)

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