Pivot with row wise Sum in SQL Server [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
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
Expected Result:-
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
Pivot I tried:-
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)

There are few things wrong in your query. First of all you selected column 'Slab' which is not in your table (might by due to copy from another query) Instead you need to select custno and name.
Then your query will run but you will have three rows for for each customer since each customer has three distinct value in quantity field. The reason behind is the group by clause (group by CustNo,[Name],BrandName,Qty) in inner query. Instead I have used window function to sum(amt) for each customer.
I have also used two set of dynamic column names to get rid of null value in the result. One to pivot as you used in your code (#cols) and other list contains coalesce(columnname,0) to convert null into 0.
And if you are using SQL Server 2017 and onward version then I would suggest to use string_agg() to concatenate the column names since it's easier and faster in performance. I have used it in Query#2.
Schema and insert statement:
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);
Query#1 (using stuff() and xml path 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)
Output:
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
Query#2 (using string_agg() instead of stuff() and for 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)
Output:
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

Related

SQL Pivot Query with aggregation & Total

Table-A
ProductId
BrandName
1
Brand-1
2
Brand-2
3
Brand-3
Table-B
DCDate
DCNo
ProductId
Weight
2021-03-09
3
1
12.5
2021-03-09
3
1
12.6
2021-03-09
3
1
12.5
2021-03-09
3
2
10.5
2021-03-09
3
2
10.4
2021-03-09
3
3
15.5
2021-03-09
1
1
12.5
2021-03-09
1
3
15.7
2021-03-09
2
2
10.6
2021-03-09
4
1
12.7
2021-03-09
4
1
12.6
Expected Result:-
BrandName
1
2
3
4
Brand-1
1
0
3
2
Brand-2
0
1
2
0
Brand-3
1
0
1
0
Need Dynamic Pivot Query
What i tried
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([DCNo]) from [Table-B] FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
SELECT a.[DCNo] , b.[BrandName], count(*) as Total FROM [Table-B] a inner join [Table-A] b on a.[ProductID]=b.[ProductID] group by [DCNo] , b.[BrandName] order by DCNo, b.[BrandName]
) x
pivot
(
max(Total)
for DCNo in (' + #cols + ')
) p '
execute(#query)
I've recreated it in a sqlfiddle: http://sqlfiddle.com/#!18/d1554/9 .
You were close but I sometimes find it easier to move all sum/count/grouping logic to the PIVOT section of the query instead. Results in simpler queries (most of the times)
CREATE TABLE [Table-A] (
ProductId INT,
BrandName VARCHAR(50)
)
CREATE TABLE [Table-B](
DCDate DATETIME,
DCNo INT,
ProductId INT,
Weight DECIMAL
)
INSERT INTO [Table-A] VALUES
(1, 'CAT'),
(2, 'APPLE'),
(3, 'PARROT')
INSERT INTO [Table-B] VALUES
('2021-03-09', 3, 1, 12.5),
('2021-03-09', 3, 1, 12.6),
('2021-03-09', 3, 1, 12.5),
('2021-03-09', 3, 2, 10.5),
('2021-03-09', 3, 2, 10.4),
('2021-03-09', 3, 3, 15.5),
('2021-03-09', 1, 1, 12.5),
('2021-03-09', 1, 3, 15.7),
('2021-03-09', 2, 2, 10.6),
('2021-03-09', 4, 1, 12.7),
('2021-03-09', 4, 1, 12.6)
-- Query
DECLARE #cols AS NVARCHAR(MAX) = '';
DECLARE #nullcols AS NVARCHAR(MAX) = '';
DECLARE #query AS NVARCHAR(MAX);
-- Determine columns
;WITH cte AS (
SELECT
DISTINCT
dcno,
',' + QUOTENAME([DCNo]) AS col,
', ISNULL(' + QUOTENAME([DCNo]) + ', 0) AS ' + QUOTENAME([DCNo]) AS nullcol
FROM [Table-B]
)
SELECT
#cols += col,
#nullcols += nullcol
FROM cte
ORDER BY dcno
SET #cols = SUBSTRING(#cols, 2, LEN(#cols))
-- create query
SET #query = N';with CTE AS
(
SELECT
a.BrandName,
a.ProductId,
b.DCNo,
1 as Value
FROM [Table-A] a
INNER JOIN [Table-B] b
ON a.ProductId = b.ProductId
)
SELECT BrandName ' + #nullcols + ' FROM
(SELECT * FROM cte) p
PIVOT
(
SUM(Value)
FOR DCNo IN (' + #cols + ')
) AS pvt
ORDER BY pvt.ProductId
'
-- SELECT #query
execute(#query)

Order by in Pivot Column not in Result

Table-A
Productid
Brandname
1
C Brand
2
K Brand
3
A Brand
Table-B
Productid
Rate
Slab
1
10
1
2
20
1
3
30
1
1
12
2
2
22
2
3
32
2
Dynamic Pivot Query:-
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Brandname) from [Table-A] FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT Slab,' + #cols + '
from
(
select a.BrandName, b.Rate,b.Slab from [Table-A] a inner join [Table-B] b on a.productid=b.productid
) x
pivot
(
max(Rate)
for Brandname in (' + #cols + ')
) p '
execute(#query)
Result:-
Slab
A Brand
C Brand
K Brand
1
30
10
20
2
32
12
22
But I need to order Brandname Column using productid
Expected Result:-
Slab
C Brand
K Brand
A Brand
1
10
20
30
2
12
22
32
While preparing column list for dynamic pivot use order by productid and remove distinct your problem will be solved.
Schema and insert statements:
create table [Table-A](Productid int,Brandname varchar(10));
insert into [Table-A] values(1 ,'C Brand');
insert into [Table-A] values(2 ,'K Brand');
insert into [Table-A] values(3 ,'A Brand');
create table [Table-B] (Productid int, Rate int , Slab int)
insert into [Table-B] values(1 ,10 ,1);
insert into [Table-B] values(2 ,20 ,1);
insert into [Table-B] values(3 ,30 ,1);
insert into [Table-B] values(1 ,12 ,2);
insert into [Table-B] values(2 ,22 ,2);
insert into [Table-B] values(3 ,32,2);
Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Brandname) from [Table-A] order by productid FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT Slab,' + #cols + '
from
(
select a.BrandName, b.Rate,b.Slab from [Table-A] a inner join [Table-B] b on a.productid=b.productid
) x
pivot
(
max(Rate)
for Brandname in (' + #cols + ')
) p '
execute(#query)
Output:
Slab
C Brand
K Brand
A Brand
1
10
20
30
2
12
22
32
If you are using SQL Server 2017 or above string_agg() will be smarter and faster option.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select distinct #cols=STRING_AGG(quotename(brandname),',') from [Table-A]
set #query = 'SELECT Slab,' + #cols + '
from
(
select a.BrandName, b.Rate,b.Slab from [Table-A] a inner join [Table-B] b on a.productid=b.productid
) x
pivot
(
max(Rate)
for Brandname in (' + #cols + ')
) p '
execute(#query)
Output:
Slab
C Brand
K Brand
A Brand
1
10
20
30
2
12
22
32
db<>fiddle here
You want the columns to be in a particular order. You don't seem to need the SELECT DISTINCT in the subquery, based on your data. But if you do, you can use aggregation instead:
select #cols = STUFF((SELECT ',' + QUOTENAME(Brandname)
FROM [Table-A]
GROUP BY BrandName
ORDER BY MIN(ProductId)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'
), 1, 1, '')
However, without aggregation:
select #cols = STUFF((SELECT ',' + QUOTENAME(Brandname)
FROM [Table-A]
ORDER BY ProductId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'
), 1, 1, '');
Once #cols is in the right order, the rest of the query should fall into place.

