窗口函数过滤器不会忽略汇总null

时间:2017-11-06 16:27:35

标签: sql postgresql

我有一个包含电影的简单表:

CREATE TABLE public.films
(
    id integer NOT NULL,
    title character varying(255),
    release_year integer
)

我还有一个查询,按年计算电影数量,所有电影的总数(使用ROLLUP)和一个窗口函数,可以为每一行添加电影总数:

SELECT 
    release_year, 
    COUNT(*),
    SUM(COUNT(*) FILTER (WHERE release_year IS NOT NULL)) OVER ()
FROM films 
GROUP BY ROLLUP(release_year)

我添加了FILTER (WHERE release_year IS NOT NULL)部分,因为我想忽略ROLLUP生成的聚合行。令人惊讶的是,这种过滤并不起作用:

| release_year | count | sum |
 ----------------------------
|       [null] |   225 | 450 | 
|         2014 |    57 | 450 | <--- sum should be 225 everywhere
|         2015 |    53 | 450 |
|         2016 |    57 | 450 |
|         2017 |    58 | 450 |

我知道其他一些可能的方法来解决这个问题,例如将窗口函数移动到外部查询或按release_year IS NOT NULL分区,但我很好奇为什么这个特殊情况不起作用我期望。我错过了什么?

我使用Postgres 10。

1 个答案:

答案 0 :(得分:1)

过滤器不能在ROLLUP上工作,因为它会呈现&#39;正在制作ROLLUP。

如果您删除了SUM,则会看到:

SELECT
-- Adding window function row_number for test:
    row_number() OVER() as rn,
    release_year,
    COUNT(*),
-- Removing SUM and OVER for test:
    COUNT(*) FILTER (WHERE release_year IS NOT NULL)
FROM films 
GROUP BY ROLLUP(release_year)

输出如下内容:

rn  | release_year | count | sum |
----+--------------+-------+------
1   |       [null] |   225 | 225 | 
2   |         2014 |    57 | 57  |
3   |         2015 |    53 | 53  |
4   |         2016 |    57 | 57  |
5   |         2017 |    58 | 58  |

由于FILTER适用于ROLLUP,而不适用于&#39; final&#39; table,它没有找到任何NULL release_year,并且由于ROLLUP,它总结了所有release_year&#39;

由于Window Functions&#39;呈现&#39;在#final;&#39;表,当您添加SUM() OVER()时,它会在列中汇总所有内容;看到列,它全部都是因为它会呈现&#39;呈现&#39;在ROLLUP之后。

编辑(此外):

有一个命令,postgresql计算(我说渲染)一个查询。通常它从WHERE子句开始,然后分组,然后聚合,并且最后它总是在窗口函数中。

如果您运行EXPLAIN,您可以更清楚地看到它:

                                  QUERY PLAN
------------------------------------------------------------------------------
 WindowAgg  (cost=XXX.XX..XXX.XX rows=XXX width=XX)
   ->  GroupAggregate  (cost=XXX.XX..XXX.XX rows=XXX width=XX)
         Group Key: release_year
         Group Key: ()
         ->  Sort  (cost=XXX.XX..XXX.XX rows=XXXX width=X)
               Sort Key: release_year
               ->  Seq Scan on films  (cost=X.X..XX.XX rows=XXXX width=X)

首先,它会执行WHERE子句,但由于查询中没有WHERE,因此它会从表格中获取所有行(在电影上扫描)。

第二,它按发布日期执行组和订单,因为它位于GROUP BY ROLLUP子句中(排序)。

第三,它对分组数据(GroupAggregate)运行聚合函数COUNT。由于FILTER只是CASE WHEN的替代,因此它会在分组数据上运行。有关FILTER here

的更多信息

最后,它在聚合(WindowAgg)上运行窗口函数。

一些注释:

当我说&#39; over&#39;时,我的意思是内部postgresql有一个时间表 - 这个&#39; final&#39;表我在上面提到的,实际上是一组数据 - 它运行你在查询中使用的所有函数或子句。

运行窗口函数后,它返回一个RESULT表,这就是你得到的。但在中间,postgresql使用数据集来获得你所要求的内容。

<强>结论

从这个意义上讲,您的查询不会返回您想要的内容,因为FILTER对分组数据起作用,而不是在release_year上有NULL的数据集。只有窗口函数才能访问此NULL,因为在返回给您之前对最后一组数据进行处理,并且由于窗口函数的用途有限,您必须查询此RESULT表以获得您想要的内容。

然而,如果你想了解更多:

更多在postgresql手册中: