【问题标题】:Can Window Function be used with Grouping Sets?窗口函数可以与分组集一起使用吗?
【发布时间】:2023-03-12 14:20:01
【问题描述】:

简介

在本问答中,我们将探讨在使用 PostgreSQL 的同一查询中使用 window functionsgrouping sets。读者应该熟悉这两个概念才能充分利用这篇文章。 Thisfiddle 可用于跟随。

数据

下表包含一些事务。对于每笔交易,我们都有客户、交易发生的城市以及交付给客户的商品数量。

     CREATE TABLE transactions (customer TEXT, city TEXT, units INT);
     INSERT INTO transactions VALUES ('Bob', 'Manchester', 10),
                                     ('Chuck', 'Manchester', 20),
                                     ('Bob', 'Concord', 10),
                                     ('Tim', 'Manchester', 15),
                                     ('Jane', 'Derry', 10),
                                     ('Tim', 'Derry', 15),
                                     ('Tim', 'Concord', 20),
                                     ('Bob', 'Manchester', 20),
                                     ('Chuck', 'Concord', 10);

期望的结果

我希望能够生成一份报告,其中包含对“this 行表示所有交易的比例”或“this 此客户的所有交易”。我也希望能够回答所有这些可能的问题。使用这种报告,我们可以简单地查看每一行并提取有关其与整个报告或特定数据切片的关系的信息。

第一次尝试

我们可以尝试通过使用具有不同 GROUP BY 子句的多个查询来创建这样的报告,然后将它们与 UNION ALL 联合起来,或者我们可以尝试以下操作:

-- Incorrect results 
  SELECT customer
       , city
       , SUM(units) AS "units"
       , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER () , 2) AS "to report"
       , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY customer) , 2) AS "to customer"
       , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY city) , 2) AS "to city"
    FROM transactions
GROUP BY CUBE(customer, city)

这里我们在GROUP BY 子句中使用CUBE,它将生成与客户和城市列的所有可能组合相对应的分组。比率的分子是对应于该行的单位总数的聚合。请注意,它对于所有比率都是相同的,并且它包含用作“单位”列的表达式,即SUM(units)。计算分母更复杂,因为我们需要一个窗口函数来计算所需切片的总单位数,即特定客户或城市的总数或整个报告的总数。

不正确的结果

很遗憾,上述查询产生的比率不正确。例如,第一行有 10 个单位,占总数的 7.7% (130),Bob (40) 占总数的 25%,Concord (40) 占总数的 25%,但结果显示低于正确比率在所有情况下。另一个例子是“客户”和“城市”都是NULL的行,这里的“单位”列是130,但计算出的“报告”比率是25%。显然,比率列中的分母是错误的。我们怎样才能得到想要的结果?

customer city units to report to customer to city
Bob Concord 10 1.92 12.50 12.50
Bob Manchester 30 5.77 37.50 23.08
Bob null 40 7.69 50.00 15.38
Chuck Concord 10 1.92 16.67 12.50
Chuck Manchester 20 3.85 33.33 15.38
Chuck null 30 5.77 50.00 11.54
Jane Derry 10 1.92 50.00 20.00
Jane null 10 1.92 50.00 3.85
Tim Concord 20 3.85 20.00 25.00
Tim Derry 15 2.88 15.00 30.00
Tim null 50 9.62 50.00 19.23
Tim Manchester 15 2.88 15.00 11.54
null null 130 25.00 50.00 50.00
null Manchester 65 12.50 25.00 50.00
null Derry 25 4.81 9.62 50.00
null Concord 40 7.69 15.38 50.00

