【问题标题】:Trending sum over time随时间变化的趋势总和
【发布时间】:2013-07-21 11:38:59
【问题描述】:

我有一个看起来像这样的表(在 Postgres 9.1 中):

CREATE TABLE actions (
  user_id: INTEGER,
  date:    DATE,
  action:  VARCHAR(255),
  count:   INTEGER
)

例如:

    user_id    |    date    |     action   | count
---------------+------------+--------------+-------
             1 | 2013-01-01 | Email        |     1
             1 | 2013-01-02 | Call         |     3
             1 | 2013-01-03 | Email        |     3
             1 | 2013-01-04 | Call         |     2
             1 | 2013-01-04 | Voicemail    |     2
             1 | 2013-01-04 | Email        |     2
             2 | 2013-01-04 | Email        |     2

我希望能够查看用户在一段时间内针对一组特定操作的总操作;例如,电话 + 电子邮件:

  user_id  | date        |  count  
-----------+-------------+---------
         1 | 2013-01-01  |       1
         1 | 2013-01-02  |       4
         1 | 2013-01-03  |       7
         1 | 2013-01-04  |      11
         2 | 2013-01-04  |       2

到目前为止,我创建的怪物看起来像这样:

SELECT
  date, user_id, SUM(count) OVER (PARTITION BY user_id ORDER BY date) AS count
FROM
  actions
WHERE
  action IN ('Call', 'Email') 
GROUP BY
  user_id, date, count;

这适用于单个操作,但当它们发生在同一天时似乎会中断多个操作,例如,我们得到9,而不是2013-01-04 上的预期11

    date    |      user_id | count
------------+--------------+-------
 2013-01-01 | 1            |     1
 2013-01-02 | 1            |     4
 2013-01-03 | 1            |     7
 2013-01-04 | 1            |     9 <-- should be 11?
 2013-01-04 | 2            |     2

是否可以调整我的查询来解决这个问题?我尝试删除 count 上的分组,但 Postgres 似乎不喜欢这样:

column "actions.count" must appear in the GROUP BY clause
or be used in an aggregate function
LINE 2:      date, user_id, SUM(count) OVER (PARTITION BY user...
                                ^

【问题讨论】:

  • 一如既往:请使用您的 Postgres 版本。

标签: sql postgresql aggregate-functions window-functions


【解决方案1】:

此查询产生您正在寻找的结果:

SELECT DISTINCT   
  date, user_id, SUM(count) OVER (PARTITION BY user_id ORDER BY date) AS count 
  FROM actions
WHERE
  action IN ('Call', 'Email');

默认窗口已经是您想要的,according to the official docs 并且“DISTINCT”消除了电子邮件和电话在同一天发生时的重复行。

SQL Fiddle

【讨论】:

    【解决方案2】:

    表中有一个名为“count”的列,SELECT子句中的表达式别名为“count”,是不明确的。

    阅读文档:http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-GROUPBY

    如果有歧义,GROUP BY 名称将被解释为 输入列名而不是输出列名。

    这意味着,您的查询不按 SELECT 子句中评估的“计数”分组,而是按从表中获取的“计数”值分组。

    这个查询给出了预期的结果,见SQL Fiddle

    SELECT date, user_id, count
    from (
       Select date, user_id, 
              SUM(count) OVER (PARTITION BY user_id ORDER BY date) AS count
      FROM actions
      WHERE
        action IN ('Call', 'Email') 
    ) alias
    GROUP BY
      user_id, date, count;
    

    【讨论】:

    • 您的查询是正确的,您的报价是正确的,但解释只涵盖了问题的一半。窗口函数在 聚合函数之后应用。因此,GROUP BY 中根本不允许使用窗口函数引用表达式。您的查询通过将窗口函数放入子查询中来避免该问题,但这不是必需。您可以在单个查询级别中对聚合函数的结果使用窗口函数(如我的回答所示),这通常更快(在我的测试中为 2.5 倍)。
    【解决方案3】:

    断言

    不清楚是按user_id还是date排序

    还不清楚您是否要在结果列表中包含日期,因为在基表中没有行。在这种情况下,请参考这个密切相关的答案:
    PostgreSQL: running count of rows for a query 'by minute'

    修复名称

    首先,我使用的是这个测试表而不是你的有问题的表

    CREATE TEMP TABLE actions (
      user_id integer,
      thedate    date,
      action  text,
      ct   integer
    );

    您使用reserved words 和函数名作为标识符(列名)是问题的一部分。

    修复查询

    结合聚合和窗口函数

    由于首先应用聚合函数,因此您的原始查询会将为user_id = 1thedate = '2013-01-04' 找到的两个 行合并为一个。您必须乘以 count(*) 才能得到实际的运行计数。

    您可以不使用子查询,因为您可以组合聚合函数和窗口函数。首先应用聚合函数。您甚至可以在聚合函数的结果上创建一个窗口函数

    SELECT thedate
         , user_id
         , sum(ct * count(*)) OVER (PARTITION BY user_id
                                    ORDER BY thedate) AS running_ct
    FROM   actions
    WHERE  action IN ('Call', 'Email') 
    GROUP  BY user_id, thedate, ct
    ORDER  BY user_id, thedate;
    

    或简化为:

    ...
     , sum(sum(ct)) OVER (PARTITION BY user_id
                          ORDER BY thedate) AS running_ct
    ...
    

    这也应该是提出的解决方案中最快的

    这里,内部sum() 是一个聚合函数,而外部sum() 是一个窗口函数——在聚合函数的结果之上。

    或者使用DISTINCT

    另一种方法是使用DISTINCT or DISTINCT ON,因为这是在 窗口函数之后应用的:

    DISTINCT - 这是可能的,因为无论如何running_ct 在这种情况下都保证是相同的,因为所有对等点都会立即为default frame definition of window functions 求和。

    SELECT DISTINCT
           thedate
         , user_id
         , sum(ct) OVER (PARTITION BY user_id ORDER BY thedate) AS running_ct
    FROM   actions
    WHERE  action IN ('Call', 'Email')
    ORDER  BY thedate, user_id;
    

    或者用DISTINCT ON简化:

    SELECT DISTINCT ON (thedate, user_id)
    ...
    

    ->SQLfiddle demonstrating all variants.

    【讨论】:

    • 保留字的使用究竟是如何导致问题的?
    • @MarkStosberg:count是 SQL 标准中的保留字。不幸地将count 用作函数名和列名,OP 似乎感到困惑。您也不应该在答案中使用 count 作为标识符。
    • 欣赏信息。不幸的是,我没有创建表,但我可以看到这可能会导致问题。这可以通过引用标识符来解决吗?
    • @JohnLedbetter:可以,但最好避免将保留字作为标识符开头。行情可以忘记...
    • 我知道这是/最佳实践/避免保留字,我只是没有发现在这种情况下导致问题的实际问题。
    猜你喜欢
    • 1970-01-01
    • 2014-05-27
    • 1970-01-01
    • 1970-01-01
    • 2020-02-05
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    相关资源
    最近更新 更多