选择具有最接近时间戳的行

时间:2014-11-02 23:09:59

标签: mysql sql

我有一个类似于以下内容的表 - 基本上包含时间戳以及其他一些列:

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:30,结束时间为14:32:00,粒度为130,则理想时间为:

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

但是,对于每个时间,可能不存在时间戳,在这种情况下,应该选择与每个理想时间戳具有最接近时间戳的行。如果两个时间戳同样远离理想时间戳,则应选择较早的时间戳。

数据库是Web服务的一部分,所以目前我只是忽略SQL查询中的粒度并稍后在(Java)代码中过滤掉不需要的结果。但是,就内存消耗和性能而言,这似乎并不理想。

有什么想法吗?

2 个答案:

答案 0 :(得分: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中的每一行都可以列出多次,如果使用的间隔小于存储数据的间隔的一半(或类似的东西,则得到点;) )。

注意:我没有测试查询,它们是从我的头脑中写的。请根据您的使用情况进行调整,并纠正可能存在的轻微错误......

答案 1 :(得分: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定义了一个" bin"考虑。

此MySQL语句给出了在给定开始时间内以@seconds为间隔的给定时间的bin:

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

注意:整个+ 1技巧是存在的,所以我们不会以-1索引的bin结束(它从零开始)。所有时间都是从开始时间计算出来的,以确保时间范围> = 60秒。

在每个bin中,我们需要知道每个时间帧与bin中心距离的大小。通过确定从开始起的秒数并从bin中减去它(然后取绝对值)来完成。

在这个阶段,我们随时都有#34; bining up"并在垃圾箱内订购。

要过滤掉这些结果,我们LEFT JOIN到同一个表并设置条件以删除不需要的行。 LEFT JOIN时,所需的行在NULL ed表中将匹配LEFT JOIN

我更喜欢使用变量替换开头,结尾和秒,但仅限于可读性。 MySQL风格的注释包含在LEFT JOIN ON子句中,用于标识条件。

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

可能有一种更简洁的方式来编写查询,但它确实将结果过滤到了需要的,但有一个值得注意的例外 - 我故意放入一个重复的条目。此查询将返回这两个条目,因为它们符合所述标准。