Postgres窗口函数和异常分组

时间:2012-01-13 01:44:50

标签: sql postgresql aggregate-functions window-functions

我正在尝试整理一个查询,该查询将在一段时间内检索用户的统计信息(盈利/亏损)作为累积结果。

这是我到目前为止的查询:

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.payouts.buyin在聚合函数中。我收到错误:

  

列“sp.payout”必须出现在GROUP BY子句中或用于   聚合函数

我在Ubuntu Linux服务器上运行9.1 我错过了什么,或者这可能是Postgres的真正缺陷吗?

1 个答案:

答案 0 :(得分:31)

事实上,您使用聚合函数。您正在使用 window functions 。这就是PostgreSQL要求sp.payouts.buyin包含在GROUP BY子句中的原因。

通过附加OVER子句,聚合函数sum()将转换为窗口函数,该函数在保持所有行时聚合每个分区的值。

您可以合并窗口函数和聚合函数。首先应用聚合。我从你的描述中不理解你想如何处理每个事件的多个支付/购买。作为猜测,我计算每个事件的总和。 现在我可以从sp.payout子句中删除s.buyinGROUP BY,并按playerevent获取一行:

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_ide.event_id在各自的表格中为PRIMARY KEY

我将e.event_id添加到ORDER BY子句的WINDOW以获得确定的排序顺序。 (同一日期可能会有多个事件。)结果中还包括event_id,以区分每天的多个事件。

虽然查询限制为单个播放器(WHERE p.player_id = 17),但我们无需将p.namep.player_id添加到GROUP BYORDER 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.nameGROUP BY,以便在所有条款中具有相同的排序顺序,希望获得性能优势。否则,您可以删除那里的列。 (与第一个查询中的e.date类似。)