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中都有第一个结果作为正确的行来选择。但是,工作日开始的时间到了,所以以后必须继续。