2011-05-24 42 views
4

我有一個非常大的MySQL表,其中包含從大量傳感器讀取的數據。本質上,有一個時間戳和一個值列。我就省略了傳感器ID,此指標的其他細節:如何高效地確定使用SQL的行之間的更改

CREATE TABLE `data` (
    `time` datetime NOT NULL, 
    `value` float NOT NULL 
) 

value列很少改變,我需要找點時間,當發生這些變化。假設有一個值每分鐘,下面的查詢返回正是我需要的:

SELECT d.*, 
    (SELECT value FROM data WHERE time<d.time ORDER by time DESC limit 1) 
    AS previous_value 
FROM data d 
HAVING d.value<>previous_value OR previous_value IS NULL; 

+---------------------+-------+----------------+ 
| time    | value | previous_value | 
+---------------------+-------+----------------+ 
| 2011-05-23 16:05:00 |  1 |   NULL | 
| 2011-05-23 16:09:00 |  2 |    1 | 
| 2011-05-23 16:11:00 | 2.5 |    2 | 
+---------------------+-------+----------------+ 

唯一的問題是,這是非常低效的,大多是由於相關子查詢。使用MySQL 5.1所提供的工具來優化這個最好的方法是什麼?

最後一個約束是這些值在它們被插入數據表之前沒有排序,並且它們可能在稍後的時間點被更新。這可能會影響任何可能的非標準化策略。

+0

表中有什麼索引? – 2011-05-24 11:55:23

+0

旁註:它有一個壞習慣,有一個名爲'time'或'datetime'或'date'或'float'的表或字段等。 – 2011-05-24 11:57:56

+0

@ypercube:除了本例中未顯示的列上的鍵(如合成的主鍵),時間列上有一個唯一的鍵。 – 2011-05-24 12:03:36

回答

3

你可以試試這個 - 我「M不會保證它會表現得更好,但它是我平時的一排‘前’行相關的方式:

SELECT 
    * --TODO, list columns 
FROM 
    data d 
     left join 
    data d_prev 
     on 
      d_prev.time < d.time --TODO - Other key columns? 
     left join 
    data d_inter 
     on 
      d_inter.time < d.time and 
      d_prev.time < d_inter.time --TODO - Other key columns? 
WHERE 
    d_inter.time is null AND 
    (d_prev.value is null OR d_prev.value <> d.value) 

(我認爲這是正確的 - 可以用一些樣本數據來有效的做吃了它)。

基本上,想法是將表加入自己,併爲每一行(在d)找到候選行(在d_prev)爲「上一個」行。然後進一步加入,試圖找到存在於當前行(在d)和候選行(在d_prev)之間的一行(在d_inter中)。如果我們找不到這樣的行(d_inter.time is null),那麼該候選人確實是上一行。

+0

太棒了!這實際上是我尋找的那種「技巧」。你查詢的幅度比原來的要快。它的速度還不夠快,但它可能是我需要的數據聚合的基礎。非常感謝你的回答。 – 2011-05-24 14:48:29

+0

如果沒有更好的解決方案出現,我會立即投票並在幾天內接受它。 – 2011-05-24 14:54:18

+0

我想你也可能在技術上需要或者在WHERE子句的最後括號內的語句中d.value爲null。 – user1383092 2016-04-22 10:01:49

3

我想這不是您切換數據庫引擎的選項。在情況下,它可能是,那麼window functions將允許你這樣寫:

SELECT d.* 
FROM (
    SELECT d.*, lag(d.value) OVER (ORDER BY d.time) as previous_value 
    FROM data d 
) as d 
WHERE d.value IS DISTINCT FROM d.previous_value; 

如果沒有,你可以嘗試重寫查詢,像這樣:

select data.* 
from data 
left join (
    select data.measure_id, 
      data.time, 
      max(prev_data) as prev_time 
    from data 
    left join data as prev_data 
    on prev_data.time < data.time 
    group by data.measure_id, data.time, data.value 
    ) as prev_data_time 
on prev_data_time.measure_id = data.measure_id 
and prev_data_time.time = data.time 
left join prev_data_value 
on prev_data_value.measure_id = data.measure_id 
and prev_data_value.time = prev_data_time.prev_time 
where data.value <> prev_data_value.value or prev_data_value.value is null 
+0

@Denis,請注意,'group by'已經對其中列出的元素進行了排序,所以不需要最後一個'。 – Johan 2011-05-24 12:26:14

+2

確實如此,但是這種排序是實現的副作用,而不是SQL標準。你永遠不知道MySQL什麼時候會放棄副作用(Oracle做的)。 :-) – 2011-05-24 12:29:29

+0

您也可以在'(value,time)'或'(sensor_id,value,time)'上試驗索引,並使用該索引查看查詢計劃。 – 2011-05-24 12:35:08

相關問題