2011-07-02 40 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, ... 
相關問題