根据时间从多个表中获取最接近数据的最快方法

时间:2013-02-03 23:15:40

标签: mysql sql time-series

我有三张桌子,设置如下:

TEMPERATURE_1
  time
  zone (FK)
  temperature
TEMPERATURE_2
  time
  zone (FK)
  temperature
TEMPERATURE_3
  time
  zone (FK)
  temperature

每个表中的数据会定期更新,但不一定同时更新(即时间条目不相同)。

我希望每次能够访问每张表中最接近的读数,即:

TEMPERATURES
  time
  zone (FK)
  temperature_1
  temperature_2
  temperature_3

换句话说,对于我的三个表中的每个唯一时间,我想在TEMPERATURES表中有一行,其中temperature_n值是每个原始表中与时间最接近的温度读数。

目前,我已经使用两种观点进行了设置:

create view temptimes
as select time, zone 
  from temperature_1
union
  select time, zone
  from temperature_2
union
  select time, zone
  from temperature_3;

create view temperatures
as select tt.time,
          tt.zone,
          (select temperature 
           from temperature_1
           order by abs(timediff(time, tt.time))
           limit 1) as temperature_1,
          (select temperature 
           from temperature_2
           order by abs(timediff(time, tt.time))
           limit 1) as temperature_2,
          (select temperature 
           from temperature_3
           order by abs(timediff(time, tt.time))
           limit 1) as temperature_3,
from temptimes as tt
order by tt.time;

这种方法有效,但在生产中使用速度太慢(对于每个温度约1000条记录的小数据集,需要几分钟)。

我对SQL不太满意,所以我确定我错过了正确的方法。我该如何处理这个问题?

3 个答案:

答案 0 :(得分:0)

这很慢的原因是它需要3次表扫描来计算和排序差异。

我假设您已经在时区列上拥有索引 - 目前他们无法帮助解决表扫描问题。

根据您的需要和数据收集率,有许多选项可以避免这种情况。

您已经说过,数据是定期收集的,但不是同时收集的。这表明了一些选择。

  1. 您需要临时数据的重要程度 - 日,小时,分钟等。仅将时区信息存储到该重要级别(或者使用其他列)并对其进行查询
  2. 如果您知道3个壁橱时间将在某个时间范围内(小时,天等)放入一个where子句,将计算限制为潜在候选人的时间。您正在有效地构建直方图类型桶 - 您将需要一个日历表来有效地执行此操作。
  3. 使比较单向,即将考虑时间限制在您寻找的时间之后,因此如果您正在寻找12:00:00,那么13:45:32是候选人,但是11:59:59 isn'吨。
  4. 我理解你想要完成的事情 - 问问自己为什么以及一个更简单的解决方案是否能满足你的需求。

答案 1 :(得分:0)

昂贵的部分是相关子查询必须为每个temperature_*表的每一行计算时间差,以便为一个一个最近的行>主查询中一行行的列。

如果您可以根据索引在当前时间之前选择一个行并且一个行,那么速度会快得多计算这两个候选人的时差。您需要的只是表格中time 列的索引。

我忽略了专栏zone,因为它的作用在问题中仍然不清楚,它只会给核心问题增加更多噪音。应该很容易添加到查询中。

如果没有其他视图,此查询会立即执行所有操作:

SELECT time
      ,COALESCE(temp1
            ,CASE WHEN timediff(time, time1a) > timediff(time1b, time) THEN
                (SELECT t.temperature
                 FROM   temperature_1 t
                 WHERE  t.time = y.time1b)
             ELSE
                (SELECT t.temperature
                 FROM   temperature_1 t
                 WHERE  t.time = y.time1a)
             END) AS temp1

      ,COALESCE(temp2
            ,CASE WHEN timediff(time, time2a) > timediff(time2b, time) THEN
                (SELECT t.temperature
                 FROM   temperature_2 t
                 WHERE  t.time = y.time2b)
             ELSE
                (SELECT t.temperature
                 FROM   temperature_2 t
                 WHERE  t.time = y.time2a)
             END) AS temp2

      ,COALESCE(temp3
            ,CASE WHEN timediff(time, time3a) > timediff(time3b, time) THEN
                (SELECT t.temperature
                 FROM   temperature_3 t
                 WHERE  t.time = y.time3b)
             ELSE
                (SELECT t.temperature
                 FROM   temperature_3 t
                 WHERE  t.time = y.time3a)
             END) AS temp3
