【问题标题】:Count cumulative total in Postgresql在 Postgresql 中计算累计总数
【发布时间】:2011-08-07 14:15:13
【问题描述】:

我正在使用countgroup by 来获取每天注册的订阅者数量:

  SELECT created_at, COUNT(email)  
    FROM subscriptions 
GROUP BY created at;

结果:

created_at  count
-----------------
04-04-2011  100
05-04-2011   50
06-04-2011   50
07-04-2011  300

我想改为每天获取订阅者的累计总数。我怎么得到这个?

created_at  count
-----------------
04-04-2011  100
05-04-2011  150
06-04-2011  200
07-04-2011  500

【问题讨论】:

    标签: sql postgresql aggregate-functions


    【解决方案1】:

    用途:

    SELECT a.created_at,
           (SELECT COUNT(b.email)
              FROM SUBSCRIPTIONS b
             WHERE b.created_at <= a.created_at) AS count
      FROM SUBSCRIPTIONS a
    

    【讨论】:

      【解决方案2】:
      SELECT
        s1.created_at,
        COUNT(s2.email) AS cumul_count
      FROM subscriptions s1
        INNER JOIN subscriptions s2 ON s1.created_at >= s2.created_at
      GROUP BY s1.created_at
      

      【讨论】:

      • 我试过sum(s2.count),控制台报错:'聚合函数调用不能嵌套'
      • 我的意思是COUNT(s2.email),对不起。请查看我编辑的解决方案。
      • 谢谢哥们!我正在处理一个更复杂的查询,您的结构很容易理解(因此也很容易实现)。
      【解决方案3】:

      我假设您每天只需要一行,并且您仍然希望显示没有任何订阅的天数(假设没有人订阅某个日期,您想显示该日期和前一天的余额吗?)。如果是这种情况,您可以使用 'with' 功能:

      with recursive serialdates(adate) as (
          select cast('2011-04-04' as date)
          union all
          select adate + 1 from serialdates where adate < cast('2011-04-07' as date)
      )
      select D.adate,
      (
          select count(distinct email)
          from subscriptions
          where created_at between date_trunc('month', D.adate) and D.adate
      )
      from serialdates D
      

      【讨论】:

      • 谢谢,with 函数也很有用。学到了一些新东西。
      • 您可以使用内置函数代替serialdates:generate_series(timestamp '2011-04-04', timestamp '2011-04-07', interval '1 day')
      【解决方案4】:

      对于较大的数据集,window functions 是执行此类查询的最有效方式 - 表将仅被扫描一次,而不是像自联接那样每个日期扫描一次。它看起来也简单了很多。 :) PostgreSQL 8.4 及更高版本支持窗口函数。

      这就是它的样子:

      SELECT created_at, sum(count(email)) OVER (ORDER BY created_at)
      FROM subscriptions
      GROUP BY created_at;
      

      这里OVER创建窗口; ORDER BY created_at 表示它必须以created_at 的顺序来汇总计数。


      编辑:如果您想在一天内删除重复的电子邮件,您可以使用sum(count(distinct email))。不幸的是,这不会删除跨越不同日期的重复项。

      如果你想删除 all 重复项,我认为最简单的方法是使用子查询和DISTINCT ON。这会将电子邮件归因于它们的最早日期(因为我按 created_at 升序排序,它会选择最早的):

      SELECT created_at, sum(count(email)) OVER (ORDER BY created_at)
      FROM (
          SELECT DISTINCT ON (email) created_at, email
          FROM subscriptions ORDER BY email, created_at
      ) AS subq
      GROUP BY created_at;
      

      如果您在(email, created_at) 上创建索引,此查询也不应该太慢。


      (如果你想测试,这就是我创建示例数据集的方式)

      create table subscriptions as
         select date '2000-04-04' + (i/10000)::int as created_at,
                'foofoobar@foobar.com' || (i%700000)::text as email
         from generate_series(1,1000000) i;
      create index on subscriptions (email, created_at);
      

      【讨论】:

      • 这是很好的 intgr,只是我的订阅表包含很多重复的电子邮件行。所以over 正在做的是sum-ming count 数字,但我仍然需要在每个后续日期重新计算唯一的电子邮件。
      • 我用DISTINCT ON 子查询更新了我的答案。它仍然比 Andriy 的答案快得多——可以在几秒钟内处理一百万行——但可能更复杂。
      • 关于 generate_series 函数的好技巧!
      • 注意DISTINCT ON也可以变成与GROUP BY的等价查询;在这种情况下,SELECT email, MIN(created_at) as created_at FROM subscriptions GROUP BY email。尽管来自DISTINCT ON 的已排序子查询似乎为 Window 函数所需的排序提供了一些优势,但哪个更有效可能会有所不同。
      • 我希望每个月都有这个,我需要如何更改这个查询?我遇到了真正的问题。
      【解决方案5】:

      最好的办法是有一个日历表: 日历 ( 日期日期, 月份整数, 四分之一整数, 半个整数, 周整数, 年份 )

      然后,您可以加入此表,对您需要的字段进行汇总。

      【讨论】:

      • 这与获取总和无关。
      【解决方案6】:

      今天(2021 年)看到此答案的任何人 您可以使用汇总

      SELECT created_at, COUNT(email)  
          FROM subscriptions 
      GROUP BY rollup(created_at);
      

      这将为您提供一个包含总数的新行

      created_at  count
      -----------------
      04-04-2011  100
      05-04-2011   50
      06-04-2011   50
      07-04-2011  300
      NULL        500
      

      如果您有多个参数要显示在分组依据中,您也可以对部分结果使用汇总。例如,如果您有 created_by

      SELECT created_at, created_by COUNT(email)  
          FROM subscriptions 
      GROUP BY rollup(created_at, created_by);
      

      这将为您提供一个包含总数的新行

      created_at  created_by  count
      -----------------------------
      04-04-2011     1        80
      04-04-2011     2        20
      04-04-2021    NULL      100
      05-04-2011     1        20
      05-04-2011     2        30
      05-04-2011    NULL      50
      NULL          NULL      150
      

      我只取了前两天的数字,但就是这样。它将按日期分组显示,然后是当天的总数,然后是总数。

      此处rollup() 中的顺序很重要,关于如何显示部分总计

      【讨论】:

      • 这应该是 2021+ 年公认的答案!在我的系统上:窗口函数(计划时间:1.134 毫秒,执行时间:1.045 毫秒),汇总(计划时间:0.245 毫秒,执行时间:0.642 毫秒)。 Rollup 的性能明显更高。
      猜你喜欢
      • 2021-06-07
      • 1970-01-01
      • 2021-11-10
      • 2014-05-15
      • 1970-01-01
      • 1970-01-01
      • 2019-01-19
      • 2015-09-07
      相关资源
      最近更新 更多