滚动平均postgres

Gle*_*rce 5 sql postgresql plpgsql window-functions

我正在运行Postgres 9.2,并且有一张大桌子,例如

CREATE TABLE sensor_values
(
  ts timestamp with time zone NOT NULL,
  value double precision NOT NULL DEFAULT 'NaN'::real,
  sensor_id integer NOT NULL
)
Run Code Online (Sandbox Code Playgroud)

我有不断进入系统的价值,即每分钟很多。我想为最近的200个值保持滚动标准偏差/平均值,以便可以确定进入系统的新值是否在平均值的3个标准偏差之内。为此,我需要当前的标准偏差,并且要为最近的200个值进行不断更新。由于表可能有数亿行,因此我不想按时间顺序为传感器获取最后说的200行,然后对传入的每个新值执行vg(value),var_samp(value)。将更快地更新标准偏差和均值。

我已经开始编写PL / pgSQL函数来更新滚动方差,并针对进入系统的每个特定传感器的新值取平均值。

我可以使用伪代码来做到这一点

newavg = oldavg + (new_value - old_value)/window_size
new_variance += (new_value-old_value)*(new_value-newavg+old_value-oldavg)/(window_size-1)
Run Code Online (Sandbox Code Playgroud)

这是基于 http://jonisalonen.com/2014/efficiency-and-accurate-rolling-standard-deviation/

基本上,该窗口的大小为200个值。old_value是窗口的第一个值。当出现新值时,我们将窗口向前移动一个。得到结果后,我为传感器存储以下值

The first value of the window.
The mean average of the window values.
The variance of the window values.
Run Code Online (Sandbox Code Playgroud)

这样我就不必不断地获得最后的200个值并进行求和等操作,当一个新的传感器值进入时我可以重用这个值。

我的问题是,第一次运行时,我没有传感器的先前窗口数据,即上面的三个值,因此我必须以较慢的方式进行操作。

就像是

WITH s AS
        (SELECT value FROM sensor_values WHERE sensor_values.sensor_id = $1  AND ts >= (NOW() - INTERVAL '2 day')::timestamptz ORDER BY ts DESC LIMIT 200)
    SELECT avg(value), var_samp(value)  INTO last_window_average, last_window_variance FROM s;
Run Code Online (Sandbox Code Playgroud)

但是,如何从该select语句中获取要保存的最后一个值(最有效)?我可以从PL / pgSQL中的s访问第一行。

我以为PL / pgSQL将是更快/更干净的方法,但是最好这样做是客户端代码?是否有更好的方法在滚动统计信息更新上执行此类型?

vye*_*rov 0

我认为,每次使用适当的索引重新计算最新 200 个条目不会太慢。如果你要做一个索引,比如:

CREATE INDEX i_sensor_values ON sensor_values(sensor_id, ts DESC);
Run Code Online (Sandbox Code Playgroud)

您将能够相当快地获得结果:

SELECT sum("value") -- add more expressions as required
  FROM sensor_values
 WHERE sensor_id=$1
 ORDER BY ts DESC
 LIMIT 200;
Run Code Online (Sandbox Code Playgroud)

您可以在函数的循环中执行此查询PL/pgSQL。如果您很快就会迁移到 9.3(或更高版本),您还可以使用LATERAL联接来实现此目的。

我认为覆盖索引在这里不会有什么好处,因为表在不断变化并且IndexOnlyScan不会生效。

检查松散索引扫描也很好。

PS 列名value应该用双引号引起来,因为这是SQL 保留字