【问题标题】:PostgreSQL: running count of rows for a query 'by minute'PostgreSQL:“按分钟”查询的运行行数
【发布时间】:2012-01-01 21:10:17
【问题描述】:

我需要每分钟查询到那一分钟的总行数。

到目前为止,我能达到的最好成绩并没有奏效。它返回每分钟计数,而不是每分钟的总计数:

SELECT COUNT(id) AS count
     , EXTRACT(hour from "when") AS hour
     , EXTRACT(minute from "when") AS minute
  FROM mytable
 GROUP BY hour, minute

【问题讨论】:

  • 您想要列数的 SUM 还是 COUNT?示例查询不清楚。
  • 行总和是什么意思?
  • 总计数。对不起,我会修复这个例子
  • 是否要计算此查询产生的行数?
  • 我们都遇到了“切片到分钟的行数”与“(它返回每分钟计数_”与“但每分钟计数”的问题。我建议您重新考虑和编辑您的问题是为了澄清这些不一致。

标签: sql postgresql datetime aggregate-functions window-functions


【解决方案1】:

只返回有活动的分钟数

最短

SELECT DISTINCT
       date_trunc('minute', "when") AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY 1;

使用date_trunc(),它会返回您需要的内容。

不要在查询中包含id,因为您想要GROUP BY 分钟切片。

count() 通常用作普通的aggregate function。附加OVER 子句使其成为window function。在窗口定义中省略 PARTITION BY - 你想要一个运行计数所有行。默认情况下,从ORDER BY 定义的当前行的第一行到最后一个对等方计数。 The manual:

默认框架选项是RANGE UNBOUNDED PRECEDING,即 与RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 相同。与ORDER BY, 这将框架设置为分区启动后的所有行 通过当前行的最后一个ORDER BY 对等点。

而这恰好是您所需要的。

使用count(*) 而不是count(id)。它更适合您的问题(“行数”)。它通常比count(id) 略快。而且,虽然我们可能假设id 是NOT NULL,但问题中没有指定,所以count(id) 是错误,严格来说,因为NULL 值不计入@987654347 @。

您不能在同一查询级别使用GROUP BY 分钟切片。聚合函数应用在之前窗口函数,窗口函数count(*) 这样每分钟只能看到 1 行。
但是,您可以使用SELECT DISTINCT,因为DISTINCT 是在 窗口函数之后应用的。

ORDER BY 1 只是此处ORDER BY date_trunc('minute', "when") 的简写。
1 是对SELECT 列表中第一个表达式的位置引用。

如果您需要格式化结果,请使用to_char()。喜欢:

SELECT DISTINCT
       to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY date_trunc('minute', "when");

最快

SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) sub
ORDER  BY 1;

很像上面的,但是:

我使用子查询来聚合和计算每分钟的行数。这样,我们每分钟得到 1 行,而外部 SELECT 中没有 DISTINCT。

现在使用sum() 作为窗口聚合函数来将子查询的计数相加。

我发现每分钟有很多行,这要快得多。

包括没有活动的分钟数

最短

@GabiMe asked in a comment 如何在时间范围内为每个 minute 获取一行,包括未发生事件的那些(基表中没有行):

SELECT DISTINCT
       minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER  BY 1;

使用generate_series() 在第一个事件和最后一个事件之间的时间范围内的每一分钟生成一行 - 这里直接基于来自子查询的聚合值。

LEFT JOIN 将所有时间戳截断到分钟并计数。 NULL 值(不存在行)不会添加到运行计数中。

最快

使用 CTE:

WITH cte AS (
   SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) 
SELECT m.minute
     , COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(min(minute), max(minute), interval '1 min')
   FROM   cte
   ) m(minute)
LEFT   JOIN cte USING (minute)
ORDER  BY 1;

再次,在第一步中聚合并计算每分钟的行数,它省略了以后DISTINCT 的需要。

不同于count(),sum()可以返回NULL。默认为0 和COALESCE。

在"when"上有很多行和一个索引,这个带有子查询的版本在我用 Postgres 9.1 - 9.4 测试的几个变体中是最快的:

SELECT m.minute
     , COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) c USING (minute)
ORDER  BY 1;

【讨论】:

  • 我的意思是,即使 running_ct==0 也会显示在结果中(即返回所有分钟的行集,而不仅仅是其中有活动的那些)
  • @bugspy.net:这是一个全新的问题。我为我的答案添加了另一个答案。
  • @bugspy.net: But you can. ;)
  • 完成!我奖励了你 100 分作为赏金
  • @ErwinBrandstetter 惊人的答案!这对我很有帮助,非常感谢
猜你喜欢
  • 2013-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
  • 1970-01-01
  • 2011-02-17
  • 1970-01-01
相关资源
最近更新 更多