在postgresql中返回指数移动平均快照的最快方法

时间:2015-07-08 22:52:18

标签: postgresql function

我正在收集和绘制数据,我需要做的一件事就是计算一些数据的指数移动平均值。我将我的数据存储在postgres中。

基于我读过的另一个堆栈页面(How to calculate an exponential moving average on postgres?),我有以下功能。

CREATE OR REPLACE FUNCTION ema_func(
state double precision,
inval double precision,
alpha double precision)
RETURNS double precision AS
$BODY$
begin
  return case
     when state is null then inval
     else alpha * inval + (1-alpha) * state
     end;
end
$BODY$
LANGUAGE plpgsql VOLATILE

然后我使用像这样的聚合将它们放在一起:

CREATE AGGREGATE ema(double precision, double precision) (
  SFUNC=ema_func,
  STYPE=float8
);

我正在绘制股票信息,所以在某一天我有大约7000-8000条数据。我不需要所有这些信息来绘制数据图形(取决于我的窗口设置,1个像素可能值得大约60秒),所以我想每隔n秒提取一次我的数据快照。我写了这个函数来为我做这件事,这给我节省了一些时间。

CREATE OR REPLACE FUNCTION emasnapshots(
    ptable varchar,
    timestart timestamptz,
    timeend timestamptz,
    duration double precision,
    psymbol varchar,
    alpha double precision)
    returns setof timevalue as
$BODY$
    DECLARE
        localstart timestamptz;
        localend timestamptz;
        timevalues timevalue%rowtype;
        groups int := ceil((SELECT EXTRACT(EPOCH FROM (timeend - timestart))) / duration);
    BEGIN
        EXECUTE 'CREATE TEMP TABLE allemas ON COMMIT DROP AS select datetime, ema(value, ' || quote_literal(alpha) || ') over (order by datetime asc) from ' || quote_ident(ptable) || ' where symbol = ' || quote_literal(psymbol) || ' and datetime >= ' || quote_literal(timestart) || ' and datetime <= ' || quote_literal(timeend);
        FOR i in 1 .. groups LOOP
            localStart := timestart + (duration * (i - 1) * interval '1 second');
            localEnd := timestart + (duration * i * interval '1 second');
            EXECUTE 'select * from allemas where datetime >= ' || quote_literal(localstart) || ' and datetime <= ' || quote_literal(localend) || ' order by datetime desc limit 1' into timevalues;
            return next timevalues;
        end loop;
    return;
    END
$BODY$
LANGUAGE plpgsql VOLATILE

使用

运行我的EMA
select datetime::timestamptz, ema(value, 0.0952380952380952380952380952381 /* alpha */) over (order by datetime asc) from "4" where symbol = 'AAPL' and datetime >= '2015-07-01 7:30' and datetime <= '2015-07-01 14:00:00'

需要大约1.5秒来收集所有数据(7733行)并将其推送到互联网上(我的数据处于另一种状态)

运行我用

编写的emasnapshot函数
select start, average from emasnapshots ('4', '2015-07-01 9:30-4', '2015-07-01 16:00-4', 60, 'AAPL', 0.0952380952380952380952380952381);
为了清楚起见,

需要大约0.5秒来收集所有数据并将其推送到互联网(390行)顺便说一句。我是从桌子上拉出来的#4; 4&#34;在股票市场时间7月1日我想要每60秒拍摄一次快照。最后一个数字是我的alpha,这意味着我正在计算20秒的emas(alpha = 2 /(period + 1))

我的问题是,我能以最快的方式做到这一点吗?有没有办法告诉我的功能哪个部分是较慢的部分?就像临时表创建或抓取快照部分一样?我应该以不同的方式选择间隔中的最近日期吗?我应该从原始表格(按时间编制索引)中选择我的间隔中的最新时间并将其与我新创建的表格连接起来吗?

我刚开始写一周前写的postgres函数。我意识到我新创建的表没有编入索引,因此可能需要更长的时间才能完成与日期相关的操作,就像我要求它做的那样。有没有解决的办法?我正在处理大量具有大量不同符号的数据,因此我不确定为所有可能性创建电子邮件表是个好主意。我不想将所有数据都删除并在本地进行处理,因为如果图形软件有多天打开,那么很容易包含35,000行,这些行必须转移然后处理。

是的,我不认为它是索引速度或类似的东西,因为我可以运行:

select * from "4" where symbol = 'AAPL' and datetime >= '2015-07-01 07:30' and datetime <= '2015-07-01 14:00' order by datetime asc limit 450

