PostgreSQL优化:平均日期范围

时间:2017-05-23 19:01:41

标签: postgresql optimization moving-average

我有一个查询(带有子查询),用于计算前几年的平均温度,每天加/减一周。它有效,但速度并不快。下面的时间序列值只是一个例子。为什么我使用doy是因为我想在每年的同一天左右推出一个滑动窗口。

SELECT days,
    (SELECT avg(temperature)
     FROM temperatures
     WHERE site_id = ? AND
      extract(doy FROM timestamp) BETWEEN
      extract(doy FROM days) - 7 AND extract(doy FROM days) + 7
    ) AS temperature
FROM generate_series('2017-05-01'::date, '2017-08-31'::date, interval '1 day') days

所以我的问题是,这个查询能以某种方式得到改善吗?我正在考虑使用某种窗口函数,或者可能laglead。但是,至少常规窗口函数仅适用于特定数量的行,而在两周窗口内可以有任意数量的测量。

我现在可以使用我现有的东西,但随着数据量的增长,查询的执行速度也会增加。可以删除后两个extract,但这没有明显的速度提升,只会使查询不太清晰。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:1)

原始查询的最佳索引是

create index idx_temperatures_site_id_timestamp_doy
  on temperatures(site_id, extract(doy from timestamp));

这可以大大提高原始查询的效果。

虽然您的原始查询很简单&可读,它有1个缺陷:它会计算每天平均14次(平均)。相反,您可以每天计算这些平均值&计算2周窗口的加权平均值(一天平均值的权重需要是原始表中各行的计数)。像这样:

with p as (
  select timestamp '2017-05-01' min,
         timestamp '2017-08-31' max
)
select     t.*
from       p
cross join (select     days, sum(sum(temperature)) over pn1week / sum(count(temperature)) over pn1week
            from       p
            cross join generate_series(min - interval '1 week', max + interval '1 week', interval '1 day') days
            left join  temperatures on site_id = ? and extract(doy from timestamp) = extract(doy from days)
            group by   days
            window     pn1week as (order by days rows between 7 preceding and 7 following)) t
where      days between min and max
order by   days

然而,这里获得的收益并不多,因为这只是原始查询的两倍(具有最佳索引)。

http://rextester.com/JCAG41071

备注:我使用timestamp,因为我认为您的列类型为timestamp。但事实证明,您使用timestamptz(又名。timestamp with time zone)。使用该类型,您无法为extract(doy from timestamp)表达式编制索引,因为that expression's output is dependent of the actual client's time zone setting

对于timestamptz,请使用(至少)以site_id开头的索引。使用窗口版本无论如何都应该提高性能。

http://rextester.com/XTJSM42954