【问题标题】:Rolling average postgres滚动平均postgres
【发布时间】:2015-07-01 09:02:39
【问题描述】:

我正在运行 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
)

我不断有值进入系统,即每分钟很多。我想保持最后 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)

这是基于 http://jonisalonen.com/2014/efficient-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.

这样我就不必不断地获取最后 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;

但是我怎样才能从该 select 语句中获得要保存的最后一个值(最简单的)? 我可以从 PL/pgSQL 中的 s 访问第一行吗?

我认为 PL/pgSQL 会是更快/更清洁的方法,但也许更好的是客户端代码? 有没有更好的方法来执行这种滚动统计更新?

【问题讨论】:

  • avg(value) over (partition by sensor_id order by ts rows between 200 preceding and current row) as avg怎么样

标签: sql postgresql plpgsql window-functions


【解决方案1】:

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

CREATE INDEX i_sensor_values ON sensor_values(sensor_id, ts DESC);

您将能够很快得到结果:

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

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

我认为覆盖索引在这里不会有什么好处,因为表格在不断变化,IndexOnlyScan 不会起作用。

也可以检查Loose Index scans

附:列名value 应该用双引号引起来,因为这是一个SQL reserved word

【讨论】:

  • 嗯在做 SELECT sensor_id, value FROM sensor_values WHERE sensor_id=555 ORDER BY ts DESC LIMIT 200;花了两分钟,因为它必须订购所有数据。 1 秒缓存。 explain.depesz.com/s/DbN 执行 SELECT ts, value FROM sensor_values WHERE sensor_values.sensor_id = 540 AND ts >= (NOW() - INTERVAL '2 day')::timestamptz ORDER BY ts DESC LIMIT 200 需要 100 ms 。当我每分钟可以获得一千个新条目时,我认为这太开销了。我认为索引工作正常。也许我可以每 1000 个左右的条目使它成为非实时的。
  • @GlennPierce,你没有提到分区。当然,要获得适当的分区修剪,您需要添加适当的谓词。你能给出第二个查询的计划吗?
  • 计划第二次查询 explain.depesz.com/s/tXrD 不知道为什么要检查早些年的分区表
  • @GlennPierce,这是因为规划器不擅长分区键上的表达式。尝试预先计算该值(在客户端代码或 PL/pgSQL 函数中)并将其用作常量,例如 ts >= '2015-04-22'::timestamptz
猜你喜欢
  • 2013-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-18
相关资源
最近更新 更多