【问题讨论】:

    标签: sql postgresql group-by window-functions


    【解决方案1】:

    为什么结果有误?

    请注意,尽管问题中的结果是错误的,但它们并非完全没有意义。以“客户”和“城市”均为NULL 的行为例。这一行有 130 个单位,这是数据中的单位总数,所以我们应该期望“报告”的比率为 100%,但结果显示为 25%,这意味着在这种情况下比率的分母是 4乘以 130,即 520。再以第一行为例,这里我们有 10 个单位,“报告”的比率为 1.92%,同样分母错了 4 倍,即实际比率应为 7.69%。显然,报告的总数是实际值的四倍。

    “to customer”和“to city”列的结果也是错误的,但原因不同。以“客户”为NULL 而“城市”不是NULL 的行为例。三个城市的“与城市”的比率是 50%,但应该是 100%。这是因为该比率的分母是应有的两倍。同样的事情也发生在“与客户”的比率上。问题在于行的分区。例如,“to report”列中没有PARTITION BY 子句,因此我们考虑了报告的所有行,总计为 520。

    考虑GROUP BY 子句产生四个分组:

    • (客户,城市) - 相当于GROUP BY customer, city
    • (客户) - 相当于GROUP BY customer
    • (城市) - 相当于GROUP BY city
    • () - 相当于使用没有GROUP BY 子句的聚合

    这些分组中的每一个都是对数据进行切片的不同方式,每个分组的总数为 130。在“to customer”和“to city”列的情况下,我们的分母是它的两倍应该。以城市为例,注意“city”不是NULL 的行包含每个城市的单位两次:一次是“customer”不是NULL,另一次是“customer”是NULL。这两个类别分别对应于上面的第一组和第三组。客户也可以这样说,但行将对应于第一组和第二组。显然,在计算每个比率的分母时,我们需要考虑到不同的行属于数据的不同切片。

    钥匙

    获得所需结果的关键是使用GROUPING 聚合函数。此函数“返回一个位掩码,指示当前分组集中不包括哪些 GROUP BY 表达式”。换句话说,它可以为GROUP BY 子句生成的每个分组提供不同的结果。我们可以使用这个函数来帮助计算每个比率列的分母。为了达到预期的效果,我们在窗口函数的PARTITION BY 子句中使用GROUPING 函数,如下所示:

      SELECT customer
           , city
           , SUM(units) AS "units"
           , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city)) , 2) AS "to report"
           , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city), customer) , 2) AS "to customer"
           , ROUND( 100 * SUM(units) / SUM(SUM(units)) OVER (PARTITION BY GROUPING(customer, city), city) , 2) AS "to city"
        FROM transactions
    GROUP BY CUBE(customer, city)
    

    PARTITION BY 子句中包含GROUPING 函数可确保每个比率的分母仅对应于特定分组的行。幸运的是,我们不必过多考虑将传递给GROUPING 函数的参数。您可以简单地包括出现在GROUP BY 中的所有列,尽管只有那些没有出现在所有分组集中的列是必需的。

    想要的结果

    现在我们在考虑分组的情况下计算分母,我们得到了正确的结果。

    customer city units to report to customer to city
    Bob Manchester 30 23.08 75.00 46.15
    Bob Concord 10 7.69 25.00 25.00
    Chuck Concord 10 7.69 33.33 25.00
    Chuck Manchester 20 15.38 66.67 30.77
    Jane Derry 10 7.69 100.00 40.00
    Tim Concord 20 15.38 40.00 50.00
    Tim Derry 15 11.54 30.00 60.00
    Tim Manchester 15 11.54 30.00 23.08
    Bob null 40 30.77 100.00 30.77
    Chuck null 30 23.08 100.00 23.08
    Jane null 10 7.69 100.00 7.69
    Tim null 50 38.46 100.00 38.46
    null Concord 40 30.77 30.77 100.00
    null Derry 25 19.23 19.23 100.00
    null Manchester 65 50.00 50.00 100.00
    null null 130 100.00 100.00 100.00

    【讨论】:

    • 很好的解释 对于第一次使用分析函数的人来说,这是一个常见的混淆来源。这通过sqlframes.com/docs/sql_generation/analytic_functions 的示例进行了解释。理想情况下,生成 SQL 的系统应该默认处理这种自然预期的行为。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-29
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    • 2010-11-14
    • 2012-06-15
    • 2010-10-17
    相关资源
    最近更新 更多