PostgreSQL 优化:日期范围内的平均值
PostgreSQL optimization: average over range of dates
我有一个查询(带有子查询)计算过去几年的平均温度,plus/minus 每天一个星期。它有效,但并不是那么快。下面的时间序列值只是一个例子。为什么我使用 doy
是因为我想在每年的同一日期前后滑动 window。
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
所以我的问题是,能否以某种方式改进此查询?我正在考虑使用某种 window 函数或 lag
和 lead
。然而,至少常规 window 函数仅适用于特定数量的行,而两周内可以有任意数量的测量 window.
我可以接受目前拥有的东西,但随着数据量的增长,查询的执行速度也会增加。后两个 extract
可以删除,但这没有明显的速度提升,只会使查询更难辨认。任何帮助将不胜感激。
原始查询的最佳索引是
create index idx_temperatures_site_id_timestamp_doy
on temperatures(site_id, extract(doy from timestamp));
这可以大大提高原始查询的性能。
虽然您的原始查询简单易读,但它有 1 个缺陷:它会计算每天的平均值 14 次(平均)。相反,您可以每天计算这些平均值并计算 2 周 window 的加权平均值(一天平均值的权重需要计算原始 table 中的各个行)。像这样:
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
开头的索引。使用 window 版本应该会提高性能。
我有一个查询(带有子查询)计算过去几年的平均温度,plus/minus 每天一个星期。它有效,但并不是那么快。下面的时间序列值只是一个例子。为什么我使用 doy
是因为我想在每年的同一日期前后滑动 window。
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
所以我的问题是,能否以某种方式改进此查询?我正在考虑使用某种 window 函数或 lag
和 lead
。然而,至少常规 window 函数仅适用于特定数量的行,而两周内可以有任意数量的测量 window.
我可以接受目前拥有的东西,但随着数据量的增长,查询的执行速度也会增加。后两个 extract
可以删除,但这没有明显的速度提升,只会使查询更难辨认。任何帮助将不胜感激。
原始查询的最佳索引是
create index idx_temperatures_site_id_timestamp_doy
on temperatures(site_id, extract(doy from timestamp));
这可以大大提高原始查询的性能。
虽然您的原始查询简单易读,但它有 1 个缺陷:它会计算每天的平均值 14 次(平均)。相反,您可以每天计算这些平均值并计算 2 周 window 的加权平均值(一天平均值的权重需要计算原始 table 中的各个行)。像这样:
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
开头的索引。使用 window 版本应该会提高性能。