Mysql 选择时间戳最接近的行

Mysql 选择时间戳最接近的行,mysql,sql,Mysql,Sql,我有一个类似于以下内容的表-基本上包含一个时间戳以及一些其他列: WeatherTable +---------------------+---------+----------------+ + | TS | MonthET | InsideHumidity | .... | +---------------------+---------+----------------+ | | 2014-10-27 14:24:22 |

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

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)代码中过滤掉不需要的结果。然而,就内存消耗和性能而言,这似乎远远不够理想


有什么想法吗?

你可以试着这样做:

首先创建时间间隔列表。使用存储过程
make_interval
from创建一个临时表,以如下方式调用它:

call make_intervals(@startdate,@enddate,15,'SECOND');
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;
然后,您将有一个名为
interval\u start
的两列之一的表
time\u interval
。使用此选项可以找到与每个间隔最近的时间戳,如下所示:

call make_intervals(@startdate,@enddate,15,'SECOND');
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;
这将找到与每个时间间隔最近的时间戳。注意:
WeatherTable
中的每一行可以列出不止一次,如果使用的间隔小于存储数据间隔的一半(或者类似的,您就得到了点;)


注意:我没有测试这些查询,它们是从我的脑袋里写出来的。请根据您的用例进行调整并纠正可能存在的小错误…

出于测试目的,我将您的数据集扩展到以下时间戳。我的数据库中的列称为
time\u 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”。 从给定开始时间开始,间隔
@seconds
的给定时间的bin由以下MySQL语句给出:

((floor(((t1.time_stamp - @time_start) - (@seconds/2))/@seconds) + 1) * @seconds)
注意:整个+1技巧都在那里,因此我们不会以-1索引的bin结束(它将从零开始)。所有时间均从开始时间开始计算,以确保>=60秒的工作时间

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

在这一阶段,我们将一直“装箱”,并在箱子内订购

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

我用变量替换了开始、结束和秒,但只是为了可读性。MySQL风格的注释包含在识别条件的
左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

可能有一种更简洁的方式来编写查询,但它确实将结果过滤到所需的内容,但有一个明显的例外——我特意添加了一个重复条目。此查询将返回这两个条目,因为它们确实满足所述条件。

首先,您需要建立规则以显示缺少的值。例如,假设您需要2014-10-27 14:24:29上的InsideHumiddy值。您会说它是54、53或53.5?任意粒度总是以秒为单位,规则可以是,最后一个已知值,加权平均值,等等。此外,您需要知道为什么没有这个timestam的值。采样频率、死区内的值、通信错误、现场设备故障等是否存在差异@JeremyMiller始终以秒为单位指定,但可以大于60。无论什么“计数”都是最接近理想值的——我已经更新了这个问题(希望)让它更清楚。谢谢。我想我正在接近一个数学解。将很快更新。+1,这比我以前想出的任何东西都好!我添加了一个
WHERE
子句,这将大大提高性能!嗯,出于某种原因,
min(abs(
行似乎导致查询只返回一行-有什么想法吗?嗯..是的。虽然主要概念是正确的,但我有点混淆了。必须放入另一个子查询。现在应该更近了!:)啊,我明白了-现在我认为子查询导致临时表出现问题(获取
无法重新打开表:'time_interval'
错误。)请确保在完成后添加某种解释-我真的很想理解这个概念:)感谢您尝试让这个工作-看起来是一个有趣的方法!越来越近了。上面的查询现在在每个bin中都有第一个结果作为正确的行来选择。但是,工作日开始的时间到了,所以以后必须继续。