SQL to convert distinct item in row to column

I have a table as
Date Item Quantity
20170101 Mango 5
20170101 Orange 6
20170102 Mango 7
20170102 Orange 8
I want below output
Date Mango Orange
20170101 5 6
20170102 7 8
for this i used below sql query
SELECT
Date,
SUM(case when Item='Mango' then Quantity else 0 end) AS Mango,
SUM(case when Item='Orange' then Quantity else 0 end) AS Orange
FROM orderTable
GROUP BY date
but this is kind of hard coding for Mango and Orange. What if I need new item in orderTable. Can any one suggest me how can I make this query dynamic. So that if I add new item it automatically create new coulmn having item name as name and it will have 0 value under column against date when order not placed for that item.
like
Date Item Quantity
20170101 Mango 5
20170101 Orange 6
20170102 Mango 7
20170102 Orange 8
20170102 Cherry 9
then output should be as ...
Date Mango Orange Cherry
20170101 5 6 0
20170102 7 8 9
IF OBJECT_ID('Test') IS NOt NUll
DROP TABLE Test
CREATE TABLE Test
(
Date VARCHAR(100),
Item VARCHAR(100),
Quantity INT
)
INSERT Test VALUES
('20170101', 'Mango', 5),
('20170101', 'Orange', 6),
('20170102', 'Mango', 7),
('20170102', 'Orange', 8),
('20170102', 'Cherry', 9)
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #Columns AS VARCHAR(MAX)
DECLARE #Columns2 AS VARCHAR(MAX)
SELECT #Columns = COALESCE(#Columns + ',','') + QUOTENAME(Item)
FROM (SELECT DISTINCT Item FROM Test) AS B
ORDER BY B.Item
SELECT #Columns2 = COALESCE(#Columns2 + ',','') + 'ISNULL(' + QUOTENAME(Item) + ', 0) AS ' + Item
FROM (SELECT DISTINCT Item FROM Test) AS B
ORDER BY B.Item
SET #SQL = '
WITH PivotData AS
(
SELECT Date, Item, Quantity FROM Test
)
SELECT
Date, ' + #Columns2 + '
FROM PivotData
PIVOT
(
SUM(Quantity)
FOR Item
IN (' + #Columns + ')
) AS PivotResult
ORDER BY Date'
EXEC(#SQL);
DROP TABLE Test
Result:
Date Cherry Mango Orange
20170101 0 5 6
20170102 9 7 8
Reference (code pic not shown but you can access it if you view source the page) :
http://sqlmag.com/t-sql/pivoting-dynamic-way
IF OBJECT_ID('Tempdb..#Temp') IS NOt NUll
Drop Table #Temp
;With cte([Date] ,Item ,Quantity)
AS
(
SELECT '20170101','Mango' ,5 Union all
SELECT '20170101','Orange' ,6 Union all
SELECT '20170102','Mango' ,7 Union all
SELECT '20170102','Orange' ,8 Union all
SELECT '20170102','Cherry' ,9
)
SELECT * INTO #Temp FROM cte
DECLARE #dynamicCol nvarchar(max),
#Sql nvarchar(max),
#dynamicCol2 nvarchar(max)
SELECT #dynamicCol=STUFF((SELECT DISTINCT ', ' + 'ISNULL('+Item +',''0'') AS '+ Item FROM #Temp
FOR XML PATH('')),1,1,'')
SELECT #dynamicCol2=STUFF((SELECT DISTINCT ', ' + Item FROM #Temp
FOR XML PATH('')),1,1,'')
SET #Sql='
SELECT [Date] , '+ #dynamicCol +' From
(
SELECT [Date] ,Item ,Quantity From
#temp
)AS Src
PIVOT
(
MAX([Quantity]) For [Item ] IN ('+#dynamicCol2+')
)
AS Pvt
'
PRINT #Sql
EXEC(#Sql)
OutPut
Date Cherry Mango Orange
----------------------------------
20170101 0 5 6
20170102 9 7 8
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Item)
FROM dbo.OrderTable c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT DateOrdered, ' + #cols + ' from
(
select Item, DateOrdered, Quantity
from dbo.OrderTable
) x
pivot
(
Sum(Quantity)
for Item in (' + #cols + ')
) p '
execute(#query)
If you do this it will give you result that you want
Try this :
CREATE TABLE [dbo].[test](
[Date] [int] NULL,
[Item] [nvarchar](50) NULL,
[Quantity] [int] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170101, N'Mango', 5)
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170101, N'Orange', 6)
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170102, N'Mango', 7)
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170102, N'Orange', 8)
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170102, N'Cherry', 5)
GO
INSERT [dbo].[test] ([Date], [Item], [Quantity]) VALUES (20170103, N'Cherry', 2)
GO
dynamic sql :
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(Item)
from test
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT DATE, ' + #cols + N' from
(
select Date,Item, Quantity from test
) x
pivot
(
max(Quantity)
for Item in (' + #cols + N')
) p '
exec sp_executesql #query;
result :
DATE Cherry Mango Orange
20170101 NULL 5 6
20170102 5 7 8
20170103 2 NULL NULL

