【问题标题】:Combination of dynamic pivot and static pivot in SQL ServerSQL Server 中动态透视和静态透视的组合
【发布时间】:2021-06-07 10:01:15
【问题描述】:

动态枢轴结合静态聚合

我有一个看起来像这样的表:

Place    State   Category CategCount MCount Buys Cost
London   UK      Old      3          NULL   22   4.50
London   UK      Old      6          5      3    22.00
Brussels BE      Young    2          NULL   4    3.50
Brussels BE      M        NULL       5      12   1.20
Brussels BE      M        NULL       2      1    1.20

我基本上需要:

  • 按多个字段分组(例如,地点、州、类别)
  • 按此类组计数
  • 每个组的总和 MCount、成本(以及其他,不在示例中),这些列是静态的
  • 透视类别并为每个此类分组类别(此处为 Old、Young)求和 CategCount。这是动态部分

结果应如下所示:

Count  Place    State   Category SumMCount SumOld SumYoung SumCost SumBuys 
2      London   UK      Old      5         9      0        26.50   25
1      Brussels BE      Young    0         0      2        3.50    4
2      Brussels BE      NULL     7         0      0        2.40    13

我知道如何获取动态数据透视查询(根据https://stackoverflow.com/a/38505375/111575),并且我知道如何执行静态部分。 但我不知道如何将两者结合起来。有人有什么想法吗?也许我做错了?

到目前为止我得到了什么:

以下内容为我提供了 OldYoung 的正确动态透视结果,但不确定如何将计数和“常规”总和/聚合添加到其中:

create table #temp
(
    Place nvarchar(20),
    State nvarchar(20),
    Category nvarchar(20) null,
    CategCount int null,
    MCount int null,
    Buys int,
    Cost int
)
    
insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)

DECLARE @cols  AS NVARCHAR(MAX)='';
DECLARE @query AS NVARCHAR(MAX)='';

SELECT @cols = @cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select @cols = substring(@cols, 0, len(@cols)) --trim "," at end
--select (@cols) as bm

set @query = 
'SELECT * from 
(
    select
        sum(CategCount) as totalCatCount,
        Category
    from #temp
    group by Category
) src
pivot 
(
    max(totalCatCount) for Category in (' + @cols + ')
) piv'

execute(@query)
drop table #temp

返回:

以下是没有旋转的“常规”查询:

select count(*) as count, place, state, category,
    sum(ISNULL(CategCount, 0)) as SumCatCount,
    sum(ISNULL(MCount, 0)) as SumMCount,
    sum(ISNULL(buys, 0)) as SumBuys,
    sum(Cost) as SumCost
from #temp
group by place, state, category

返回:

但它应该看起来像这样:

【问题讨论】:

  • 您必须将静态查询放入动态查询中。对于这样的事情,通常更容易首先为单个需求编写完整的静态查询,然后使其成为动态查询。然后你就知道你的目标是什么查询,并且只需要让它动态工作。
  • @Larnu,我确实有静态查询,它在我的帖子底部。但我坚持的事情是将两者结合起来。我的意思是,如何在某些列而不是其他列上进行旋转,但全部返回?或者您是否建议完全编写动态查询并放弃枢轴(即“手动”执行)?
  • 您应该知道,视图不仅不允许动态代码,它们还需要一个固定的(预定义的)列集。动态 SQL 可以使用视图,但视图不能使用也不能是动态的。
  • @RBarryYoung,谢谢,我不知道。因此,即使您将其填充到存储过程中,视图仍然不允许它,对吧?虽然我可以做相反的事情,写一个 SP 而不是一个视图,它没有那个限制。
  • @Abel 正确。有一两个晦涩难懂的技巧可以绕过动态 SQL 的限制,但即便如此,视图也必须有一个预定义的列集,就像表和表值函数一样。程序没有任何限制。

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

我已将查询的静态数据透视部分用作动态数据透视的来源。创建两组动态透视列列表。一个用于旋转,另一个使用 Coalesce() 选择旋转列(将 null 转换为 0)。如果任何类别都没有类别计数,则该类别已被替换为 null(case when)。 Category 和 SumCatCount 的另外两个别名已在枢轴条件下使用后创建。

