2012-01-13 1 views
10

Я пытаюсь собрать запрос, который будет получать статистику пользователя (прибыль/убыток) как совокупный результат за определенный промежуток времени.Функция и группа окна postgres по исключению

Вот запрос, я до сих пор:

SELECT p.name, e.date, 
    sum(sp.payout) OVER (ORDER BY e.date) 
    - sum(s.buyin) OVER (ORDER BY e.date) AS "Profit/Loss" 
FROM result r 
    JOIN game g ON r.game_id = g.game_id 
    JOIN event e ON g.event_id = e.event_id 
    JOIN structure s ON g.structure_id = s.structure_id 
    JOIN structure_payout sp ON g.structure_id = sp.structure_id 
          AND r.position = sp.position 
    JOIN player p ON r.player_id = p.player_id 
WHERE p.player_id = 17 
GROUP BY p.name, e.date, e.event_id, sp.payout, s.buyin 
ORDER BY p.name, e.date ASC 

Запрос будет работать. Однако результат несколько неверен. Причина в том, что event может иметь несколько игр (с разными sp.payouts). Следовательно, вышесказанное выдается с несколькими строками, если у пользователя есть 2 результата в событии с разными выплатами (т. Е. На одно событие есть 4 игры, а пользователь получает £ 20 от одного и 40 фунтов за другого).

Очевидным решением было бы внесению поправок в GROUP BY к:

GROUP BY p.name, e.date, e.event_id 

Однако, Postgres жалуется на это, как не кажется, признают, что sp.payout и s.buyin внутри агрегатной функции. Я получаю сообщение об ошибке:

column "sp.payout" must appear in the GROUP BY clause or be used in an aggregate function

Я запускаю 9.1 на сервере Ubuntu Linux.
Я что-то упустил, или это может быть подлинным дефектом в Postgres?

ответ

21

Вы являетесь не, фактически, используя агрегатные функции. Вы используете window functions. Вот почему PostgreSQL требует sp.payout и s.buyin для включения в предложение GROUP BY.

Добавляя в OVER положение, совокупная функция sum() превращается в оконной функции, которая агрегирует значения каждого раздела в то время сохраняя все строки.

Вы можете функции окна комбинированного окна и агрегатные функции. Сначала применяются агрегирования. Я не понял из вашего описания, как вы хотите обрабатывать несколько выплат/buyins за событие. Думаю, я рассчитываю их сумму за каждое событие. Теперь можно удалить sp.payout и s.buyin из пункта GROUP BY и получить одну строку за player и event:

SELECT p.name 
    , e.event_id 
    , e.date 
    , sum(sum(sp.payout)) OVER w 
    - sum(sum(s.buyin )) OVER w AS "Profit/Loss" 
FROM player   p 
JOIN result   r ON r.player_id  = p.player_id 
JOIN game    g ON g.game_id  = r.game_id 
JOIN event    e ON e.event_id  = g.event_id 
JOIN structure   s ON s.structure_id = g.structure_id 
JOIN structure_payout sp ON sp.structure_id = g.structure_id 
          AND sp.position  = r.position 
WHERE p.player_id = 17 
GROUP BY e.event_id 
WINDOW w AS (ORDER BY e.date, e.event_id) 
ORDER BY e.date, e.event_id; 

В этом выражении: sum(sum(sp.payout)) OVER w, внешний sum() является функцией окна, внутренняя sum() представляет собой совокупность функций ,

Предполагая, что p.player_id и e.event_id являются PRIMARY KEY в соответствующих таблицах.

Я добавил e.event_id в ORDER BY статьи WINDOW, чтобы получить детерминированный порядок сортировки. (В тот же день может быть несколько событий.) Также включается event_id в результате, чтобы различать несколько событий в день.

Хотя запрос ограничивает к одного игрока (WHERE p.player_id = 17), нам не нужно, чтобы добавить p.name или p.player_id к GROUP BY и ORDER BY. Если одно из объединений будет чрезмерно умножать строки, результирующая сумма будет неправильной (частично или полностью умноженной). Группировка по p.name не смогла восстановить запрос.

Я также удалил e.date из статьи GROUP BY. Первичный ключ e.event_id охватывает все столбцы строки ввода since PostgreSQL 9.1.

Если изменить запрос, чтобы вернуть несколько игроков сразу, адаптируют: (?)

... 
WHERE p.player_id < 17 -- example - multiple players 
GROUP BY p.name, p.player_id, e.date, e.event_id -- e.date and p.name redundant 
WINDOW w AS (ORDER BY p.name, p.player_id, e.date, e.event_id) 
ORDER BY p.name, p.player_id, e.date, e.event_id; 

Если p.name не определен уникальным, группа и порядок по player_id дополнительно получить правильные результаты в детерминированный порядок сортировки.

Я оставил e.date и p.name в GROUP BY, чтобы иметь идентичный порядок сортировки во всех предложениях, надеясь на получение выгоды. Иначе вы можете удалить столбцы. (Аналогично только для e.date в первом запросе.)

+0

Первый запрос работает, однако вывод запроса не дает требуемых результатов. Я вижу, какая поправка будет работать в теории, но Postgres не нравится. Я попробую это позже и дам вам знать. однако похоже, что в выводе вашего запроса будет 2 строки, если «event_id» имеет более одной суммы «выплаты». – Martin

+0

Я просто попробовал его с поправками, которые вы предложили, и он возвращается с несколькими строками, где имеется несколько значений sp.payout для одного event_id. – Martin

+0

@Martin: см. Мой исправленный ответ. –