我正在收集数据并绘制图表,我需要做的一件事是计算我的一些数据的指数移动平均值。我的数据存储在 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 所说的内容进行了修改:
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))
最佳答案
首先,关于您的函数的次要效率问题的一些注意事项:
- 您不必
quote_literal()
除了字符串之外的任何内容。 Bobby Tables 是不可能的通过double precision
或timestamp
参数注入(inject)您的 SQL 语句。 - 在动态SQL语句中只需要手动拼接表名和列名;可以使用
USING
子句注入(inject)参数值。这节省了大量的解析时间。 - 将尽可能多的计算移到循环之外。例如:
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
,而不是在包括网络传输时间在内的客户端上进行测量。
关于postgresql - 在 postgresql 中返回指数移动平均线快照的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31305215/