Выбор положительного совокупного значения и игнорирование отрицательного в Postgres SQL - PullRequest
5 голосов
/ 13 августа 2011

Я должен применить определенное преобразование fn(argument).Здесь argument равно value, но не тогда, когда оно отрицательно.Когда вы получаете первый отрицательный value, то вы «ждете», пока он не суммирует с последовательными значениями, и эта сумма становится положительной.Тогда вы делаете fn(argument).См. Таблицу, которую я хочу получить:

value      argument 
---------------------
  2           2      
  3           3      
 -10          0      
  4           0
  3           0
  10          7
  1           1

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

Как и в случае решения Postgres SQL, похоже, что оконные функции подходят, но я не достаточно опытен, чтобы написать выражение, которое делает это.На самом деле, я новичок в «мышлении в sql», к сожалению.Я думаю, что это легко сделать императивным способом, но я пока не хочу писать хранимую процедуру.

Ответы [ 2 ]

14 голосов
/ 13 февраля 2015

Полагаю, я опоздал, но это может кому-то помочь:

select
    value,
    greatest(0, value) as argument
from your_table;
1 голос
/ 13 августа 2011

Это на самом деле не подходит ни к одной из предопределенных функций агрегирования. Вы, вероятно, должны написать свой собственный. Обратите внимание, что в postgresql агрегатные функции могут использоваться в качестве оконных функций, и фактически это единственный способ писать оконные функции в любом другом месте, кроме C, начиная с 9.0.

Вы можете написать функцию, которая отслеживает состояние «суммирования» значений, за исключением того, что она всегда возвращает входное значение, если текущая «сумма» положительна, и просто добавляет, когда «сумма» отрицательна. Тогда вам просто нужно взять большее из этой суммы или нуля. Брать:

-- accumulator function: first arg is state, second arg is input
create or replace function ouraggfunc(int, int)
 returns int immutable language plpgsql as $$
begin
  raise info 'ouraggfunc: %, %', $1, $2; -- to help you see what's going on
  -- get started by returning the first value ($1 is null - no state - first row)
  if $1 is null then
    return $2;
  end if;
  -- if our state is negative, we're summing until it becomes positive
  -- otherwise, we're just returning the input
  if $1 < 0 then
    return $1 + $2;
  else
    return $2;
  end if;
end;
$$;

Вам нужно создать агрегатную функцию для вызова этого аккумулятора:

create aggregate ouragg(basetype = int, sfunc = ouraggfunc, stype = int);

Это определяет, что агрегат принимает целые числа в качестве входных данных и сохраняет свое состояние как целое число.

Я скопировал ваш пример в таблицу:

steve@steve@[local] =# create table t(id serial primary key, value int not null, argument int not null);
NOTICE:  CREATE TABLE will create implicit sequence "t_id_seq" for serial column "t.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t_pkey" for table "t"
CREATE TABLE
steve@steve@[local] =# copy t(value, argument) from stdin;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 2    2
>> 3    3
>> -10  0
>> 4    0
>> 3    0
>> 10   7
>> 1    1
>> \.

И теперь вы можете получить эти значения, используя агрегатную функцию с предложением окна:

steve@steve@[local] =# select value, argument, ouragg(value) over(order by id) from t;
INFO:  ouraggfunc: <NULL>, 2
INFO:  ouraggfunc: 2, 3
INFO:  ouraggfunc: 3, -10
INFO:  ouraggfunc: -10, 4
INFO:  ouraggfunc: -6, 3
INFO:  ouraggfunc: -3, 10
INFO:  ouraggfunc: 7, 1
 value | argument | ouragg
-------+----------+--------
     2 |        2 |      2
     3 |        3 |      3
   -10 |        0 |    -10
     4 |        0 |     -6
     3 |        0 |     -3
    10 |        7 |      7
     1 |        1 |      1
(7 rows)

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

create function positive(int) returns int immutable strict language sql as
$$ select case when $1 > 0 then $1 else 0 end $$;

и сейчас:

select value, argument, positive(ouragg(value) over(order by id)) as raw_agg from t

Получает аргументы для функции, которую вы указали в вопросе.

...