SQL PIVOT result set [duplicate]

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 8 years ago.
Looking for help with pivoting a result set as I'm very new to it.
Here I have test data inserted into a table.
CREATE TABLE #temp (procCode int, member_id varchar(10))
INSERT INTO #temp(procCode,member_id)
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90658,'jjjkk'
UNION all
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90658,'jjjkk'
UNION all
SELECT 90658,'jjjj'
UNION all
SELECT 90658,'k'
UNION all
SELECT 90649,'jjjj'
UNION all
SELECT 90649,'k'
UNION all
SELECT 906,'jjjj'
UNION all
SELECT 906,'jjjj'
select
member_id,procCode, COUNT(*) as countProcCode
FROM #temp
GROUP BY member_id,procCode
This right now outputs data like this:
member_id procCode CountProcCode
jjjj 906 2
jjjj 90649 1
jjjkk 90658 2
k 90649 1
jjjj 90658 3
k 90658 3
How I need it to display is like this:
member_id Count906 count90649 count90658
jjjj 2 1 3
k 0 1 3
jjjkk 0 0 2
Any help is greatly appreciated.
There are more than just these procCodes and member_id so I couldn't really say where member_id in (506,50658,50649) as there are additional ones that could appear.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.procCode)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT member_id, ' + #cols + ' from
(
select member_id, member_id as b
, procCode
from #temp
) x
pivot
(
count(b)
for procCode in (' + #cols + ')
) p '
execute(#query)
drop table #temp
Results
member_id 906 90649 90658
jjjj 2 1 3
jjjkk 0 0 2
k 0 1 3
Recomended reading

SQL Server calculate dynamic columns

There is a given table with following structure:
DateAndTime | Count Wine | Count Beer
2014-08-11 16:45:22.480 | 100 | 50
2014-08-12 16:45:22.480 | 50 | 50
2014-08-18 16:45:22.480 | 200 | 100
2014-08-19 16:45:22.480 | 300 | 200
What I need is a select statement with following Output:
--- | Week No 33 | Week No 34
Beer | 50 | 150
Wine | 75 | 250
So the columns (week no.) are dynamically depending on data.
And the calaculated values should be the average of the rows within the calendar week.
I have no idea how to solve this....
Not sure if it is an elegant way.. but this works.
create table sample
(
dtdate smalldatetime,
cWine int,
cBeer int
)
insert into sample
values('2014-08-11 16:45:22.480', 100 , 50),('2014-08-12 16:45:22.480', 50, 50),
('2014-08-18 16:45:22.480', 200 , 100),('2014-08-19 16:45:22.480', 300 , 200)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(DATEPART(WEEK,dtDate)) from sample FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)') ,1,1,'')
SELECT #query = 'SELECT Countname, '+#cols+'
FROM
(
SELECT countName,y,Avg(CountValue)avge from
(
SELECT countName,countValue,dtdate,DATEPART(WEEK,dtdate)as y from sample
unpivot(
countValue for Countname in (cwine,cBeer)
)unpiv )x group by y,countName
) x
pivot
(
sum(avge)
for y in ('+#cols+')
) p'
execute(#query)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(DATEPART(WEEK,dtdate))
from master.sample
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #query = 'SELECT Countname, '+#cols+'
FROM
(
SELECT countName,y,Avg(CountValue)avge from
(
SELECT countName,countValue,dtdate,DATEPART(WEEK,dtdate)as y from sample
unpivot(
countValue for Countname in (cwine,cBeer)
)unpiv )x group by y,countName
) x
pivot
(
sum(avge)
for y in ('+#cols+')
) p'
SELECT #query
execute(#query)