4

我有三个表,设置如下:

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 不是很好,所以我确定我错过了正确的方法。我应该如何处理这个问题?

4

3 回答 3

0

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

我假设您已经在时区列上有索引 - 目前它们不会因为表扫描问题而有所帮助。

有许多选项可以避免这种情况,具体取决于您的需求和数据收集率。

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

  1. 您需要临时数据的重要性级别 - 天、小时、分钟等。仅将时区信息存储到该重要性级别(或有另一列)并对此进行查询。
  2. 如果您知道 3 个壁橱时间将在某个时间范围内(小时、天等),则放入 where 子句以将计算限制在可能的候选时间。您正在有效地构建直方图类型的存储桶 - 您将需要一个日历表来有效地执行此操作。
  3. 进行单向比较,即仅将考虑限制在您要查找的时间之后的那些时间,因此,如果您要查找 12:00:00,则 13:45:32 是候选时间,但 11:59:59 不是。

我了解您要完成的工作-问问自己为什么以及更简单的解决方案是否可以满足您的需求。

于 2013-02-03T23:55:10.453 回答
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 不同)。

于 2013-02-04T02:39:36.063 回答
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,分别表示在或之前的最佳时间和在或之后的最佳时间。您可以在选择中使用逻辑来选择最接近的值。使用索引返回温度的连接应该是有效的。

但是,我要重申,我认为最后一个温度或之前的温度可能是最好的选择。

于 2013-02-04T03:17:53.333 回答