SQL 动态分组或数据透视和小计

Posted

技术标签:

【中文标题】SQL 动态分组或数据透视和小计【英文标题】:SQL Dynamic Grouping or Pivot and subtotals 【发布时间】:2019-09-18 15:26:19 【问题描述】:

我需要在 SQL 中创建一个类似于以下内容的动态分组表:

Customer    Rating    Asset Type    Exposure
ABC         9-        apples        -10
                      bananas        20
                      subtotal       10
DEF         5         grapes          5
GHI         8+        apples         15
                      bananas         9
                      subtotal       25

我已经尝试过枢轴和左连接,但我得到了我需要的东西。

代码

SELECT 
A.CUSTOMER
,A.FRR as Rating
,C.PRODUCT_TYPE
,SUM(D.UTILIZATION) as Exposure
from table1 A
LEFT JOIN table1 B ON A.CUSTOMER = 
B.CUSTOMER and A.PRODUCT_TYPE = 'Apples'
LEFT JOIN table1 C ON A.CUSTOMER = 
C.CUSTOMER and A.PRODUCT_TYPE = 'Bananas'
LEFT JOIN table1 D ON A.CUSTOMER = 
D.CUSTOMERand A.PRODUCT_TYPE = 'Grapes'
GROUP BY A.CUSTOMER
,A.FRR
,C.PRODUCT_TYPE
;

您能提供的任何帮助/指导都会很棒!

【问题讨论】:

所有这些相关数据真的存储在一个表的不同行的相同列中吗?您将 table1 加入自身 4 次 嗨 Caius,感谢您的回答,不,但这是我根据我在 google/*** 上找到的其他研究尝试的最后一次尝试 【参考方案1】:

Oracle GROUP BY 支持 ROLLUP 和 GROUPING SETS,可为您提供更广泛的聚合:

所以您的基本查询如下所示:

SELECT 
  ... 
GROUP BY 
  A.CUSTOMER,A.FRR,C.PRODUCT_TYPE

它会产生:

Customer    Rating    Asset Type    Exposure
ABC         9-        apples        -10
ABC         9-        bananas        20
DEF         5         grapes          5
DEF         5         apples         15
DEF         5         bananas         9

如果您将其更改为按分组集分组:

SELECT 
  ... 
GROUP BY GROUPING SETS (
  (A.CUSTOMER,A.FRR,C.PRODUCT_TYPE),(A.CUSTOMER,A.FRR)
)

您将获得对水果进行小计的额外行,水果名称为 NULL 表示它是一个总数(实际上 GROUPING() 函数表示它是“null 因为它是一个总数”还是“null 因为基础数据确实为空”但是..):

Customer    Rating    Asset Type    Exposure
ABC         9-        apples        -10
ABC         9-        bananas        20
ABC         9-        NULL           10     <-- total of fruit for ABC/9-
DEF         5         grapes          5
DEF         5         apples         15
DEF         5         bananas         9
DEF         5         NULL           29    <-- total of fruit for DEF/5

GROUP BY ROLLUP(A.CUSTOMER,A.FRR,C.PRODUCT_TYPE) 会做类似的事情,除了它从右到左一直到根(你会得到像 ABC NULL NULL 45DEF NULL NULL 62NULL NULL NULL 107 这样的总计行

脚注:我假设您的示例数据有错误 - 我看不出 5+15+9 如何加起来等于 25,或者 DEF 和 GHI 是否应该相同等。最后,我不会如果行上的数据与上面的行相同,则不要尝试隐藏行上的数据,因为这意味着保持数据集有意义的唯一因素就是顺序。在用户看到它之前在前端做是可以的,但是在后端做它可能会在使用之前传递到几个地方,这意味着这些地方中的任何一个都可能会失去顺序并破坏数据。有关更多讨论,请参阅我刚刚在此处提出的非常相似的答案:Get count of particular category and further bifurcated sub category using SQL query

如果您仍然不顾一切地想隐藏第 N 行 X 列中与第 N-1 行 X 列中的数据相同的数据,您可以使用 LAG 分析函数拉取上一行数据并做出决定关于是否与该行数据相同是否为空


你问过是否只有一行是否总计。在这种情况下,我们也许可以做一些事情,比如计算明细行的数量,并排除那些总计行且它汇总的明细行数为 1 的行

为此,将以下行添加到查询的末尾:

HAVING NOT (GROUPING(PRODUCT_TYPE) = 1 and COUNT(DISTINCT PRODUCT_TYPE) = 1) 

有关此的更多信息,请参阅 db fiddle:https://dbfiddle.uk/?rdbms=oracle_18&fiddle=935992f52f067c84b2899e2acea688a0

基本上它是如何工作的:

GROUPING(PRODUCT_TYPE) 为详细信息行返回 0,为小计行返回 1 COUNT(DISTINCT PRODUCT_TYPE) 为详细信息行返回 1,为总计行返回 N,其中 N 是滚动到该总计中的不同资产的数量: 10 行“apple”生成的 COUNT() 为 10,但 COUNT(DISTINCT) 为 1。 5 行“apple”和 5 行“orange”生成 COUNT() 为 10 和 COUNT(DISTINCT) 为 2。

通过排除不同资产类型的计数为 1 且该行是总计行的行,我们抑制了实质上重复详细信息行的总计行

【讨论】:

凯乌斯,谢谢你的帮助!这很棒!最后一个问题,分组集效果很好,如果每个客户有超过 1 个订单项,有没有办法只小计?再次感谢您的帮助!约翰 是的,由于我匆忙添加了这个例子,我的数学受到了影响 没有办法只选择性地应用分组集,但您当然可以删除它之后计算的行。我会在演示中添加一些东西 凯乌斯,谢谢!这简单明了,可以解决问题。出色的编码,我感谢您的解释!【参考方案2】:

您可以将lag() 解析函数与rollup(customer, frr, product_type) 一起使用:

with t1 as
(
select customer, frr, product_type, sum(utilization) as exposure,
       lag(customer) over (order by customer,product_type) as lg_cst,
       lag(frr) over (order by customer,product_type) as lg_frr
  from table1
 group by rollup(customer, frr, product_type) 
), t2 as
(
select 
       case when nvl(lg_cst,'') = customer 
       then 
            null
       else   
            customer
       end as customer,
       case when nvl(lg_frr,'') = frr 
       then 
            null
       else   
            frr
       end as frr2,
       case when nvl(lg_frr,'') = frr and product_type is null and customer is not null then 
              'subtotal'
            when customer is null then
              'Grand Total'
            else 
               product_type
       end as product_type, exposure
  from t1
)
select *
  from t2
 where product_type is not null; 

Demo

【讨论】:

以上是关于SQL 动态分组或数据透视和小计的主要内容,如果未能解决你的问题,请参考以下文章

排除元素的 Excel 数据透视表小计

动态 SQL 透视查询中的分组和聚合函数

从 R 中的数据透视表库呈现的数据透视表中删除小计和总计

Excel 数据透视表:将小计乘以标量

Pandas 数据透视表和小计

数据透视表不显示小计