并通过互联网获得150毫秒以下的回复。显然,这方面的处理方式较少。

非常感谢您的时间!

根据PATRICK&#39;答案编辑。

我现在有了下面的查询,我从Patrick所说的内容进行了修改:

SELECT datetime, ema FROM (
    SELECT datetime, ema, rank() OVER (PARTITION BY bucket ORDER BY datetime DESC) = 1 as rank
        FROM (
        SELECT datetime, ema(value, 0.0952380952380952380952380952381) OVER (ORDER BY datetime ASC) AS ema, 
            ceil(extract(epoch from (datetime - '2015-07-01 7:30')) / 60) AS bucket
        FROM "4" 
        WHERE symbol = 'AAPL' 
        AND datetime BETWEEN '2015-07-01 7:30' AND '2015-07-01 14:00' ) x ) y
WHERE rank = true;

因为我收到的错误是我无法在where子句中放置一个rank语句所以我把它拆分成一个不同的select语句,我这样做了吗?有三个选择语句对我来说很奇怪,但我是一个SQL新手,并试图学习,因为我可能没那么糟糕。

我对上述查询的解释声明如下所示。

Subquery Scan on y  (cost=6423.35..6687.34 rows=4062 width=16)
  Filter: y.rank
   ->  WindowAgg  (cost=6423.35..6606.11 rows=8123 width=24)
    ->  Sort  (cost=6423.35..6443.65 rows=8123 width=24)
          Sort Key: x.bucket, x.datetime
          ->  Subquery Scan on x  (cost=5591.23..5895.85 rows=8123 width=24)
                ->  WindowAgg  (cost=5591.23..5814.62 rows=8123 width=16)
                      ->  Sort  (cost=5591.23..5611.54 rows=8123 width=16)
                            Sort Key: "4".datetime
                            ->  Bitmap Heap Scan on "4"  (cost=359.99..5063.74 rows=8123 width=16)
                                  Recheck Cond: (((symbol)::text = 'AAPL'::text) AND (datetime >= '2015-07-01 07:30:00-06'::timestamp with time zone) AND (datetime <= '2015-07-01 14:00:00-06'::timestamp with time zone))
                                  ->  Bitmap Index Scan on "4_pkey"  (cost=0.00..357.96 rows=8123 width=0)
                                        Index Cond: (((symbol)::text = 'AAPL'::text) AND (datetime >= '2015-07-01 07:30:00-06'::timestamp with time zone) AND (datetime <= '2015-07-01 14:00:00-06'::timestamp with time zone))

1 个答案:

答案 0 :(得分:1)

首先,关于您的功能的一些小问题:

  • 除了字符串之外,您不必quote_literal()Bobby Tables不可能通过double precisiontimestamp参数注入您的SQL语句。
  • 在动态SQL语句中,您只需手动拼接表名和列名;可以使用USING子句注入参数值。这节省了大量的解析时间。
  • 在循环外移动尽可能多的计算。例如:
DECLARE
  ...
  dur_int interval := duration * interval '1 second';
  localStart timestamptz := timestart;
  localEnd timestamptz := localStart + dur_int;
BEGIN
  ...
  FOR i in 1 .. groups LOOP
    ...
    localStart := localStart + dur_int;
    localEnd := localEnd + dur_int;
  END LOOP;
  ...

但这真的很无聊......

在您的代码中,首先使用7733行数据填充临时表,然后在运行390次循环中使用动态查询一次从中提取一条记录。一切都非常非常浪费。您可以只用一个语句替换整个函数体:

RETURN QUERY EXECUTE format('SELECT datetime, ema '
   'FROM ('
     'SELECT datetime, ema, '
            'rank() OVER (PARTITION BY bucket ORDER BY datetime DESC) AS rank '
     'FROM ('
       'SELECT datetime, ema(value, $1) OVER (ORDER BY datetime ASC) AS ema, '
              'ceil(extract(epoch from (datetime - $2)) / $3) AS bucket '
       'FROM %I ' 
       'WHERE symbol = $4 '
         'AND datetime BETWEEN $2 AND $5) x '
     'WHERE rank = 1) y '
   'ORDER BY 1', ptable) USING alpha, timestart, duration, psymbol, timeend;

这里的原则是在最里面的查询中你计算&#34;桶&#34;表中每个处理过的行都会落入其中。在下一级查询中,您可以根据datetime计算每个存储桶中所有行的等级。然后在主查询中,从每个存储桶中选择最近一行,即rank = 1

速度:您应该对服务器上的所有查询进行EXPLAIN ,而不是在包含网络传输时间的客户端上进行测量。