【问题标题】:How to perform a filtered query in a PostgreSQL window partition?如何在 PostgreSQL 窗口分区中执行过滤查询?
【发布时间】:2016-04-10 02:40:44
【问题描述】:

我正在尝试更好地理解 PostgreSQL (9.3) 窗口函数。假设我有一个简单的表格:

SimpleTable
    id int,
    tservice timestamp

并希望:

Select id, tservice , count(*) OVER (PARTITION BY id ....) as counter
from SimpleTable

SimpleTable 中的记录的 tservice 时间可以追溯到 40 年前,但是 计数需要仅限于每条记录的 tservice 时间戳之前的三年。

如何为 SimpleTable 中的每条记录生成计数?

推论问题:如何更改同一查询以添加今天日期前三年发生的所有记录的计数?

编辑#1:现在我明白这个问题在哪里模糊(我学到了一些东西:))。 使用下面的答案,我想获得 3 年的计数和当前日期的计数,例如:

                             3yrs prior   current date
1, 100, '2001-01-01 00:00:00', 0             0
2, 100, '2002-01-01 00:00:00', 1             0
3, 100, '2003-01-01 00:00:00', 2             0 
4, 100, '2004-01-01 00:00:00', 3             0
5, 100, '2005-01-01 00:00:00', 3             0              
6, 100, '2006-01-01 00:00:00', 3             0
7, 100, '2007-01-01 00:00:00', 3             0 
8, 100, '2008-01-01 00:00:00', 3             0
9, 100, '2009-01-01 00:00:00', 3             0
10, 100, '2010-01-01 00:00:00',3             0
11, 100, '2011-01-01 00:00:00',3             0
12, 100, '2012-01-01 00:00:00',3             0
13, 100, '2013-01-01 00:00:00',3             0
14, 100, '2014-01-01 00:00:00',3             1
15, 100, '2015-01-01 00:00:00',3             2
16, 100, '2016-01-01 00:00:00',3             3  (today is 2016-01-06)

编辑#2:这可以得到我需要的答案,但不使用窗口分区。我在想 PostgreSQL 没有实现带有间隔的 RANGE - 这是我认为这个问题所需要的。

     select s1.recid, s1.tservice, s1.client_recid, 
    (select count(*) from simpletable  s2 
        where (s1.tservice - s2.tservice)::INTERVAL <= interval '3 years' and
        s2.tservice < s1.tservice  and
        s2.client_recid = s1.client_recid)
from simpletable s1
order by client_recid, tservice

在几十万条记录中,这在我的笔记本电脑上大约需要 10 秒。有更快的方法吗?

附录注意:使用 Erwin 概述的带有光标的函数式方法将执行时间减少到 146 毫秒。感谢大家的精彩教程。

【问题讨论】:

  • 关于“每条记录服务前三年”的COUNT,请提供“样本数据”和“预期结果”

标签: database postgresql window-functions cumulative-sum


【解决方案1】:

您的想法只是不可能使用窗口函数的frame definition。 (你也开始怀疑了。)RANGEROWS 子句对不同的值或行进行计数,并且对值的含义没有概念。

您想要计算在特定时间段内的所有行,并且需要以不同的方式处理。您可以运行相关子查询或 LATERAL 子查询来计算每一行,但这很昂贵。

更聪明的方法是并行运行两个游标并保持运行计数。我为一个非常相似的问题实现了这一点:

缩放很多更好。我在那里添加了详细的基准。

【讨论】:

  • 很棒的教学!提供的测试和评估方法非常有用。谢谢:)
【解决方案2】:

不太确定您的目标是什么,通常不会按 ID 分区(假设 ID 每行都是唯一的)。通常,您按多个行共享的某个值进行分区。一个例子可能会有所帮助:SQL Fiddle

PostgreSQL 9.3 架构设置

CREATE TABLE SimpleTable
    ("id" int, "client_id" int, "tservice" timestamp)
;

INSERT INTO SimpleTable
    ("id", "client_id", "tservice")
VALUES
    (1, 100, '2001-01-01 00:00:00'),
    (2, 100, '2002-01-01 00:00:00'),
    (3, 100, '2003-01-01 00:00:00'),
    (4, 100, '2004-01-01 00:00:00'),
    (5, 100, '2005-01-01 00:00:00'),
    (6, 100, '2006-01-01 00:00:00'),
    (7, 100, '2007-01-01 00:00:00'),
    (8, 100, '2008-01-01 00:00:00'),
    (9, 100, '2009-01-01 00:00:00'),
    (10, 100, '2010-01-01 00:00:00'),
    (11, 100, '2011-01-01 00:00:00'),
    (12, 100, '2012-01-01 00:00:00'),
    (13, 100, '2013-01-01 00:00:00'),
    (14, 100, '2014-01-01 00:00:00'),
    (15, 100, '2015-01-01 00:00:00'),
    (16, 100, '2016-01-01 00:00:00')
