Postgres窗口函数和异常分组

Mar*_*tin 12 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
Run Code Online (Sandbox Code Playgroud)

查询将运行.但是,结果略有不正确.原因是一个人event可以有多个游戏(有不同的sp.payouts).因此,如果用户在具有不同支出的事件中具有2个结果(即,每个事件有4个游戏,并且用户从一个获得20英镑,而从另一个获得40英镑),则上面出现多行.

显而易见的解决方案是修改GROUP BY为:

GROUP BY p.name, e.date, e.event_id
Run Code Online (Sandbox Code Playgroud)

然而,Postgres在此抱怨,因为它似乎没有认识到sp.payout并且s.buyin在一个聚合函数内.我收到错误:

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

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

Erw*_*ter 32

事实上,您并非使用聚合函数.您正在使用窗口功能.这就是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;
Run Code Online (Sandbox Code Playgroud)

在这个表达式中:sum(sum(sp.payout)) OVER w,outer sum()是一个window函数,inner sum()是一个聚合函数.

假设p.player_ide.event_idPRIMARY KEY它们各自的表英寸

我加入e.event_idORDER BY了的WINDOW条款以确定性的排序顺序到达.(同一日期可能有多个事件.)event_id结果中还包括每天区分多个事件.

虽然查询限制为单个 player(WHERE p.player_id = 17),但我们不需要添加p.namep.player_idto GROUP BYORDER BY.如果其中一个连接会过度地乘以行,则得到的总和将是不正确的(部分或完全相乘).然后分组p.name无法修复查询.

我也e.dateGROUP BY条款中删除了.自PostgreSQL 9.1以来,主键e.event_id涵盖输入行的所有列.

如果您更改查询以立即返回多个玩家,请调整:

...
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;
Run Code Online (Sandbox Code Playgroud)

除非p.name定义为唯一(?),否则通过分组和顺序以player_id确定的排序顺序获得正确的结果.

我只保留e.date并且p.nameGROUP BY所有条款中具有相同的排序顺序,希望获得性能优势.否则,您可以删除那里的列.(仅e.date在第一个查询中类似.)