2016-11-14 10 views
0

У меня часто возникают запросы с той же комбинацией агрегатных функций. Например.Как повторно использовать выражение с агрегатами в PostgreSQL без замедления

SELECT 
    my_id, 
    sum(a * weight)/nullif(sum(CASE WHEN a IS NOT NULL THEN weight END), 0) AS a, 
    sum(b * weight)/nullif(sum(CASE WHEN b IS NOT NULL THEN weight END), 0) AS b 
FROM my_table 
GROUP BY my_id 

Я хотел бы избежать повторения одних и тех же выражений снова и снова. Было бы здорово, чтобы получить тот же результат с новой функцией weighted_avg:

SELECT 
    my_id, 
    weighted_avg(a, weight) AS a, 
    weighted_avg(b, weight) AS b 
FROM my_table 
GROUP BY my_id 

Единственным способом сделать это, я не знаю, является использование CREATE AGGREGATE с промежуточным состоянием и SFUNC который вызывается для каждой строки. К сожалению, это намного медленнее исходного запроса, что делает его непригодным в моем случае.

Я полагаю, мой идеальным решением будет выглядеть

CREATE AGGREGATE FUNCTION weighted_avg(x float, weight float) 
RETURNS float AS $$ 
    SELECT sum(x * weight)/nullif(sum(CASE WHEN x IS NOT NULL THEN weight END), 0) 
$$ language SQL IMMUTABLE; 

и быть инлайн при выполнении запроса. Но я не могу найти ничего подобного, поддерживаемого Postgres.

+1

Использование функции, вероятно, всегда будет немного медленнее, чем просто использовать выражения в исходном коде. –

+0

У меня все в порядке с некоторыми издержками, но реализация plpgsql с 'CREATE AGGREGATE' занимает в 4 раза больше времени для выполнения в моем случае. Поэтому я бы сохранил оригинальные выражения, которые приемлемы, но я надеялся на лучшее решение. –

+0

Используйте подзапрос в 'FROM' для вычисления входных выражений один раз. –

ответ

0

Вы не указали свою тестовую функцию агрегата. Это, как я хотел бы создать его:

create function weighted_avg_acumm (fa float[], x float, weight float) 
returns float[] as $$ 
    select array[ 
     fa[1] + x * weight, 
     fa[2] + weight 
    ]::float[] 
$$ language sql immutable strict; 

create function weighted_avg_acumm_final (fa float[]) 
returns float as $$ 
    select fa[1]/fa[2] 
$$ language sql immutable strict; 

create aggregate weighted_avg (x float, weight float)(
    sfunc = weighted_avg_acumm, 
    finalfunc = weighted_avg_acumm_final, 
    stype = float[], 
    initcond = '{0,0}' 
); 

Update

я тестировал, и это также гораздо медленнее для меня:

create table t (a int, weight int); 
insert into t (a, weight) 
select 
    nullif(round(random() * 10), 0), 
    trunc(random() * 10) + 1 
from generate_series(1,1000000) 
; 

explain analyze 
select weighted_avg(a, weight) 
from t; 
                QUERY PLAN              
------------------------------------------------------------------------------------------------------------------- 
Aggregate (cost=269425.25..269425.26 rows=1 width=8) (actual time=7933.440..7933.440 rows=1 loops=1) 
    -> Seq Scan on t (cost=0.00..14425.00 rows=1000000 width=8) (actual time=0.018..241.571 rows=1000000 loops=1) 
Planning time: 0.189 ms 
Execution time: 7933.508 ms 

explain analyze 
select 
    sum(a::numeric * weight)/
    nullif(sum(case when a is not null then weight end), 0) 
from t; 
                QUERY PLAN              
------------------------------------------------------------------------------------------------------------------- 
Aggregate (cost=26925.00..26925.02 rows=1 width=8) (actual time=904.852..904.852 rows=1 loops=1) 
    -> Seq Scan on t (cost=0.00..14425.00 rows=1000000 width=8) (actual time=0.010..127.264 rows=1000000 loops=1) 
Planning time: 0.048 ms 
Execution time: 904.891 ms 
+0

Это почти то же самое (некоторые разные ноль и обработка NULL). К сожалению, это примерно в 4 раза медленнее, чем собственное выражение. –

 Смежные вопросы

  • Нет связанных вопросов^_^