【问题标题】:Quickest way to return snapshots of Exponential Moving Average in postgresql在 postgresql 中返回指数移动平均线快照的最快方法
【发布时间】:2015-09-27 02:30:04
【问题描述】:

我正在收集和绘制数据,我需要做的一件事是为我的一些数据计算指数移动平均值。我的数据存储在 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);

为了清楚起见,收集所有数据并将其推送到互联网(390 行)大约需要 0.5 秒。我在 7 月 1 日的股市交易时间从表“4”中撤出,我想要每 60 秒拍摄一次快照。最后一个数字是我的 alpha,这意味着我正在计算 20 秒 emas (alpha = 2/(period + 1))

我的问题是,我是否以最快的方式执行此操作?有没有办法判断我的功能的哪一部分是较慢的部分?就像是创建临时表还是抓取快照部分?我应该以不同的方式以不同的方式选择最近的日期吗?我是否应该从原始表(按时间编制索引)中选择间隔中的最新时间并将其与新创建的表连接起来?

我大约一周前才开始编写 postgres 函数。我意识到我新创建的表没有被索引,因此像我要求它做的那样做与日期相关的事情可能需要更长的时间。有没有解决的办法?我正在处理具有许多不同符号的大量数据,因此我不确定为所有可能性创建 ema 表是一个好主意。我不想吸收所有数据并在本地进行处理,因为如果绘图软件有多天的开放时间,那很容易包含 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 的回答编辑。

我现在有了下面的查询,我根据 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 语句,我这样做对吗?拥有三个 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))

【问题讨论】:

  • 是的,这看起来不错,但rankinteger,所以你应该使用rank = 1。在EXPLAIN 输出中,您会看到最终成本是 6423.35..6687.34。如果您按自己的方式工作,您会发现大部分费用都发生在Bitmap Heap Scan on "4" (cost=359.99..5063.74 ...;即选择符合条件的行。将索引放在(symbol, datetime) 上会更好(但插入行会更慢,所以权衡你的选择)。出于好奇,您可以发布原始查询的EXPLAIN 吗?
  • @Patrick "rank() OVER (PARTITION BY bucket ORDER BY datetime DESC) = 1" 被作为布尔值输入,如果我放弃 1 那么它给了我排名,我现在意识到 = 1 是因为 where 语句并相应地对其进行了修改 - 谢谢:)。当我对我的函数进行解释时,我只得到“对 emasnapshotsold 的函数扫描(成本 = 0.25..10.25 行 = 1000 宽度 = 16)”不知道如何让它更详细。?我的主键是 (symbol, datetime) 所以我认为会自动为此创建一个索引?

标签: postgresql function


【解决方案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;

这里的原则是,在最里面的查询中,您计算​​表中每个已处理的行将落入的“桶”。在下一级查询中,您根据datetime 计算每个存储桶中所有行的排名。然后,在主查询中,您从每个存储桶中选择最近的一行,即 rank = 1 所在的行。

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

【讨论】:

  • 我相信我有这个权利,我将我的返回更改为“记录”并将你的返回查询执行格式语句放在 BEGIN 和 END 之间(经过一些谷歌搜索)当我现在运行它时,我得到“错误:在 WHERE SQL 状态中不允许使用窗口函数:42P20 上下文:PL/pgSQL 函数 emasnapshots(字符变化,时区时间戳,时区时间戳,双精度,字符变化,双精度)第 3 行返回查询“我试过在函数之外手动运行查询我得到了第 8 行:WHERE rank() OVER (PARTITION BY bucket ORDER BY datetime DE...
  • 顺便说一句,感谢您向我解释存储桶的事情。我开始研究一些其他的功能,我现在意识到它们的编写效率并没有达到应有的水平。我通过嵌套三个 select 语句消除了关于 where rank() 的错误,但我对 sql 真的一无所知,所以我用我的新查询和解释语句编辑了问题的底部,我希望你不要'不介意再帮忙一次。我不想成为一个坐着等人解决他的问题的人
  • 啊,是的,我的错。我制作复杂查询的方式遗留下来的,如果这是任何借口的话。答案已更正。
  • 在编写复杂查询时,您使用“表”(有意引用)。有实际的表,但每个返回记录的语句(如VIEWSELECT 语句或您的emasnapshots() 函数)都像任何其他表一样工作。在 PostgreSQL 中,以上所有内容通常被称为“行源”。您可以继续以复杂的方式组合这些。因此,如果您有一个复杂的问题,请将其分解为更小的问题并将它们组合起来: (1) 获取 datetime、ema 和 bucket; (2) 对它们进行排名; (3) 为每个具有正确等级的存储桶找到日期时间和 ema。无尽的选择。
猜你喜欢
  • 1970-01-01
  • 2013-01-22
  • 2017-06-04
  • 2022-01-26
  • 1970-01-01
  • 2017-01-19
  • 1970-01-01
  • 2018-04-05
  • 1970-01-01
相关资源
最近更新 更多