2014-11-02 167 views
2

我有一個表,看起來像下面 - 主要包含時間戳以及其他一些列:最接近時間戳選擇行

WeatherTable 
+---------------------+---------+----------------+  + 
| TS     | MonthET | InsideHumidity | .... | 
+---------------------+---------+----------------+  | 
| 2014-10-27 14:24:22 |  0 |    54 |  | 
| 2014-10-27 14:24:24 |  0 |    54 |  | 
| 2014-10-27 14:24:26 |  0 |    52 |  | 
| 2014-10-27 14:24:28 |  0 |    54 |  | 
| 2014-10-27 14:24:30 |  0 |    53 |  | 
| 2014-10-27 14:24:32 |  0 |    55 |  | 
| 2014-10-27 14:24:34 |  9 |    54 |  | 
....... 

我試圖制定一個返回的所有行的SQL查詢在一定的時間範圍內(這裏沒有問題),具有一定的任意粒度,例如每15秒。該數字總是以秒爲單位指定,但不限於小於60的值。使事情進一步複雜化,時間戳不一定落在所需的粒度上,所以不是簡單地選擇14:24:00的時間戳,14:24:15,14:24:30等 - 結果中包含與每個值最接近的時間戳的行。

例如,如果開始時間被給定爲14時24分三十○秒,結束時間爲十四時32分00秒,和粒度爲130,理想的時間將是:

14:24:30 
14:26:40 
14:28:50 
14:31:00 

然而,那麼每個時間點的時間戳可能不存在,在這種情況下,應該選擇具有與這些理想時間戳中的每個理想時間戳最接近的時間戳的行。在兩個時間戳距離理想時間戳較遠的情況下,應該選擇較早的時間戳。

數據庫是Web服務的一部分,所以目前我只是忽略了SQL查詢的粒度,並在以後用(Java)代碼過濾不需要的結果。然而,這在內存消耗和性能方面似乎還不太理想。

任何想法?

+0

首先您需要建立顯示缺席值的規則。例如,假設您在2014-10-27 14:24:29需要InsideHumidity值。你會說它是54,53或53.5? – Horaciux 2014-11-02 23:27:46

+0

任意粒度總是以秒爲單位<= 60?而且,如果是這樣,如果我們有43,那麼43,83,126都算不算?或者,每43歲? – 2014-11-02 23:31:46

+0

該規則可能是,最後一次已知的價值,加權平均等。此外,您需要知道爲什麼你沒有這個時間戳的價值。是否在採樣頻率,死區內的值,通信錯誤,現場設備故障等方面存在差異。 – Horaciux 2014-11-02 23:35:30

回答

3

你可以嘗試做這樣的:

首先創建time_intervals的列表。從Get a list of dates between two dates使用存儲過程make_intervals創建臨時表調用它在某種程度上這樣的:

call make_intervals(@startdate,@enddate,15,'SECOND'); 

然後,您將有一個表time_intervals名爲interval_start兩列之一。使用此找到最接近時間戳每個區間不知何故像:

CREATE TEMPORARY TABLE IF NOT EXISTS time_intervals_copy 
    AS (SELECT * FROM time_intervals); 

SELECT 
    time_intervals.interval_start, 
    WeatherTable.* 
FROM time_intervals 
JOIN WeatherTable 
    ON WeatherTable.TS BETWEEN @startdate AND @enddate 
JOIN (SELECT 
     time_intervals.interval_start AS interval_start, 
     MIN(ABS(time_intervals.interval_start - WeatherTable.TS)) AS ts_diff 
     FROM time_intervals_copy AS time_intervals 
     JOIN WeatherTable 
     WHERE WeatherTable.TS BETWEEN @startdate AND @enddate 
     GROUP BY time_intervals.interval_start) AS min 
    ON min.interval_start = time_intervals.interval_start AND 
    ABS(time_intervals.interval_start - WeatherTable.TS) = min.ts_diff 
GROUP BY time_intervals.interval_start; 

這將找到最近的時間戳每TIME_INTERVAL。注:WeatherTable中的每一行可以多次列出,如果使用的時間間隔小於存儲數據間隔的一半(或類似的東西,你會得到該點;))。

注意:我沒有測試這些查詢,它們是從我的頭上寫的。請根據您的使用情況進行調整,糾正可能存在的小錯誤...

+0

+1,這比我以前想到的任何東西都好! – berry120 2014-11-02 23:47:16

+1

