在 SQL 服务器中以行方式求和进行透视
Pivot with row wise Sum in SQL Server
TABLE-A:
PID
BrandName
1
Brand1
2
Brand2
3
Brand3
4
Brand4
5
Brand5
6
Brand6
7
Brand7
8
Brand8
TABLE-B:
CustNo
Name
BrandName
Qty
Amt
1
C1
Brand1
3
300
1
C1
Brand2
2
400
1
C1
Brand4
1
300
1
C1
Brand5
2
100
2
C2
Brand1
2
200
2
C2
Brand3
1
200
3
C3
Brand2
1
300
3
C3
Brand7
3
150
预期结果:-
CustNo
Name
Brand1
Brand2
Brand3
Brand4
Brand5
Brand6
Brand7
Brand8
Amt
1
C1
3
2
0
1
2
0
0
0
1100
2
C2
2
0
1
0
0
0
0
0
400
3
C3
0
1
0
0
0
0
3
0
450
我试过的支点:-
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(BrandName) from [TABLE-A] order by PID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set @query = 'SELECT CustNo,[Name],' + @cols + '
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float)) as Amt,BrandName from [TABLE-B] group by CustNo,[Name],BrandName,Qty
) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p '
execute(@query)
您的查询几乎没有错误。首先,您 selected 列 'Slab' 不在您的 table 中(可能是由于从另一个查询复制)相反,您需要 select custno 和名称。
那么您的查询将 运行 但是您将针对每个客户有三行,因为每个客户在数量字段中有三个不同的值。背后的原因是内部查询中的group by子句(group by CustNo,[Name],BrandName,Qty)。相反,我使用 window 函数为每个客户求和(amt)。
我还使用了两组动态列名来去除结果中的空值。一个按照您在代码中使用的方式进行转换 (@cols),其他列表包含 coalesce(columnname,0) 以将 null 转换为 0。
如果您使用的是 SQL Server 2017 及更高版本,那么我建议使用 string_agg() 来连接列名,因为它更容易、性能更快。我在查询 #2 中使用过它。
架构和插入语句:
create table [Table-A](PID int, BrandName varchar(50));
insert into [Table-A] values(1 ,'Brand1');
insert into [Table-A] values(2 ,'Brand2');
insert into [Table-A] values(3 ,'Brand3');
insert into [Table-A] values(4 ,'Brand4');
insert into [Table-A] values(5 ,'Brand5');
insert into [Table-A] values(6 ,'Brand6');
insert into [Table-A] values(7 ,'Brand7');
insert into [Table-A] values(8 ,'Brand8');
create table [TABLE-B]( CustNo int,Name varchar(10),BrandName varchar(50),Qty int, Amt int);
insert into [TABLE-B] values(1 ,'C1', 'Brand1', 3, 300);
insert into [TABLE-B] values(1 ,'C1', 'Brand2', 2, 400);
insert into [TABLE-B] values(1 ,'C1', 'Brand4', 1, 300);
insert into [TABLE-B] values(1 ,'C1', 'Brand5', 2, 100);
insert into [TABLE-B] values(2 ,'C2', 'Brand1', 2, 200);
insert into [TABLE-B] values(2 ,'C2', 'Brand3', 1, 200);
insert into [TABLE-B] values(3 ,'C3', 'Brand2', 1, 300);
insert into [TABLE-B] values(3 ,'C3', 'Brand7', 3, 150);
查询#1(使用 stuff() 和 xml 路径 for())
DECLARE @cols AS NVARCHAR(MAX),
@colsForSelect AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
SET @colsForSelect = STUFF((SELECT ',' + ' Coalesce('+quotename(BrandName)+',0) '+ quotename(BrandName)
FROM [TABLE-A] order by pid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select @cols = STUFF((SELECT ',' + QUOTENAME(BrandName) from [TABLE-A] order by PID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set @query = 'SELECT custno,name,' + @colsForSelect + ',Amt
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float))over(partition by custno) as Amt,BrandName from [TABLE-B] ) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p '
execute(@query)
输出:
custno
name
Brand1
Brand2
Brand3
Brand4
Brand5
Brand6
Brand7
Brand8
Amt
1
C1
3
2
0
1
2
0
0
0
1100
2
C2
2
0
1
0
0
0
0
0
400
3
C3
0
1
0
0
0
0
3
0
450
查询#2(使用 string_agg() 而不是 stuff() 和 xml path())
DECLARE @cols AS NVARCHAR(MAX),
@colsForSelect AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @colsForSelect= string_agg('coalesce('+BrandName+',0) '+QUOTENAME(BrandName)+' ',' ,') from [TABLE-A]
select @cols = string_agg(QUOTENAME(BrandName),',') from [TABLE-A]
set @query = 'SELECT custno,name,' + @colsForSelect + ',Amt
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float))over(partition by custno) as Amt,BrandName from [TABLE-B] ) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p'
execute(@query)
输出:
custno
name
Brand1
Brand2
Brand3
Brand4
Brand5
Brand6
Brand7
Brand8
Amt
1
C1
3
2
0
1
2
0
0
0
1100
2
C2
2
0
1
0
0
0
0
0
400
3
C3
0
1
0
0
0
0
3
0
450
db<>fiddle here
TABLE-A:
PID | BrandName |
---|---|
1 | Brand1 |
2 | Brand2 |
3 | Brand3 |
4 | Brand4 |
5 | Brand5 |
6 | Brand6 |
7 | Brand7 |
8 | Brand8 |
TABLE-B:
CustNo | Name | BrandName | Qty | Amt |
---|---|---|---|---|
1 | C1 | Brand1 | 3 | 300 |
1 | C1 | Brand2 | 2 | 400 |
1 | C1 | Brand4 | 1 | 300 |
1 | C1 | Brand5 | 2 | 100 |
2 | C2 | Brand1 | 2 | 200 |
2 | C2 | Brand3 | 1 | 200 |
3 | C3 | Brand2 | 1 | 300 |
3 | C3 | Brand7 | 3 | 150 |
预期结果:-
CustNo | Name | Brand1 | Brand2 | Brand3 | Brand4 | Brand5 | Brand6 | Brand7 | Brand8 | Amt |
---|---|---|---|---|---|---|---|---|---|---|
1 | C1 | 3 | 2 | 0 | 1 | 2 | 0 | 0 | 0 | 1100 |
2 | C2 | 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 400 |
3 | C3 | 0 | 1 | 0 | 0 | 0 | 0 | 3 | 0 | 450 |
我试过的支点:-
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(BrandName) from [TABLE-A] order by PID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set @query = 'SELECT CustNo,[Name],' + @cols + '
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float)) as Amt,BrandName from [TABLE-B] group by CustNo,[Name],BrandName,Qty
) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p '
execute(@query)
您的查询几乎没有错误。首先,您 selected 列 'Slab' 不在您的 table 中(可能是由于从另一个查询复制)相反,您需要 select custno 和名称。
那么您的查询将 运行 但是您将针对每个客户有三行,因为每个客户在数量字段中有三个不同的值。背后的原因是内部查询中的group by子句(group by CustNo,[Name],BrandName,Qty)。相反,我使用 window 函数为每个客户求和(amt)。
我还使用了两组动态列名来去除结果中的空值。一个按照您在代码中使用的方式进行转换 (@cols),其他列表包含 coalesce(columnname,0) 以将 null 转换为 0。
如果您使用的是 SQL Server 2017 及更高版本,那么我建议使用 string_agg() 来连接列名,因为它更容易、性能更快。我在查询 #2 中使用过它。
架构和插入语句:
create table [Table-A](PID int, BrandName varchar(50));
insert into [Table-A] values(1 ,'Brand1');
insert into [Table-A] values(2 ,'Brand2');
insert into [Table-A] values(3 ,'Brand3');
insert into [Table-A] values(4 ,'Brand4');
insert into [Table-A] values(5 ,'Brand5');
insert into [Table-A] values(6 ,'Brand6');
insert into [Table-A] values(7 ,'Brand7');
insert into [Table-A] values(8 ,'Brand8');
create table [TABLE-B]( CustNo int,Name varchar(10),BrandName varchar(50),Qty int, Amt int);
insert into [TABLE-B] values(1 ,'C1', 'Brand1', 3, 300);
insert into [TABLE-B] values(1 ,'C1', 'Brand2', 2, 400);
insert into [TABLE-B] values(1 ,'C1', 'Brand4', 1, 300);
insert into [TABLE-B] values(1 ,'C1', 'Brand5', 2, 100);
insert into [TABLE-B] values(2 ,'C2', 'Brand1', 2, 200);
insert into [TABLE-B] values(2 ,'C2', 'Brand3', 1, 200);
insert into [TABLE-B] values(3 ,'C3', 'Brand2', 1, 300);
insert into [TABLE-B] values(3 ,'C3', 'Brand7', 3, 150);
查询#1(使用 stuff() 和 xml 路径 for())
DECLARE @cols AS NVARCHAR(MAX),
@colsForSelect AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
SET @colsForSelect = STUFF((SELECT ',' + ' Coalesce('+quotename(BrandName)+',0) '+ quotename(BrandName)
FROM [TABLE-A] order by pid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select @cols = STUFF((SELECT ',' + QUOTENAME(BrandName) from [TABLE-A] order by PID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set @query = 'SELECT custno,name,' + @colsForSelect + ',Amt
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float))over(partition by custno) as Amt,BrandName from [TABLE-B] ) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p '
execute(@query)
输出:
custno | name | Brand1 | Brand2 | Brand3 | Brand4 | Brand5 | Brand6 | Brand7 | Brand8 | Amt |
---|---|---|---|---|---|---|---|---|---|---|
1 | C1 | 3 | 2 | 0 | 1 | 2 | 0 | 0 | 0 | 1100 |
2 | C2 | 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 400 |
3 | C3 | 0 | 1 | 0 | 0 | 0 | 0 | 3 | 0 | 450 |
查询#2(使用 string_agg() 而不是 stuff() 和 xml path())
DECLARE @cols AS NVARCHAR(MAX),
@colsForSelect AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @colsForSelect= string_agg('coalesce('+BrandName+',0) '+QUOTENAME(BrandName)+' ',' ,') from [TABLE-A]
select @cols = string_agg(QUOTENAME(BrandName),',') from [TABLE-A]
set @query = 'SELECT custno,name,' + @colsForSelect + ',Amt
from
(
select CustNo,[Name],Qty,SUM(cast([amt] as float))over(partition by custno) as Amt,BrandName from [TABLE-B] ) x
pivot
(
max(Qty)
for brandname in (' + @cols + ')
) p'
execute(@query)
输出:
custno | name | Brand1 | Brand2 | Brand3 | Brand4 | Brand5 | Brand6 | Brand7 | Brand8 | Amt |
---|---|---|---|---|---|---|---|---|---|---|
1 | C1 | 3 | 2 | 0 | 1 | 2 | 0 | 0 | 0 | 1100 |
2 | C2 | 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 400 |
3 | C3 | 0 | 1 | 0 | 0 | 0 | 0 | 3 | 0 | 450 |
db<>fiddle here