2013-12-01 4 views
3

У меня есть пользовательский агрегатная функция суммы, которая принимает булев тип данных:Повышение производительности пользовательской функции совокупной в PostgreSQL

create or replace function badd (bigint, boolean) 
    returns bigint as 
$body$ 
select $1 + case when $2 then 1 else 0 end; 
$body$ language sql; 

create aggregate sum(boolean) (
    sfunc=badd, 
    stype=int8, 
    initcond='0' 
); 

Этого агрегат должен рассчитать количество строк с TRUE. Например, следующий должен вернуть 2 (и делает):

with t (x) as 
    (values 
     (true::boolean), 
     (false::boolean), 
     (true::boolean), 
     (null::boolean)  
    ) 
select sum(x) from t; 

Однако, его производительность очень плохо, это в 5,5 раза медленнее, чем с помощью литья в целое:

with t as (select (gs > 0.5) as test_vector from generate_series(1,1000000,1) gs) 
select sum(test_vector) from t; -- 52012ms 

with t as (select (gs > 0.5) as test_vector from generate_series(1,1000000,1) gs) 
select sum(test_vector::int) from t; -- 9484ms 

Это единственный способ как улучшить этот агрегат, чтобы написать некоторую новую функцию C - например некоторая альтернатива int2_sum функция в src/backend/utils/adt/numeric.c?

+0

Это не int2-сумма. Это суммарная сумма, которая больше похожа на бит-1. И это тоже неправильно. Он должен быть: '$ 1 + $ 2 :: int', или' $ 1 + case $ 2, когда true, а затем 1 else 0 end', или что-то в этом порядке. –

+1

Я по-прежнему не вижу причины для настраиваемой совокупности, как 'select sum (x :: int) из t;' и 'select sum (1) из t, где x;' являются правильными и намного быстрее – foibs

+0

@Denis I обновил мой вопрос, мой агрегат работает правильно и делает то, что я хочу, но он медленный. –

ответ

4

Ваш тестовый пример вводит в заблуждение, вы считаете TRUE. У вас должны быть как TRUE , так и FALSE - или даже NULL, если применимо.

Like @foibs already explained, для этого не использовалась бы специальная функция агрегации. Встроенные C-функции - это намного быстрее и выполняют эту работу. Используйте вместо этого (также демонстрируя более простой и более разумный тест):

SELECT count(NULLIF(g%2 = 1, FALSE)) AS ct 
FROM generate_series(1,100000,1) g; 

Как это работает?
Compute percents from SUM() in the same SELECT sql query

Несколько быстрых & простых способов (плюс эталонные) в рамках этой связанной с ответом на dba.SE:
For absolute performance, is SUM faster or COUNT?

Или еще быстрее, тест на TRUE в пункте WHERE, где можно:

SELECT count(*) AS ct 
FROM generate_series(1,100000,1) g; 
WHERE g%2 = 1    -- excludes FALSE and NULL ! 

Если вам придется написать собственный агрегат для какой-то причине, эта форма будет выше:

CREATE OR REPLACE FUNCTION test_sum_int8 (int8, boolean) 
    RETURNS bigint as 
'SELECT CASE WHEN $2 THEN $1 + 1 ELSE $1 END' LANGUAGE sql; 

Сложение выполняется только при необходимости.Ваш оригинал добавит 0 для случая FALSE.

Еще лучше, используйте plpgsql функция. Это экономит накладные расходы на каждый вызов, так как работает как подготовленный оператор (запрос не перепланируется). Делает разницу для крошечной агрегатной функции, которая называется много раз:

CREATE OR REPLACE FUNCTION test_sum_plpgsql (int8, boolean) 
    RETURNS bigint AS 
$func$ 
BEGIN 
RETURN CASE WHEN $2 THEN $1 + 1 ELSE $1 END; 
END 
$func$ LANGUAGE plpgsql; 