我添加了一個'WHERE'子句,這應該會帶來巨大的性能提升! – wolfgangwalther 2014-11-03 00:12:32

+0

嗯,由於某種原因,'min(abs('線似乎導致查詢返回一行 - 任何想法?) – berry120 2014-11-03 12:47:59

1

出於測試目的,我將您的數據集擴展到以下時間戳。我的數據庫中的列被稱爲time_stamp

2014-10-27 14:24:24 
2014-10-27 14:24:26 
2014-10-27 14:24:28 
2014-10-27 14:24:32 
2014-10-27 14:24:34 
2014-10-27 14:24:25 
2014-10-27 14:24:32 
2014-10-27 14:24:34 
2014-10-27 14:24:36 
2014-10-27 14:24:37 
2014-10-27 14:24:39 
2014-10-27 14:24:44 
2014-10-27 14:24:47 
2014-10-27 14:24:53 

我總結的想法,但讓我提供解決方案,我能找出以前更詳細地解釋。

要求是解決時間戳+/-給定的時間。既然我們必須走向任何一個方向,我們都會想要把時間段分成兩半。然後,時間範圍的-1/2定義爲時間範圍的+1/2,以定義要考慮的「箱」。

從給定的起始時間的@seconds間隔給定的時間窗通過這個MySQL的語句中給出:

((floor(((t1.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds) 

注:整+ 1招是有,這樣我們就不會結束與-1索引的bin(它將從零開始)。所有時間均從開始時間開始計算,以確保> = 60秒的時間表工作。

在每個bin中,我們需要知道每個時間範圍內bin的中心距離的大小。這是通過確定從開始的秒數和從箱中減去它(然後取絕對值)來完成的。

在這個階段,我們將把所有時間都「放在一起」,並在箱內排序。

要將這些結果過濾掉,我們將LEFT JOIN添加到同一張表並設置條件以刪除不需要的行。當編輯LEFT JOIN時,期望的行將在LEFT JOIN ed表中具有NULL匹配。

我比較喜歡使用變量替換開始,結束和秒,但僅用於可讀性。 MySQL樣式註釋包含在LEFT JOINON子句中,用於標識條件。

SET @seconds = 7; 
SET @time_start = TIMESTAMP('2014-10-27 14:24:24'); 
SET @time_end = TIMESTAMP('2014-10-27 14:24:52'); 

SELECT t1.* 
FROM temp t1 
LEFT JOIN temp t2 ON 
    #Condition 1: Only considering rows in the same "bin" 
    ((floor(((t1.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds) 
= ((floor(((t2.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds) 
AND 
(
    #Condition 2 (Part A): "Filter" by removing rows which are greater from the center of the bin than others 
    abs(
     (t1.time_stamp - @time_start) 
     - (floor(((t1.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds 
) 
    > 
    abs(
     (t2.time_stamp - @time_start) 
     - (floor(((t2.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds 
) 
    OR 
    #Condition 2 (Part B1): "Filter" by removing rows which are the same distance from the center of the bin 
    (
    abs(
     (t1.time_stamp - @time_start) 
     - (floor(((t1.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds 
    ) 
    = 
    abs(
     (t2.time_stamp - @time_start) 
     - (floor(((t2.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds 
    ) 
    #Condition 2 (Part B2): And are in the future from the other match 
    AND 
     (t1.time_stamp - @time_start) 
     > 
     (t2.time_stamp - @time_start) 
) 
) 
WHERE t1.time_stamp - @time_start >= 0 
AND @time_end - t1.time_stamp >= 0 
#Condition 3: All rows which have a match are undesirable, so those 
#with a NULL for the primary key (in this case temp_id) are selected 
AND t2.temp_id IS NULL 

有可能是編寫查詢更簡潔的方式,但它確實對結果進行篩選下來需要有一個明顯的例外什麼 - 我故意放在一個重複的條目。該查詢將返回兩個這樣的條目,因爲它們符合所述的標準。

+1

一定要添加一些解釋,當你完成 - 真的很想理解這個概念:) – wolfgangwalther 2014-11-03 02:25:04

+0

感謝您試圖讓這個工作 - 看起來像一個有趣的方法! – berry120 2014-11-03 09:10:49

+1

靠近。上面的查詢現在將每個bin中的第一個結果作爲要選擇的正確行。但是,開始工作的時間,所以稍後必須回升。 – 2014-11-03 15:48:30