使用PostgreSQL优化去除峰值

时间:2018-11-13 11:11:15

标签: postgresql optimization timescaledb

我想直接从使用TimescaleDB存储在PostgreSQL-DB中的数据中删除峰值。

我的数据存储为间隔1秒的值,我希望获得5分钟的平均值,而不会出现峰值。

我使用标准偏差确定尖峰,并排除距离固定zscore多的所有数据。

因此,第一步是获取与分析相关的所有数据(数据过滤后),然后计算每个5分钟数据块的平均值和标准差(avg_and_stddev_per_interval),然后将初始数据(数据过滤后)与计算出的平均值和stddev,排除所有不符合我的条件的值,并最终计算出最终的5分钟平均值而不出现峰值。

with data_filtered as (
    select ts, value
    from schema.table 
    where some_criteria = 42 
    and ts >= '2018-11-12 10:00:00'
    and ts < '2018-11-13 10:00:00'
), 
avg_and_stddev_per_interval as (
    select time_bucket('5 minutes', ts) as five_min,
    avg(value) as avg_value,
    stddev(value) as stddev_value,
    from data_filtered
    group by five_min   
)
select 
    time_bucket('5 minutes', ts) as tb,
    avg(value) as value,
from data_filtered
left join avg_and_stddev_per_interval 
    on data_filtered.ts >= avg_and_stddev_per_interval.five_min 
    and data_filtered.ts < avg_and_stddev_per_interval.five_min + interval '5 minutes'
    where abs((value-avg_value)/stddev_value) < 1 
    group by tb;

一切正常,但是速度非常慢。请求没有任何分组(select * from data_filtered)的完整数据并在本地计算我的条件要快得多。但是,我想减少数据量,所以这种方法在这种情况下是不可能的。

有什么方法可以加快查询速度吗?

3 个答案:

答案 0 :(得分:0)

eurotrash'注释导致更快的代码如下:

COALESCE()

在这里,我摆脱了仅出于可读性考虑而存在的所有CTE。

尽管如此,这仍然比不要求去除尖峰的平均值要慢8倍。

解释分析:

select 
    time_bucket('5 minutes', ts) as tb, avg(value) as value
from schema.table   
left join (
    select time_bucket('5 minutes', ts) as five_min,
        avg(value) as value,
        stddev(value) as stddev_value,
        from schema.table
        where some_criteria = 42
        and ts >= '2018-11-12 00:00:00'
        and ts < '2018-11-13 00:00:00'
        group by five_min
    ) as fm
    on ts >= fm.five_min 
    and ts < fm.five_min + interval '5 minutes'         
where some_criteria = 42
    and ts >= '2018-11-12 00:00:00'
    and ts < '2018-11-13 00:00:00'
    and abs((value-avg_value)/stddev_value) < 1     
group by tb;

答案 1 :(得分:0)

最简单的方法是用(临时)视图替换CTE零件。这将使优化程序可以改组并重新组装查询部分。


CREATE TEMP VIEW data_filtered as
    SELECT ts, value
    FROM schema.table
    WHERE some_criteria = 42
    AND ts >= '2018-11-12 10:00:00'
    AND ts < '2018-11-13 10:00:00'
        ;

CREATE TEMP VIEW avg_and_stddev_per_interval as
    SELECT time_bucket('5 minutes', ts) as five_min
    , avg(value) as avg_value
    , stddev(value) as stddev_value
    FROM data_filtered
    GROUP BY 1
        ;

SELECT
    time_bucket('5 minutes', ts) as tb
    , avg(value) as value
FROM data_filtered df
LEFT JOIN avg_and_stddev_per_interval  av
    ON df.ts >= av.five_min
    AND df.ts < av.five_min + interval '5 minutes'
    WHERE abs((value-avg_value)/stddev_value) < 1
    GROUP BY 1
        ;

答案 2 :(得分:0)

似乎最糟糕的性能发生在JOIN中(根据答案中的查询而不是问题中的查询)。理想情况下,当子查询返回很多结果时,您就不会加入该子查询,但是根据您的条件,我看不出如何避免它。

这是我的建议:

  1. 子查询结果放入临时表
  2. 临时表已建立索引
  3. 联接是在临时表上执行的
  4. 将所有这些封装在一个函数中

现在我通常讨厌这样做,因为我不喜欢创建临时表,但是有时候它确实确实为您提供了其他方法无法实现的最佳性能。 (不是说不能用另一种方法来做,但是我想不出一种更好的性能方法。)

是这样的:

CREATE OR REPLACE FUNCTION schema.my_function()
    RETURNS TABLE (tb SOMETYPE, avg NUMERIC) AS
$BODY$
BEGIN
    CREATE TEMP TABLE fm ON COMMIT DROP AS
        select time_bucket('5 minutes', ts) as five_min,
            avg(value) as value,
            stddev(value) as stddev_value
        from schema.table
        where some_criteria = 42
        and ts >= '2018-11-12 00:00:00'
        and ts < '2018-11-13 00:00:00'
        group by five_min;

    CREATE INDEX ON fm (five_min);

    RETURN time_bucket('5 minutes', ts), avg(value)
    from schema.table
    left join fm
        on ts >= fm.five_min 
        and ts < fm.five_min + interval '5 minutes'
    where some_criteria = 42
    and ts >= '2018-11-12 00:00:00'
    and ts < '2018-11-13 00:00:00'
    and abs((value-avg_value)/stddev_value) < 1     
    group by tb;
END
$BODY$
    LANGUAGE plpgsql;

很明显,我创建的索引只是基于您发布的查询中的示例,尽管我看到实际的查询包含其他内容,所以您希望对要连接的任何字段建立索引。

我称tb的类型为SOMETYPE,因为我不知道time_bucket返回什么类型。当然,您可以传递查询中应该可变为参数的任何部分。