【问题标题】:Temporal Aggregation in PostgreSQLPostgreSQL 中的时间聚合
【发布时间】:2011-12-23 15:45:33
【问题描述】:

我正在使用 PostgreSQL 数据库进行时间聚合的 Java 实现。

我的桌子是这样的

Value | Start      | Stop
(int) | (Date)     | (Date)
-------------------------------
1     | 2004-01-01 | 2010-01-01
4     | 2000-01-01 | 2008-01-01

所以要形象化这个时期:

                      ------------------------------
  ----------------------------------------
2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
  [        4         ][       5=4+1      ][    1   ]

我的算法现在计算数据的时间聚合,例如总和():

Value | Start      | Stop
-------------------------------
4     | 2000-01-01 | 2004-01-01
5     | 2004-01-01 | 2008-01-01
1     | 2008-01-01 | 2010-01-01

为了测试获得的结果,我现在想直接使用 PostgreSQL 查询数据。我知道这个问题还没有简单的方法。但是,肯定有一种方法可以获得相同的结果。应支持聚合 Count、Max、Min、Sum 和 Average。我不介意一个糟糕或缓慢的解决方案,它只需要工作。

到目前为止,我发现的一个应该类似的查询如下:

select count(*), ts, te
from ( checkout a normalize checkout b using() ) checkoutNorm
group by ts, te;

我的收养是这样的:

select count(*), start, stop
from ( myTable a normalize myTable b using() ) myTableNorm
group by start, stop;

但是报错ERROR: syntax error at or near "normalize" -- LINE 2: from ( ndbs_10 a normalize ndbs_10 b using() ) ndbsNorm

有没有人可以解决这个问题?它不必基于上述查询,只要它有效。非常感谢。

【问题讨论】:

  • 你的计算对我来说没有意义。我没有看到任何逻辑方法可以从 {1, 2004-01-01, 2010-01-01} 和 {4, 2000-01-01} 派生 {4, 2000-01-01, 2004-01-01} , 2008-01-01}。我一定是错过了什么。
  • 表示从2000年到2004年(不包括)SUM(值)为4。从2004年到2008年(不包括)有两个时期重叠,所以它们的值应该加以总结。现在有意义吗?
  • 什么特别对你没有意义?想象一张包含工人工资(每行一个)和他们受雇时期的表格。现在的结果应该是每个时期的总工资(或最大值、最小值、平均值、计数)。
  • 哪个版本的 PostgreSQL,哪个时间模块?
  • 它是 x86_64-pc-linux-gnu 上的 PostgreSQL 8.4.8,由 GCC gcc-4.4.real (Debian 4.4.5-8) 4.4.5 编译,64 位,没有已知的时间补充。这在我无法更新的服务器上运行,但我可以使用本地数据库。

标签: postgresql aggregate-functions aggregation temporal-database


【解决方案1】:

你的问题真的很难理解。但我认为我想通了。
你想要一个超过value 的总和。值仅适用于某个时间段的startstop 之间。所以他们必须在那个时期开始时添加并在结束时扣除。
此外,您还需要总和有效的结果期间的开始和结束。
应该这样做:

-- DROP SCHEMA x CASCADE;
CREATE SCHEMA x;
CREATE TABLE x.tbl(val int, start date, stop date);
INSERT INTO x.tbl VALUES
 (4 ,'2000-01-01' ,'2008-01-01')
,(7 ,'2001-01-01' ,'2009-01-01')
,(1 ,'2004-01-01' ,'2010-01-01')
,(2 ,'2005-01-01' ,'2006-01-01');

WITH a AS (
    SELECT start as ts, val FROM x.tbl
    UNION  ALL
    SELECT stop, val * (-1) FROM x.tbl
    ORDER  BY 1, 2)
SELECT sum(val) OVER w AS val_sum
      ,ts AS start
      ,lead(ts) OVER w AS stop
FROM   a
WINDOW w AS (ORDER BY ts)
ORDER  BY ts;

val_sum |   start    |    stop
--------+------------+------------
      4 | 2000-01-01 | 2001-01-01
     11 | 2001-01-01 | 2004-01-01
     12 | 2004-01-01 | 2005-01-01
     14 | 2005-01-01 | 2006-01-01
     12 | 2006-01-01 | 2008-01-01
      8 | 2008-01-01 | 2009-01-01
      1 | 2009-01-01 | 2010-01-01
      0 | 2010-01-01 |

请求后编辑

对于所有请求的聚合函数:

SELECT period
      ,val_sum
      ,val_count
      ,val_sum::float /val_count AS val_avg
      ,(SELECT min(val) FROM x.tbl WHERE start < y.stop AND stop > y.start) AS val_min
      ,(SELECT max(val) FROM x.tbl WHERE start < y.stop AND stop > y.start) AS val_max
      ,start
      ,stop
FROM   (
    WITH a AS (
         SELECT start as ts, val, 1 AS c FROM x.tbl
         UNION  ALL
         SELECT stop, val, -1 FROM x.tbl
         ORDER  BY 1, 2)
    SELECT count(*) OVER w AS period
          ,sum(val*c) OVER w AS val_sum
          ,sum(c) OVER w AS val_count
          ,ts AS start
          ,lead(ts) OVER w AS stop
    FROM   a
    WINDOW w AS (ORDER BY ts)
    ORDER  BY ts
    ) y
WHERE stop IS NOT NULL;

 period | val_sum | val_count | val_avg | val_min | val_max |   start    |    stop
--------+---------+-----------+---------+---------+---------+------------+------------
      1 |       4 |         1 |       4 |       4 |       4 | 2000-01-01 | 2001-01-01
      2 |      11 |         2 |     5.5 |       4 |       7 | 2001-01-01 | 2004-01-01
      3 |      12 |         3 |       4 |       1 |       7 | 2004-01-01 | 2005-01-01
      4 |      14 |         4 |     3.5 |       1 |       7 | 2005-01-01 | 2006-01-01
      5 |      12 |         3 |       4 |       1 |       7 | 2006-01-01 | 2008-01-01
      6 |       8 |         2 |       4 |       1 |       7 | 2008-01-01 | 2009-01-01
      7 |       1 |         1 |       1 |       1 |       1 | 2009-01-01 | 2010-01-01

min()max 可能会被优化,但这应该已经足够好了。 如您所见,CTE(WITH 子句)和子查询是可交换的。

【讨论】:

  • 对不起,我不是故意要问一个难以理解的问题。非常感谢您的回答。它适用于 SUM()。如何使其与 min、max、avg 和 count 一起使用?
  • @Erwin Brandstetter:出色的技巧,与众不同。我正要创建一些很棒的规则并将它们与递归 CTE 结合起来......在 str:你的问题仍然很糟糕。如果您想要一个 AVG,这是否意味着您要尊重当前存在的所有区间的所有边界?
  • @str:我用一个功能齐全的查询修改了我的答案。
  • @wildplasser:很抱歉让你的努力付诸东流,但一旦我看到 str 想要什么,我就不得不去做。这是一种上瘾。
  • 无论如何它都不可能支持额外的聚合。但是,想要使日期间隔正常化也是一种病,你知道……很好的解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多