SQL Server 2012 Dynamic Pivot with a Join [duplicate] - sql

I have a table of Customers
Customer ID Name
1 John
2 Lewis
3 Mary
I have another table CustomerRewards
TypeID Description
1 Bronze
2 Silver
3 Gold
4 Platinum
5 AnotherOne
And the final table
RewardID TypeID CustomerID
1 1 1
2 1 1
3 2 1
4 2 2
The customerTypes table is dynamic, many of these types can be added and removed. Basically all I want is the columns to be generated dynamically and a count in each, something like
CustomerName Bronze Silver Gold Platinum AnotherOne total
John 2 1 0 0 0 3
Lewis 0 1 0 0 0 1
Grand TOTAL 2 2 0 0 0 4
The problem like I said it that the types are dynamic and the customers are dynamic so I need the columns to be dynamic depending on the types in the system
I have tagged c# as I need this in a DataGridView
Thanks in advance

You will want to use a PIVOT function for this. If you have a known number of columns, then you can hard-code the values:
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p;
See SQL Fiddle with Demo.
Now if you have an unknown number of columns, then you can use dynamic SQL to PIVOT:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle With Demo
If you need to include the Total column, then you can use ROLLUP (Static Version Demo):
select name, sum([Bronze]) Bronze, sum([Silver]) Silver,
sum([Gold]) Gold, sum([Platinum]) Platinum, sum([AnotherOne]) AnotherOne
from
(
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p
) x
group by name with rollup
Dynamic version (Demo):
DECLARE #cols AS NVARCHAR(MAX),
#colsRollup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsRollup
= STUFF((SELECT ', Sum(' + QUOTENAME(description) + ') as '+ QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT name, '+ #colsRollup + '
FROM
(
SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p
) x1
GROUP BY name with ROLLUP'
execute(#query)

Related

SQL Server query result by column to row

This is my sample code:
SQL Fiddle
I need it to result like this:
category outlet1 outlet2 outlet3
Sale 70 20 40
Expense 250 130 200
How can I do this?
EDIT: My outlets are not fixed, sorry for not telling this beforehand.
You can solve your particular problem using conditional aggregation:
SELECT c.category,
SUM(CASE WHEN o.outletname = 'Outlet1' THEN t.amt ELSE 0 END) as Outlet1,
SUM(CASE WHEN o.outletname = 'Outlet2' THEN t.amt ELSE 0 END) as Outlet2,
SUM(CASE WHEN o.outletname = 'Outlet3' THEN t.amt ELSE 0 END) as Outlet3
FROM tblcategory c INNER JOIN
tbltran t
ON t.catid = c.id INNER JOIN
tbloutlet o
ON o.id = t.outletid
GROUP BY c.category;
If the outlet names are not fixed, then you need dynamic SQL. The problem cannot be solve using a single SELECT query.
Here with dynamic Outlets http://sqlfiddle.com/#!18/a7b09/25
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(outletname)
from tbloutlet
group by outletname
order by outletname
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT category,' + #cols + ' from
(
SELECT c.category, o.outletname, SUM(t.amt) as amt
FROM tblcategory c
INNER JOIN tbltran t ON t.catid = c.id
INNER JOIN tbloutlet o ON o.id = t.outletid
GROUP BY c.category, o.outletname
) x
pivot
(
sum(amt)
for outletname in (' + #cols + ')
) p '
execute(#query);
you may also use the PIVOT operator
SELECT *
FROM
(
SELECT category, outletname, amt
FROM tblcategory c
INNER JOIN tbltran t ON t.catid = c.id
INNER JOIN tbloutlet o ON o.id = t.outletid
) d
PIVOT
(
SUM(amt)
FOR outletname in ([Outlet1] , [Outlet2] , [Outlet3])
) p
EDIT : below is the Dynamic SQL version
declare #Outlets nvarchar(max),
#SQL nvarchar(max)
select #Outlets = isnull(#Outlets + ',', '') + quotename(outlet)
from outlet
select #SQL = '
SELECT *
FROM
(
SELECT category, outletname, amt
FROM tblcategory c
INNER JOIN tbltran t ON t.catid = c.id
INNER JOIN tbloutlet o ON o.id = t.outletid
) d
PIVOT
(
SUM(amt)
FOR outletname in (' + #Outlets + ')
) p'
print #SQL -- print out for verification
exec sp_executesql #SQL

Error in Dynamic PIVOT Table in SQL

I am getting error in Dynamic PIVOT Table.
I have a Tables like
TT_Child
TTChild_ID TT_ID Roll_No Std_Reg_ID Attendance
1 3 1 22 1
2 3 2 23 0
and Table
TT_Master
TT_ID Attend_date Time_from Time_To Course_ID Faculty_ID Acad_Year Subject_ID
1 2014-03-01 10:00 11:00 1 16 2013-2014 34
2 2014-03-02 10:00 11:00 1 16 2013-2014 34
3 2014-03-03 10:00 11:00 1 16 2013-2014 34
Student_Registration_Master
Std_Reg_ID Stud_FNAME stud_MNAME Stud_MNAME
--I am using this PIVOT query but getting this error. I have to use Attend_Date and Time as a Column Name.
Create PROCEDURE [dbo].[Attendance_Test]
#courseid as int=null, #acadyear nvarchar(15)=null
AS
Declare #colList varchar(max)
Declare #qry varchar(max)
SET #colList = STUFF((SELECT distinct ',' + QUOTENAME(CONVERT(VARCHAR(19), TTM.Attend_Date, 103)) + ' '+ QUOTENAME(TTM.Time_From)
FROM TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
where (TTM.Course_ID = #courseid)
FOR XML PATH(''), TYPE).value('/', 'NVARCHAR(MAX)') ,1,1,'')
SET #qry = 'SELECT SA.Reg_ID, STUD_FNAME + STUD_MNAME + STUD_LNAME as [Student Name], '+#colList+'
FROM
(
select SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date , SA.Attendance from TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
inner join STUDENT_Registration_MASTER SR on CR.Reg_ID = SR.STUD_Reg_ID
where (TTM.Course_ID = '+cast(#courseid as varchar(50))+ ') and (TTM.Acad_Year = '''+#acadyear+''')
group by SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date, SA.Attendance
) as s
PIVOT
(
MAX(Attendance) FOR Attend_Date IN (' + #colList + ')
) pvt'
print(#qry)
Exec(#qry)
-- I am getting this error message
SELECT SA.Reg_ID, STUD_FNAME + STUD_MNAME + STUD_LNAME as [Student Name], [01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],[05/03/2014] [11:00:00.0000000]
FROM
(
select SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date , SA.Attendance from TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
inner join STUDENT_Registration_MASTER SR on CR.Reg_ID = SR.STUD_Reg_ID
where (TTM.Course_ID = 1) and (TTM.Acad_Year = '2013-2014')
group by SA.Reg_ID, SR.STUD_FNAME, SR.STUD_MNAME, SR.STUD_LNAME, TTM.Attend_Date, SA.Attendance
) as s
PIVOT
(
MAX(Attendance) FOR Attend_Date IN ([01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],[05/03/2014] [11:00:00.0000000])
) pvt
Error Message
Msg 102, Level 15, State 1, Line 12
Incorrect syntax near '10:00:00.0000000'.
Plz give solution
When you review this line of your code you will see the error:
MAX(Attendance)
FOR Attend_Date IN ([01/03/2014] [10:00:00.0000000],[02/03/2014] [10:00:00.0000000],
[03/03/2014] [10:00:00.0000000],[05/03/2014] [10:00:00.0000000],
[05/03/2014] [11:00:00.0000000])
The date and the time have each been wrapped in square brackets separately so you will get an error.
You need to concatenate the date and time together first, then wrap in square brackets using QUOTENAME. You should be able to change the code to:
SET #colList
= STUFF((SELECT distinct ',' + QUOTENAME(CONVERT(VARCHAR(19), TTM.Attend_Date, 103) + ' '+ TTM.Time_From)
FROM TT_Child SA
inner join TT_Master TTM on SA.TT_ID = TTM.TT_ID
where (TTM.Course_ID = #courseid)
FOR XML PATH(''), TYPE).value('/', 'NVARCHAR(MAX)') ,1,1,'')
See SQL Fiddle with Demo of your version and this new version.

Join tables with rows

I am trying to join three tables in SQL Server 2008 R2, where I want the items in the second table to be added as new column.
To explain in detail - I have 3 tables:
First table contains User Name and User ID
UserID UserName
1 Mike
2 John
3 George
Second Table is position ID's with Position Names
PositionID PositionName
1 RW
2 LW
3 DF
4 MDF
5 SS
6 CF
etc
Third table table contains their preferred positions where one user can have more than one
UserID PositionId
1 1
1 3
2 2
2 3
2 5
3 2
3 7
When I join these tables I want to get single row for every user with all the preferred positions like
UserID UserName PreferedPosition PreferedPosition2 PreferedPosition3
1 Mike RW LW
2 John CMF SS CF
3 George LW MDF
I don't know how to achieve this, any help would be appreciated.
If you have only a few numbers of positions, you can do it with PIVOT keyword
select
UserID,
UserName,
[1] as Position1,
[2] as Position2,
[3] as Position3
from
(
select
U.UserID, U.UserName, P.PositionName,
row_number() over (partition by U.UserID order by P.PositionName) as RowNum
from Positions_Users as PU
inner join Positions as P on P.PositionID = PU.PositionID
inner join Users as U on U.UserID = PU.UserID
) as P
pivot
(
min(P.PositionName)
for P.RowNum in ([1], [2], [3])
) as PIV
SQL FIDDLE
If, however, you want to have a dynamic number of columns, you have to use dynamic SQL, like this
declare #stmt nvarchar(max), #stmt_columns1 nvarchar(max), #stmt_columns2 nvarchar(max)
declare #Temp_Data table (RowNum nvarchar(max))
insert into #Temp_Data
select distinct row_number() over (partition by U.UserID order by P.PositionName) as RowNum
from Positions_Users as PU
inner join Positions as P on P.PositionID = PU.PositionID
inner join Users as U on U.UserID = PU.UserID
select #stmt_columns1 = stuff((select ', [' + RowNum + ']' from #Temp_Data for xml path(''), type).value('.', 'nvarchar(max)'), 1, 2, '')
select #stmt_columns2 = stuff((select ', [' + RowNum + '] as Position' + RowNum from #Temp_Data for xml path(''), type).value('.', 'nvarchar(max)'), 1, 2, '')
select #stmt = '
select
UserID,
UserName,' + #stmt_columns2 + '
from
(
select
U.UserID, U.UserName, P.PositionName,
row_number() over (partition by U.UserID order by P.PositionName) as RowNum
from Positions_Users as PU
inner join Positions as P on P.PositionID = PU.PositionID
inner join Users as U on U.UserID = PU.UserID
) as P
pivot
(
min(P.PositionName)
for P.RowNum in (' + #stmt_columns1 + ')
) as PIV'
exec sp_executesql #stmt = #stmt
SQL FIDDLE

T:SQL: select values from rows as columns

I have a table for Profiles stores profile properties values in row style, ex:
[ProfileID] [PropertyDefinitionID] [PropertyValue]
1 6 Jone
1 7 Smith
1 8 Mr
1 3 50000
and another table for property definitions :
[PropertyDefinitionID] [PropertyName]
6 FirstName
7 LastName
8 Prefix
3 Salary
How to use PIVOT or any other way to show it in this way:
[ProfileID] [FirstName] [LastName] [Salary]
1 Jone Smith 5000
It's easy to do this without PIVOT keyword, just by grouping
select
P.ProfileID,
min(case when PD.PropertyName = 'FirstName' then P.PropertyValue else null end) as FirstName,
min(case when PD.PropertyName = 'LastName' then P.PropertyValue else null end) as LastName,
min(case when PD.PropertyName = 'Salary' then P.PropertyValue else null end) as Salary
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
group by P.ProfileID
you can also do this with PIVOT keyword
select
*
from
(
select P.ProfileID, P.PropertyValue, PD.PropertyName
from Profiles as P
left outer join PropertyDefinitions as PD on PD.PropertyDefinitionID = P.PropertyDefinitionID
) as P
pivot
(
min(P.PropertyValue)
for P.PropertyName in ([FirstName], [LastName], [Salary])
) as PIV
UPDATE: For dynamic number of properties - take a look at Increment value in SQL SELECT statement
It looks like you might have an unknown number of PropertyName's that you need to turn into columns. If that is the case, then you can use dynamic sql to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(PropertyName)
from propertydefinitions
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT profileid, ' + #cols + ' from
(
select p.profileid,
p.propertyvalue,
d.propertyname
from profiles p
left join propertydefinitions d
on p.PropertyDefinitionID = d.PropertyDefinitionID
) x
pivot
(
max(propertyvalue)
for propertyname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.

Dynamically create columns sql

I have a table of Customers
Customer ID Name
1 John
2 Lewis
3 Mary
I have another table CustomerRewards
TypeID Description
1 Bronze
2 Silver
3 Gold
4 Platinum
5 AnotherOne
And the final table
RewardID TypeID CustomerID
1 1 1
2 1 1
3 2 1
4 2 2
The customerTypes table is dynamic, many of these types can be added and removed. Basically all I want is the columns to be generated dynamically and a count in each, something like
CustomerName Bronze Silver Gold Platinum AnotherOne total
John 2 1 0 0 0 3
Lewis 0 1 0 0 0 1
Grand TOTAL 2 2 0 0 0 4
The problem like I said it that the types are dynamic and the customers are dynamic so I need the columns to be dynamic depending on the types in the system
I have tagged c# as I need this in a DataGridView
Thanks in advance
You will want to use a PIVOT function for this. If you have a known number of columns, then you can hard-code the values:
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p;
See SQL Fiddle with Demo.
Now if you have an unknown number of columns, then you can use dynamic SQL to PIVOT:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle With Demo
If you need to include the Total column, then you can use ROLLUP (Static Version Demo):
select name, sum([Bronze]) Bronze, sum([Silver]) Silver,
sum([Gold]) Gold, sum([Platinum]) Platinum, sum([AnotherOne]) AnotherOne
from
(
select name, [Bronze], [Silver], [Gold], [Platinum], [AnotherOne]
from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in ([Bronze], [Silver], [Gold], [Platinum], [AnotherOne])
) p
) x
group by name with rollup
Dynamic version (Demo):
DECLARE #cols AS NVARCHAR(MAX),
#colsRollup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsRollup
= STUFF((SELECT ', Sum(' + QUOTENAME(description) + ') as '+ QUOTENAME(description)
from customerrewards
group by description, typeid
order by typeid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT name, '+ #colsRollup + '
FROM
(
SELECT name,' + #cols + ' from
(
select c.name,
cr.description,
r.typeid
from customers c
left join rewards r
on c.id = r.customerid
left join customerrewards cr
on r.typeid = cr.typeid
) x
pivot
(
count(typeid)
for description in (' + #cols + ')
) p
) x1
GROUP BY name with ROLLUP'
execute(#query)