FROM  (
  SELECT time
        ,max(t1) AS temp1
        ,max(t2) AS temp2
        ,max(t3) AS temp3

        ,CASE WHEN max(t1) IS NULL THEN
           (SELECT t.time FROM temperature_1 t
            WHERE  t.time < x.time
            ORDER  BY t.time DESC LIMIT 1) ELSE NULL END AS time1a
        ,CASE WHEN max(t1) IS NULL THEN
           (SELECT t.time FROM temperature_1 t
            WHERE  t.time > x.time
            ORDER  BY t.time      LIMIT 1) ELSE NULL END AS time1b

        ,CASE WHEN max(t2) IS NULL THEN
           (SELECT t.time FROM temperature_2 t
            WHERE  t.time < x.time
            ORDER  BY t.time DESC LIMIT 1) ELSE NULL END AS time2a
        ,CASE WHEN max(t2) IS NULL THEN
           (SELECT t.time FROM temperature_2 t
            WHERE  t.time > x.time
            ORDER  BY t.time      LIMIT 1) ELSE NULL END AS time2b

        ,CASE WHEN max(t3) IS NULL THEN
           (SELECT t.time FROM temperature_3 t
            WHERE  t.time < x.time
            ORDER  BY t.time DESC LIMIT 1) ELSE NULL END AS time3a
        ,CASE WHEN max(t3) IS NULL THEN
           (SELECT t.time FROM temperature_3 t
            WHERE  t.time > x.time
            ORDER  BY t.time      LIMIT 1) ELSE NULL END AS time3b
  FROM  (
      SELECT time, temperature AS t1, NULL AS t2, NULL AS t3 FROM temperature_1
      UNION ALL
      SELECT time, NULL AS t1, temperature AS t2, NULL AS t3 FROM temperature_2
      UNION ALL
      SELECT time, NULL AS t1, NULL AS t2, temperature AS t3 FROM temperature_3
      ) AS x
  GROUP BY time
  ) y
ORDER BY time;

->sqlfiddle

解释

suqquery x 替换您的视图temptimes并将温度带入结果中。如果所有三个表都是同步的并且具有所有相同时间点的温度,则其余的甚至不需要并且非常快 对于三个表中没有一个没有行的每个时间点,按照指示获取温度:从每个表中取出“最接近”的温度。

suqquery y 根据当前时间聚合x中的行并获取上一次(time1a)和下一次(time1b)每个表缺少温度。这些查找应该使用索引快速。

最终查询从行中获取温度,其中每个温度最接近实际丢失的时间。

如果MySQL允许从当前子查询之上的多个级别引用列,则此查询可能更简单。它不能。在 PostgreSQL ->sqlfiddle

中工作得很好

如果可以从相关子查询中返回多个列,这也会更简单,但我不知道如何在MySQL中执行此操作。

使用 CTE 窗口函数会更简单,但MySQL不知道这些现代SQL功能(与其他相关的不同) RDBMS)。

答案 2 :(得分:0)

我的建议是你没有采取最接近的时间,但你是在特定时间或之前的第一次。原因很简单:通常,给定时间的数据是当时已知的数据。在大多数情况下,纳入未来信息通常不是一个好主意。

通过此更改,您可以修改查询以利用time上的索引。查询索引的问题在于该函数排除了索引的使用。

因此,如果您想要最近的温度,请使用此代替每个变量:

      (select temperature 
       from temperature_1 t2
       where t2.time <= tt.time
       order by t2.time desc
       limit 1
      ) as temperature_1,

实际上,您也可以像这样构建它:

      (select time 
       from temperature_1 t2
       where t2.time <= tt.time
       order by t2.time desc
       limit 1
      ) as time_1,

然后加入温度信息。使用索引,这将是有效的。

考虑到这一点,您实际上可以有两个变量time_1_beforetime_1_after,以获得最佳时间或之前的最佳时间以及最佳时间。您可以使用select中的逻辑来选择最接近的值。使用索引可以有效地恢复温度。

但是,我会重申,我认为最后的温度可能是最好的选择。