MS SQL Pivot 来自多列的长数据

Posted

技术标签:

【中文标题】MS SQL Pivot 来自多列的长数据【英文标题】:MS SQL Pivot dat from long with multiple columns 【发布时间】:2022-01-04 09:17:12 【问题描述】:

我想将具有多个 id 列的表从长转换为宽。 我找到了一列的解决方案,但不是真正的多列。 我可以适应一列的最接近的解决方案是这个 T-SQL PIVOT data from long form to wide by a date

我的桌子或多或少是这样的,

create table t (id int, date date, varA_id int, VarB_id int, value int)
insert into t values
 (1,'2005-01-20',1, 1,197)
,(2,'2005-01-20',1,2,58)
,(3,'2005-01-20',1,3,90)
,(4,'2005-01-20',2,1,210)
,(5,'2005-01-20',2,2,133)
,(6,'2005-01-20',2,3,67)
,(7,'2005-01-20',3,1,87)
,(8,'2005-01-20',3,2,87)
,(9,'2005-01-20',3,3,87)

实际上没有日期,但没关系。我想以一种方式进行传播,以便为 VarA_idVarB_id 的每个排列获取列

所以我的预期结果应该是这样的

我的实际表有三个 _id 列和更多排列,所以我真的需要一个通用的解决方案。

基于我链接中的其他解决方案,我希望这样的事情能够奏效。我调整了创建列名的顶部,这将起作用。我不知道如何真正调整获取值的底部。

declare @cols nvarchar(max);
declare @sql  nvarchar(max);
  select @cols = stuff((
    select distinct 
      ', ' + 'VarA_'+convert(varchar(10),varA_id) + '_VarB_'+convert(varchar(10),varB_id)
      from t 
      order by 1
      for xml path (''), type).value('.','nvarchar(max)')
    ,1,2,'')
select  @sql = '
 select Id, date, ' + @cols + '
  from  (
    select Id, date, varA_id = ''v''+convert(varchar(10),varA_id), value
      from t
      ) as t
 pivot (sum([value]) for [varA_id] in (' + @cols + ') ) p'
select @sql
exec(@sql);

【问题讨论】:

预期结果确实会帮助我们帮助您,但它听起来就像您想要一个动态的支点。事实上,这最好在您的表示层中完成,而不是在数据库层中。 你的意思是在将数据提取到 R 或 Power Query 等其他软件之后? 我的意思是在任何应用程序中向最终用户显示数据;大概是某种报告软件,因为这通常是需要此类要求的原因。 我需要在 SQL 中执行此操作还有其他一些原因。我更改了数据并添加了输出图片 假设单个日期可能有超过 9 行? 【参考方案1】:

您的动态 sql 的主要问题是什么? 是源查询中构造的名称与生成的列名不匹配。

这是一个修复:

declare @cols varchar(max) = null;
declare @sql  nvarchar(max);

select @cols = concat(@cols+', '+char(10), quotename(concat('VarA_', varA_id, '_VarB_', varB_id))) 
from test
group by varA_id, varB_id
order by varA_id, varB_id;

-- select @cols as cols;

set @sql = 'select * '+char(10)+
  'from ( ' +char(10)+
  ' select [date], [value], ' +char(10)+
  ' concat(''VarA_'',varA_id,''_VarB_'',varB_id) as Col ' +char(10)+
  ' from test ' +char(10)+
  ') as src ' +char(10)+
  'pivot (sum([value]) for Col in ('+char(10)+ @cols +char(10)+')) pvt';
 
-- select @sql as sql;

exec(@sql);
date VarA_1_VarB_1 VarA_1_VarB_2 VarA_1_VarB_3 VarA_2_VarB_1 VarA_2_VarB_2 VarA_2_VarB_3 VarA_3_VarB_1 VarA_3_VarB_2 VarA_3_VarB_3
2005-01-20 197 58 90 210 133 67 87 87 87

db小提琴here

【讨论】:

我只需要更改部分来定义@cols,我会接受你的,因为它比我的更好select @cols = stuff(( select distinct ', ' + 'VarA_'+convert(varchar(10),varA_id)+ '_VarB_' +convert(varchar(10),varB_id) from t order by 1 for xml path (''), type).value('.','nvarchar(max)') ,1,2,'') 谢谢。我刚刚注意到您使用了 DISTINCT。所以我通过添加一个 GROUP BY 来创建字段名称来更改解决方案。 啊没想到,这对我来说比其他代码更容易理解,我不太明白【参考方案2】:

到目前为止,我自己的解决方案是添加一个帮助列,基本上只是做其他问题所做的事情。我需要改进这一点,所以我不添加列,我想要更好的名字,但至少这表明了我想要的。

alter table t add help_col nvarchar(10)
Update t
set help_col=convert(varchar(10),varA_id)+convert(varchar(10),varB_id)

declare @cols nvarchar(max);
declare @sql  nvarchar(max);
  select @cols = stuff((
    select distinct 
      ', ' + 'v'+convert(varchar(10),help_col)
      from t 
      order by 1
      for xml path (''), type).value('.','nvarchar(max)')
    ,1,2,'')
select  @sql = '
 select date, ' + @cols + '
  from  (
    select date, help_col = ''v''+convert(varchar(10),help_col), value
      from t
      ) as t
 pivot (sum([value]) for [help_col] in (' + @cols + ') ) p'
select @sql
exec(@sql);

结果

   select date, v11, v12, v13, v21, v22, v23, v31, v32, v33    from  (      select date, help_col = 'v'+convert(varchar(10),help_col), value        from t        ) as t   pivot (sum([value]) for [help_col] in (v11, v12, v13, v21, v22, v23, v31, v32, v33) ) p

产生

date        v11 v12 v13 v21 v22 v23 v31 v32 v33
2005-01-20  197 58  90  210 133 67  87  87  87

【讨论】:

以上是关于MS SQL Pivot 来自多列的长数据的主要内容,如果未能解决你的问题,请参考以下文章

具有 INT 和 NVARCHAR 数据类型的多列的 SQL Pivot [重复]

如何在sql中使用pivot到多列

带有 MIN() 和多列的 SQL PIVOT 表

oracle 多列 列转行

具有多列日期的 SQL Server 数据透视表

PIVOT/UNPIVOT 多行多列