2011-07-02 41 views
23

比方说,我有这个(MySQL的)数据库,通过增加时间戳排序:SQL:选择行,其中列值从先前行改变

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(StatusA 2011-01-01 2011-01-03和之间改变系统A),2011-01-06,2011-01-07。

查询应具有的记录数万台快速执行。

感谢

+0

为什么'2011-01-07'返回和'2011-01-04'不是? –

+2

@Andriy:2011-01-04不会返回,因为状态A在2011-01-02和2011-01-04两者均可用(均适用于系统B)。 –

+0

@Jonathan:当然!谢谢。 –

回答

25
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 ; 
+0

第一个查询在大约13秒内执行。数据库中有少于5000条记录。 – Jimmy

+1

@Jimmy:第二个? –

+0

如果5K记录不是即时的,则不能使用索引。你的索引是什么样的? – dkretz

8
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是否则使用 。

这意味着,无论这些连接是可行的。

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

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

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

+0

非常好地完成! –

+2

'交叉连接B,A.x = B.x'?为什么不'A.x = B.x'上的内连接B?否则,确实很好! (+1) –

+0

@Andriy查看更新回答 – Jiri

1

这里有一个略短版本类似的逻辑。我经常测试这个,我确定它很高效。主要是因为它消除了相关的子查询(WHERE NOT EXISIS)。

“c”在那里以确保b直接低于 - 它说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; 
+2

dorfier:你的意思是:'LEFT JOIN tableX AS c ON a.System = c.System AND a.Timestamp> c.Timestamp AND c.Timestamp> b.Timestamp'? –

+0

嗯,我似乎无法得到这个查询来完成 - 输入它在phpmyadmin只是导致漫长的等待,并最终phpmyadmin返回到主屏幕。我修改了ypercube建议的查询。 – Jimmy

+0

你的索引是什么? – dkretz

5

使用ROWNUM

我有0.05秒在20000行

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 
+0

这个问题被标记为“mysql”,而rownum仅适用于Oracle Dbs。有没有一个与此相当的mysql? – Patrick

0

叶戈尔的答案工作我在MSSQL中做了一些小改动。曾与更换ROWNUM声明:

select row_number() over (order by TIMESTAMP) as R_NUM, ... 
相关问题