Функция окна Postgres и группа исключений - PullRequest
12 голосов
/ 13 января 2012

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

Вот запрос, который у меня есть на данный момент:

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 находятся внутриагрегатная функция.Я получаю сообщение об ошибке:

столбец "sp.payout" должен появляться в предложении GROUP BY или использоваться в статистической функции

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

1 Ответ

31 голосов
/ 13 января 2012

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

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

Вы можете комбинировать оконные функции и агрегатные функции . Агрегации применяются в первую очередь. Я не понял из вашего описания, как вы хотите обрабатывать несколько выплат / покупок за событие. Как предположение, я рассчитываю их сумму за событие. Теперь Я могу удалить 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 охватывает все столбцы входной строки , начиная с 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 в первом запросе.)

...