CREATE AGGREGATE test_sum_plpgsql(boolean) (
    sfunc = test_sum_plpgsql 
,stype = int8 
,initcond = '0' 
); 

Быстрее, чем то, что вы имели, но гораздо медленнее, чем представленную альтернативу со стандартным count(). И медленнее, чем любая другая C-функция.

->SQLfiddle

+0

Ой, вы правы, мой тестовый пример должен быть: 'с t as (select (random()> 0.5) в качестве test_vector from generate_series (1,1000000,1) gs) выберите сумму (test_vector) из t;' I Я попытаюсь написать свою собственную функцию С. –

1

Я не вижу здесь настоящей проблемы. Прежде всего, используя sum, так как ваше пользовательское агрегатное имя неверно. Когда вы вызываете sum с вашим test_vector аккомпанированием int, используется встроенная сумма постгревов, а не ваша, поэтому она намного быстрее. Функция C всегда будет быстрее, но я не уверен, что вам нужно в этом случае.

Вы можете легко уронить badd функцию, и ваш sum использовать встроенный sum с где положение

with t as (select 1 as test_vector from generate_series(1,1000000,1) gs where gs > 0.5) 
select sum(test_vector) from t; 

EDIT:

Подводя итог, лучший способ оптимизировать пользовательский агрегат должен удалить его, если он не нужен. Второй способ - написать функцию C.

3

Я создал пользовательскую функцию C и агрегат для булева:

функция C:

#include "postgres.h" 
#include <fmgr.h> 

#ifdef PG_MODULE_MAGIC 
PG_MODULE_MAGIC; 
#endif 

int 
bool_sum(int arg, bool tmp) 
{ 
    if (tmp) 
    { 
    arg++; 
    } 
    return arg; 
} 

Переходные и агрегатные функции:

-- transition function 
create or replace function bool_sum(bigint, boolean) 
    returns bigint 
    AS '/usr/lib/postgresql/9.1/lib/bool_agg', 'bool_sum' 
    language C strict 
    cost 1; 
alter function bool_sum(bigint, boolean) owner to postgres; 

-- aggregate 
create aggregate sum(boolean) (
    sfunc=bool_sum, 
    stype=int8, 
    initcond='0' 
); 
alter aggregate sum(boolean) owner to postgres; 

Тест производительности:

-- Performance test - 10m rows 

create table tmp_test as (select (case when random() <.3 then null when random() < .6 then true else false end) as test_vector from generate_series(1,10000000,1) gs); 

-- Casting to integer 
select sum(test_vector::int) from tmp_test; 

-- Boolean sum 
select sum(test_vector) from tmp_test; 

В настоящее время sum(boolean) не достигает sum(boolean::int).

Update:

Оказывается, что я могу назвать существующие функции перехода C непосредственно, даже логический тип данных. Он каким-то образом волшебным образом превращается в 0/1 по дороге. Поэтому мое текущее решение для булевой суммы и среднего значения:

create or replace function bool_sum(bigint, boolean) 
    returns bigint as 
'int2_sum' 
    language internal immutable 
    cost 1; 


create aggregate sum(boolean) (
    sfunc=bool_sum, 
    stype=int8 
); 

-- Average for boolean values (percentage of rows with TRUE) 
create or replace function bool_avg_accum(bigint[], boolean) 
    returns bigint[] as 
'int2_avg_accum' 
    language internal immutable strict 
    cost 1; 

create aggregate avg(boolean) (
    sfunc=bool_avg_accum, 
    stype=int8[], 
    finalfunc=int8_avg, 
    initcond='{0,0}' 
); 
+0

+1 Для обеспечения вашего интересного решения. Даже если это не будет необходимо для представленного случая, оно может быть полезно для связанных проблем. –

+1

@ErwinBrandstetter Я обновил свой ответ - нет необходимости в пользовательской функции C, и все же она будет такой же быстрой, как встроенная в агрегаты. –

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