;

查询 1

SELECT
      id
    , tservice
    , COUNT(*) OVER (PARTITION BY client_id) AS C1
    , COUNT(CASE WHEN tservice >= (CURRENT_DATE - INTERVAL '3 years') THEN 1 ELSE NULL END)
          OVER (PARTITION BY client_id) AS C3

FROM SimpleTable

Results

| id |                  tservice | c1 | c3 |
|----|---------------------------|----|----|
|  1 | January, 01 2001 00:00:00 | 16 |  3 |
|  2 | January, 01 2002 00:00:00 | 16 |  3 |
|  3 | January, 01 2003 00:00:00 | 16 |  3 |
|  4 | January, 01 2004 00:00:00 | 16 |  3 |
|  5 | January, 01 2005 00:00:00 | 16 |  3 |
|  6 | January, 01 2006 00:00:00 | 16 |  3 |
|  7 | January, 01 2007 00:00:00 | 16 |  3 |
|  8 | January, 01 2008 00:00:00 | 16 |  3 |
|  9 | January, 01 2009 00:00:00 | 16 |  3 |
| 10 | January, 01 2010 00:00:00 | 16 |  3 |
| 11 | January, 01 2011 00:00:00 | 16 |  3 |
| 12 | January, 01 2012 00:00:00 | 16 |  3 |
| 13 | January, 01 2013 00:00:00 | 16 |  3 |
| 14 | January, 01 2014 00:00:00 | 16 |  3 |
| 15 | January, 01 2015 00:00:00 | 16 |  3 |
| 16 | January, 01 2016 00:00:00 | 16 |  3 |

【讨论】:

  • 请参阅编辑。 +1 共享价值的概念。谢谢。
  • 一旦表被分区,有没有办法动态分配一个窗口框架在该特定记录上的 tservice 之前三年??
【解决方案3】:

这是上面提到的“一种方法”,使用 LATERAL 来获取使用窗口函数无法实现的动态计数。

SQL Fiddle

PostgreSQL 9.3 架构设置

CREATE TABLE SimpleTable
    ("id" int, "client_id" int, "tservice" timestamp)
;

INSERT INTO SimpleTable
    ("id", "client_id", "tservice")
VALUES
    (1, 100, '2001-01-01 00:00:00'),
    (2, 100, '2002-01-01 00:00:00'),
    (3, 100, '2003-01-01 00:00:00'),
    (4, 100, '2004-01-01 00:00:00'),
    (5, 100, '2005-01-01 00:00:00'),
    (6, 100, '2006-01-01 00:00:00'),
    (7, 100, '2007-01-01 00:00:00'),
    (8, 100, '2008-01-01 00:00:00'),
    (9, 100, '2009-01-01 00:00:00'),
    (10, 100, '2010-01-01 00:00:00'),
    (11, 100, '2011-01-01 00:00:00'),
    (12, 100, '2012-01-01 00:00:00'),
    (13, 100, '2013-01-01 00:00:00'),
    (14, 100, '2014-01-01 00:00:00'),
    (15, 100, '2015-01-01 00:00:00'),
    (16, 100, '2016-01-01 00:00:00')
;

查询 1

select
*
from SimpleTable
cross join lateral (
                    select count(*) as countLT3yrs
                    from SimpleTable st 
                    where st.client_id = SimpleTable.client_id
                    and st.tservice >= (SimpleTable.tservice - INTERVAL '3 years')
                    and st.tservice < SimpleTable.tservice
                    ) x

Results

| id | client_id |                  tservice | countlt3yrs |
|----|-----------|---------------------------|-------------|
|  1 |       100 | January, 01 2001 00:00:00 |           0 |
|  2 |       100 | January, 01 2002 00:00:00 |           1 |
|  3 |       100 | January, 01 2003 00:00:00 |           2 |
|  4 |       100 | January, 01 2004 00:00:00 |           3 |
|  5 |       100 | January, 01 2005 00:00:00 |           3 |
|  6 |       100 | January, 01 2006 00:00:00 |           3 |
|  7 |       100 | January, 01 2007 00:00:00 |           3 |
|  8 |       100 | January, 01 2008 00:00:00 |           3 |
|  9 |       100 | January, 01 2009 00:00:00 |           3 |
| 10 |       100 | January, 01 2010 00:00:00 |           3 |
| 11 |       100 | January, 01 2011 00:00:00 |           3 |
| 12 |       100 | January, 01 2012 00:00:00 |           3 |
| 13 |       100 | January, 01 2013 00:00:00 |           3 |
| 14 |       100 | January, 01 2014 00:00:00 |           3 |
| 15 |       100 | January, 01 2015 00:00:00 |           3 |
| 16 |       100 | January, 01 2016 00:00:00 |           3 |

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 2021-06-14
    • 2017-01-04
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多