【问题标题】:Postgres window function and group by exceptionPostgres 窗口函数和按异常分组
【发布时间】:2012-02-09 07:43:33
【问题描述】:

我正在尝试组合一个查询,该查询将检索用户在一段时间内的统计数据(利润/损失)作为累积结果。

这是我目前的查询:

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 的真正缺陷?

【问题讨论】:

    标签: sql postgresql aggregate-functions window-functions


    【解决方案1】:

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

    通过附加一个OVER 子句,聚合函数sum() 变成了一个窗口函数,它在保留所有行的同时聚合每个分区的值。

    您可以组合窗口函数和聚合函数。首先应用聚合。从您的描述中,我不明白您希望如何处理每个事件的多个支出/买入。作为猜测,我计算每个事件的总和。 现在我可以从 GROUP BY 子句中删除 sp.payouts.buyin 并在每个 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 添加到WINDOW 子句的ORDER BY 以达到确定性排序顺序。 (同一日期可能有多个事件。)结果中还包括event_id,以区分每天的多个事件。

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

    我还从GROUP BY 子句中删除了e.date。主键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 进行分组和排序,以获得确定性排序顺序中的正确结果。

    我只在GROUP BY 中保留了e.datep.name,以便在所有子句中具有相同的排序顺序,希望能提高性能。否则,您可以删除那里的列。 (与第一个查询中的 e.date 类似。)

    【讨论】:

    • 第一个查询正在运行,但是查询的输出没有给出所需的结果。我可以看到理论上什么修正会起作用,但 Postgres 不喜欢它。稍后我会尝试上述方法并让您知道。但是,如果“event_id”有多个“支付”金额,则查询输出中似乎会有 2 行。
    • 我刚刚尝试了您建议的修改,它确实返回了多行,其中单个 event_id 有多个 sp.payout 值。
    • @Martin:查看我修改后的答案。
    • 您不需要在窗口函数中partition by p.name, e.event_id 以使分组按预期工作吗?
    • @RyanTuck:有几个无法解释/模糊的位。我改进并澄清了(或至少希望如此)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-07
    • 2021-10-02
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 2015-11-03
    相关资源
    最近更新 更多