你的答案如下:

 create table #temp
 (
     Place nvarchar(20),
     State nvarchar(20),
     Category nvarchar(20) null,
     CategCount int null,
     MCount int null,
     Buys int,
     Cost int
 )
     
 insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
 insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
 insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
 insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
 insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)
 
 
 DECLARE @cols  AS NVARCHAR(MAX)='';
 DECLARE @query AS NVARCHAR(MAX)='';
 DECLARE @colsForSelect  AS NVARCHAR(MAX)='';
 
 SET @cols = STUFF((SELECT distinct ',' + quotename(category)
             FROM #temp  where CategCount is not null 
             FOR XML PATH(''), TYPE
             ).value('.', 'NVARCHAR(MAX)') 
         ,1,1,'')
 
        
 SET @colsForSelect = STUFF((SELECT distinct ',' + ' Coalesce('+quotename(category)+',0) '+ quotename(category)
             FROM #temp  where CategCount is not null 
             FOR XML PATH(''), TYPE
             ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')
 
 
 --select (@cols) as bm
 
 set @query = 
 'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + @colsForSelect + ',SumCost,SumBuys from 
 (
     select count(*) as count, place, state,category OldCategory, category,    
     sum(ISNULL(MCount, 0)) as SumMCount,
    sum(ISNULL(CategCount, 0)) as OldSumCatCount,
    sum(ISNULL(CategCount, 0)) as SumCatCount,
     sum(Cost) as SumCost,
    sum(ISNULL(buys, 0)) as SumBuys
     
 from #temp
 group by place , state, category
 ) src
 pivot 
 (
     max(SumCatCount) for Category in (' + @cols + ')
 ) piv
 order by place desc,count'
 
 execute(@query)
 GO
count place state Category SumMCount Old Young SumCost SumBuys
2 London UK Old 5 9 0 26 25
1 Brussels BE Young 0 0 2 3 4
2 Brussels BE null 7 0 0 2 13

db小提琴here

【讨论】:

  • 酷!我们同时发布的,我在您发布此内容时发现了其中的一部分。但我更喜欢您的解决方案,因为结果集中没有NULL!谢谢!
  • 另外,您使用XML PATH hack,我以前见过它,但认为不需要它。将使用它作为实际查询的基础:)。
  • 拥有相同的时间真的很酷。欢呼!不用客气。最良好的祝愿。
  • 我注意到 MS SQL 服务器不允许您在 pivot 语句中执行 max(isnull(sumcatcount, 0)) for Category in ...,我很惊讶在结果集中摆脱 NULL 的解决方案相对复杂.但是,嘿,它有效! :)
  • 如果你有 SQL Server 2017 或更高版本,你应该能够使用 string_agg 而不是 FOR XML 技巧。
【解决方案2】:

感谢 cmets 中的 @Larnu 为我指明了正确的方向。他/她关于“你不能将静态加入动态查询”的声明,并且要么全部或全部都必须是动态的,这促使我构建动态部分并简单地扩展它。

我认为我需要以某种方式重复 PIVOT 部分中的列,但情况似乎并非如此。显然,只有您想要旋转的列(逻辑上如此,一旦您考虑一下)。

我还没有弄清楚的唯一部分是如何在结果集中摆脱NULL,希望有人能回答这个问题;)。

DECLARE @cols  AS NVARCHAR(MAX)='';
DECLARE @query AS NVARCHAR(MAX)='';

SELECT @cols = @cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select @cols = substring(@cols, 0, len(@cols)) --trim "," at end
--select (@cols) as bm

set @query = 
'SELECT * from 
(
    select
        count(*) as count,
        Place,
        State,
        Category,
        Category as CatPivot,
        sum(ISNULL(CategCount, 0)) as TotalCatCount,
        sum(ISNULL(Buys, 0)) as SumBuys,
        sum(ISNULL(Cost, 0)) as SumCost,
        sum(ISNULL(MCount, 0)) as SumMCount
    from #temp
    group by Category, Place, State
) src
pivot 
(
    max(TotalCatCount) for CatPivot in (' + @cols + ')
) piv'

execute(@query)

【讨论】:

    【解决方案3】:

    在这里,我分享了另一个相同的答案,但正如@Anthony Hancock 建议的那样,pivot 的动态列名是使用 string_agg() 而不是 stuff() 和 xml path for() 创建的。它速度更快,可读性更强(对于 SQL Server 2017 及更高版本)

    DECLARE @cols  AS NVARCHAR(MAX)='';
    DECLARE @query AS NVARCHAR(MAX)='';
    DECLARE @colsForSelect  AS NVARCHAR(MAX)='';
    
    
    select @cols =string_agg(category,',') from (
    select distinct category FROM #temp  where CategCount is not null )t
    
    
    select @colsForSelect= STRING_AGG(category,',') from 
    (select distinct 'coalesce('+category+',0) '+category category FROM #temp  where CategCount is not null )t
    
    
    set @query = 
    'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + @colsForSelect + ',SumCost,SumBuys from 
    (
        select count(*) as count, place, state,category OldCategory, category,    
        sum(ISNULL(MCount, 0)) as SumMCount,
        sum(ISNULL(CategCount, 0)) as OldSumCatCount,
        sum(ISNULL(CategCount, 0)) as SumCatCount,
        sum(Cost) as SumCost,
        sum(ISNULL(buys, 0)) as SumBuys
        
    from #temp
    group by place , state, category
    ) src
    pivot 
    (
        max(SumCatCount) for Category in (' + @cols + ')
    ) piv
    order by place desc,count'
    
    execute(@query)
    

    【讨论】:

    • 是的,STRING_AGG 函数是对 SQL Server 的一个很好的补充(尽管较晚)。我们部署到 Azure,但我的本地数据库仍然有点旧。升级的好理由!再次发送!
    • 它将为您节省一些毫秒。
    猜你喜欢
    • 2015-02-25
    • 1970-01-01
    • 2013-01-25
    • 2013-09-10
    • 2016-10-28
    • 2014-10-16
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多