SQL Server : group by name and specify the values ​​in columns - sql

I´ve got a SQL Server request to get a table with all article features:
SELECT
tartikel.cArtNr AS ID,
tMerkmal.cName AS Feature,
tMerkmalWertSprache.cWert AS FeatureValue
FROM
tartikel
INNER JOIN
tArtikelMerkmal ON tartikel.kArtikel = tArtikelMerkmal.kArtikel
INNER JOIN
tMerkmal ON tArtikelMerkmal.kMerkmal = tMerkmal.kMerkmal
INNER JOIN
tMerkmalWertSprache ON tArtikelMerkmal.kMerkmalWert = tMerkmalWertSprache.kMerkmalWert
WHERE
(tMerkmalWertSprache.kSprache = '1')
I get a result like this:
ID | FeatureName | FeatureValue
--------------------------------
1 | Feature 1 | Value a
1 | Feature 2 | Value a
1 | Feature 2 | Value b
1 | Feature 2 | Value c
1 | Feature 3 | Value a
but I wanted to group by FeatureName and the values in separate columns.
Like this:
ID | FeatureName | FeatureValue 1 | FeatureValue 2 | FeatureValue 3
-------------------------------------------------------------------
1 | Feature 1 | Value a | |
1 | Feature 2 | Value a | Value b | Value c
1 | Feature 3 | Value a | |
How can I modify my request to get the table which is sorted by FeatureName?

You can use the PIVOT function of SQL Server to get the result:
select id, feature, FeatureValue_1, FeatureValue_2, FeatureValue_3
from
(
SELECT tartikel.cArtNr AS ID,
tMerkmal.cName AS Feature,
tMerkmalWertSprache.cWert AS FeatureValue,
'FeatureValue_'+cast(row_number() over(partition by tartikel.cArtNr, tMerkmal.cName
order by tMerkmal.cName) as varchar(10)) seq
FROM tartikel
INNER JOIN tArtikelMerkmal
ON tartikel.kArtikel = tArtikelMerkmal.kArtikel
INNER JOIN tMerkmal
ON tArtikelMerkmal.kMerkmal = tMerkmal.kMerkmal
INNER JOIN tMerkmalWertSprache
ON tArtikelMerkmal.kMerkmalWert = tMerkmalWertSprache.kMerkmalWert
WHERE tMerkmalWertSprache.kSprache = '1'
) d
pivot
(
max(FeatureValue)
for seq in (FeatureValue_1, FeatureValue_2, FeatureValue_3)
) piv;
If you had an unknown number of FeatureValues for each Id, then you could use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ QUOTENAME('FeatureValue_'+cast(row_number() over(partition by tartikel.cArtNr, tMerkmal.cName
order by tMerkmal.cName) as varchar(10)))
FROM tartikel
INNER JOIN tArtikelMerkmal
ON tartikel.kArtikel = tArtikelMerkmal.kArtikel
INNER JOIN tMerkmal
ON tArtikelMerkmal.kMerkmal = tMerkmal.kMerkmal
INNER JOIN tMerkmalWertSprache
ON tArtikelMerkmal.kMerkmalWert = tMerkmalWertSprache.kMerkmalWert
WHERE tMerkmalWertSprache.kSprache = '1'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, feature,' + #cols + '
from
(
SELECT tartikel.cArtNr AS ID,
tMerkmal.cName AS Feature,
tMerkmalWertSprache.cWert AS FeatureValue,
''FeatureValue_''+cast(row_number() over(partition by tartikel.cArtNr, tMerkmal.cName
order by tMerkmal.cName) as varchar(10)) seq
FROM tartikel
INNER JOIN tArtikelMerkmal
ON tartikel.kArtikel = tArtikelMerkmal.kArtikel
INNER JOIN tMerkmal
ON tArtikelMerkmal.kMerkmal = tMerkmal.kMerkmal
INNER JOIN tMerkmalWertSprache
ON tArtikelMerkmal.kMerkmalWert = tMerkmalWertSprache.kMerkmalWert
WHERE tMerkmalWertSprache.kSprache = ''1''
) x
pivot
(
max(FeatureValue)
for seq in (' + #cols + ')
) p '
execute(#query)

Related

updating values in one table from another table using DYNAMIC SQL in MSSQL

I have a temp table A having 2 columns col1: ID col2: Value generated using XML. I need to update the columns in table B corresponding to column1:ID of table A with values present in col2: Value of table A. NOTE: Only specific columns and not all in table B need to be updated
table A
+----+-------+
| ID | Value |
+----+-------+
| 1 | 533 |
| 5 | 34 |
| 6 | 56 |
+----+-------+
table B
+-----+---+---+---+----+----+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-----+---+---+---+----+----+---+
| 533 | | | | 34 | 56 | |
+-----+---+---+---+----+----+---+
declare dynsql varchar(4000) = ' update table B set....... '
Try following query:
UPDATE TableB
SET [1] = ISNULL(z.[1],TableB.[1]),
[2] = ISNULL(z.[2],TableB.[2]),
[3] = ISNULL(z.[3],TableB.[3]),
[4] = ISNULL(z.[4],TableB.[4]),
[5] = ISNULL(z.[5],TableB.[5]),
[6] = ISNULL(z.[6],TableB.[6]),
[7] = ISNULL(z.[7],TableB.[7])
FROM (
SELECT [1],[2],[3],[4],[5],[6],[7]
FROM (SELECT Id, Value
FROM TableA)AS p
PIVOT (MAX(Value) FOR Id IN([1],[2],[3],[4],[5],[6],[7]))AS pvt
)z
EDIT
In order to have dynamic pivot use following query:
DECLARE #columns1 NVARCHAR(1000) = '',
#columns2 NVARCHAR(1000) = '',
#sql NVARCHAR(MAX)
SELECT #Columns1 = STUFF((SELECT ',['+Value+'] = ISNULL(z.['+Value+'],TableB.['+Value+'])'
FROM (SELECT DISTINCT Value FROM TableA)z
FOR XML PATH('')),1,1,''),
#Columns2 = STUFF((SELECT ',['+Value+']'
FROM (SELECT DISTINCT Value FROM TableA)z
FOR XML PATH('')),1,1,'')
SET #sql = 'Update TableB
Set '+#columns1+'
From (
Select '+ #columns2+'
From (Select Id, Value From TableA) AS p
Pivot (MAX(Value) For Id IN ('+#columns2+')) AS Pvt
)z'
EXECUTE(#sql)
INSERT INTO tb
SELECT [1],[2],[3],[4],[5],[6],[7] FROM
(SELECT
id,value
from ta)as p
PIVOT
(AVG(value) FOR id IN([1],[2],[3],[4],[5],[6],[7])
)as bah
Fiddle

SQL Server Pivot for counting instances in join table

I have 3 tables; category, location and business.
The category and location tables simply have an id, and a name.
Each business record has a categoryID, and a locationID, and a name field.
I'd like to construct a table that shows as a matrix, the number of businesses in each location and category combination. So having the categories as columns and locations as rows, with the counts in as the cell data.
Having a totals column and row would also be amazing.
I know I should be able to do this with pivot tables but I'm unable to get my head around the syntax for the pivots.
Any help would be much appreciated.
Thanks,
Nick
Edit: Here is a JS fiddle of my tables; http://sqlfiddle.com/#!2/4d6d2/1
Desired output:
| Activities | Bars | Sweet shops | Total
Chester | 1 | 0 | 0 | 1
Frodsham | 0 | 2 | 0 | 2
Stockport | 1 | 0 | 1 | 2
Total | 2 | 2 | 1 | 5
To get the final result that you want you can use the PIVOT function. I would first start with a subquery that returns all of your data plus gives you a total of each activity per location:
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) total
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id;
See SQL Fiddle with Demo. Using the windowing function count() over() creates the total number of activities for each location. Once you have this, then you can pivot the data to convert your categories to columns:
select
isnull(location, 'Total') Location,
sum([Activities]) Activities,
sum([Bars]) bars,
sum([Sweet Shops]) SweetShops,
sum(tot) total
from
(
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) tot
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id
) d
pivot
(
count(category)
for category in ([Activities], [Bars], [Sweet Shops])
) piv
group by grouping sets(location, ());
See SQL Fiddle with Demo. I also implemented GROUPING SETS() to create the final row with the totals for each activity.
The above works great if you have a limited number of activities but if your activities will be unknown, then you will want to use dynamic SQL:
DECLARE
#cols AS NVARCHAR(MAX),
#colsgroup AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(name)
from dbo.category
group by id, name
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsgroup = STUFF((SELECT ', sum(' + QUOTENAME(name)+ ') as '+ QUOTENAME(name)
from dbo.category
group by id, name
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT
Isnull(location, ''Total'') Location, '+ #colsgroup + ', sum(Total) as Total
from
(
select l.name location,
c.name category,
count(b.locationid) over(partition by b.locationid) total
from location l
left join business b
on l.id = b.locationid
left join category c
on b.categoryid = c.id
) x
pivot
(
count(category)
for category in ('+#cols+')
) p
group by grouping sets(location, ());'
exec sp_executesql #query;
See SQL Fiddle with Demo. Both versions give the result:
| LOCATION | ACTIVITIES | BARS | SWEET SHOPS | TOTAL |
|-----------|------------|------|-------------|-------|
| Chester | 1 | 0 | 0 | 1 |
| Frodsham | 0 | 1 | 0 | 1 |
| Stockport | 1 | 0 | 1 | 2 |
| Total | 2 | 1 | 1 | 4 |
`SELECT b.businessName, count(l.locationId),count(b.categoryId)
FROM businesses b JOIN locations l ON b.locationId=l.locationId
JOIN categories c ON b.categoryId=c.categoryId GROUP BY b.businessName;`

Transforming a column in rows [duplicate]

I have the following dataset
Account Contact
1 324324324
1 674323234
2 833343432
2 433243443
3 787655455
4 754327545
4 455435435
5 543544355
5 432455553
5 432433242
5 432432432
I'd like output as follows:
Account Contact1 Contact2 Contact3 Contact4
1 324324324 674323234
2 833343432 433243443
3 787655455
4 754327545 455435435
5 543544355 432455553 432433242 432432432
The problem is also that I have an unfixed amount of Accounts & unfixed amount of Contacts
If you are going to apply the PIVOT function, you will need to use an aggregate function to get the result but you will also want to use a windowing function like row_number() to generate a unique sequence for each contact in the account.
First, you will query your data similar to:
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
See SQL Fiddle with Demo. This will create a new column with the unique sequence:
| ACCOUNT | CONTACT | SEQ |
|---------|-----------|----------|
| 1 | 324324324 | contact1 |
| 1 | 674323234 | contact2 |
If you have a limited number of columns, then you could hard-code your query:
select account,
contact1, contact2, contact3, contact4
from
(
select account, contact,
'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
pivot
(
max(contact)
for seq in (contact1, contact2, contact3, contact4)
) piv;
See SQL Fiddle with Demo
If you have an unknown number of columns, then you will have to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(seq)
from
(
select 'contact'
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT account, ' + #cols + '
from
(
select account, contact,
''contact''
+ cast(row_number() over(partition by account
order by contact) as varchar(10)) seq
from yourtable
) x
pivot
(
max(contact)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both will give you a result of:
| ACCOUNT | CONTACT1 | CONTACT2 | CONTACT3 | CONTACT4 |
|---------|-----------|-----------|-----------|-----------|
| 1 | 324324324 | 674323234 | (null) | (null) |
| 2 | 433243443 | 833343432 | (null) | (null) |
| 3 | 787655455 | (null) | (null) | (null) |
| 4 | 455435435 | 754327545 | (null) | (null) |
| 5 | 432432432 | 432433242 | 432455553 | 543544355 |
Just a slightly different way to generate the dynamic PIVOT:
DECLARE #c INT;
SELECT TOP 1 #c = COUNT(*)
FROM dbo.YourTable
GROUP BY Account
ORDER BY COUNT(*) DESC;
DECLARE #dc1 NVARCHAR(MAX) = N'', #dc2 NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX);
SELECT #dc1 += ',Contact' + RTRIM(i), #dc2 += ',[Contact' + RTRIM(i) + ']'
FROM (SELECT TOP (#c) i = number + 1
FROM master.dbo.spt_values WHERE type = N'P' ORDER BY number) AS x;
SET #sql = N'SELECT Account ' + #dc1 +
' FROM (SELECT Account, Contact, rn = ''Contact''
+ RTRIM(ROW_NUMBER() OVER (PARTITION BY Account ORDER BY Contact))
FROM dbo.YourTable) AS src PIVOT (MAX(Contact) FOR rn IN ('
+ STUFF(#dc2, 1, 1, '') + ')) AS p;';
EXEC sp_executesql #sql;
SQLiddle demo

Display a different column value in a PIVOT table

I have the following code -
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +
QUOTENAME(Name)
FROM JobPhases
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'SELECT *
FROM
(
SELECT c.Registration, p.Name, [x] = 1
FROM JobDetails AS j
INNER JOIN JobPhases p ON p.ID = j.PhaseId
INNER JOIN Jobs job on job.ID = j.JobID
INNER JOIN Cars c on job.CarID = c.ID
) JobDetails
PIVOT
( SUM(x)
FOR Name IN (' + #cols + ')
) pvt'
Which generates this -
JobID | Repair & Reshape | Refit Stripped Parts | Polishing
1000 | id | id | id
1001 | id | id | id
1002 | id | id | id
1003 | id | id | id
1004 | id | id | id
But, I would like it to show j.EstimatedTime instead of j.ID.
The table structure -
JobDetails
ID - PK Auto increment
JobID - Int (Joined to Jobs table)
PhaseID - String (joined to JobPhases table)
EstimatedTime - decimal
JobPhases
ID - PK String
Name - VarChar(150)
Any ideas how I can achieve this?
Thanks
Try changing [x] = 1 to [x] = j.EstimatedTime .

Pivoting 2 columns from 3 Tables and creating pivot-column-names to avoid conflict - SQL-Server 2008R2

Intro and Problem
In my example i have teachers, students and courses.I would like to have an overview which course is teached by whom in which rooms and all the studends in this course. I have the basic setup runnig (with some handcoded statements). But until now i had no luck to prepare the correct STUFF statement:
Prepare #colsStudents so that i can put the name in the column header and remove the need to mess with the ids (adding 100) to avoid a conflict between rooms.id and students.id
Prepare #colsRooms so that i do not have to hardocde the roomnames
Putting i all together by using EXEC sp_executesql #sql;
You can find all sql-statements to create this schema and the data at the end.
Wanted Result Overview Courses,
I would like pivot the columns RoomName and StudentName and use the column values as the new column names. All SQL-Statements to create tables and data are at the end.
Id | Course | Teacher | A3 | E7 | Penny | Cooper | Koothrap. | Amy
---+--------+---------+----+----+-------+--------+-----------+-----+
1 | C# 1 | Marc G. | | 1 | 1 | | |
2 | C# 2 | Sam S. | | 1 | 1 | | 1 |
3 | C# 3 | John S. | 1 | | | 1 | |
4 | C# 3 | Reed C. | | 1 | | | 1 |
5 | SQL 1 | Marc G. | 1 | | | | |
6 | SQL 2 | Marc G. | 1 | | | | |
7 | SQL 3 | Marc G. | | 1 | | 1 | | 1
8 | SQL 3 | Gbn | 1 | | | | 1 |
What i have so far
With PivotData as (
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,r.Id as RoomId, r.RoomName as RoomName
,100 + s.Id as StudentId, s.StudentName as Student
FROM CourseDetails cd
Left JOIN Courses c ON cd.CourseId = c.Id
Left JOIN Teachers t ON cd.TeacherId = t.Id
Left JOIN CourseMember cm ON cd.Id = cm.CourseDetailsId
Left JOIN Students s ON cm.StudentId = s.Id
Left JOIN Rooms r ON cd.RoomId = r.Id
)
Select Course, Teacher
, [1] as A3, [2] as E7 -- RoomColumns
, [101] as Koothrappali, [102] as Cooper, [103] as Penny, [104] as Amy -- StudentColumns
FROM (
Select Course, Teacher, RoomName, RoomId,Student, StudentId
From PivotData) src
PIVOT( Max(RoomName) FOR RoomId IN ([1],[2])) as P1
PIVOT( Count(Student) FOR StudentId IN ([101],[102],[103],[104]) ) as P2
What is missing
The above statement is prepared by hand. Since i do not know the Rooms or Students in advance i need to create the Pivot Statement for the Columns Rooms and Students dynamically. On SO are plenty of examples how to do it. The normal way to do that is to use STUFF:
DECLARE #colsStudents AS NVARCHAR(MAX);
SET #colsStudents = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()] FROM
(SELECT DISTINCT 100 + Id AS y FROM dbo.Students) AS Y
ORDER BY y
FOR XML PATH('')
),1
,1
,N'');
Select #colsStudents
This returns [101],[102],[103],[104] for the Student Ids. I added 100 to each id to avoid conflicts between the students.id and teh rooms.id column.
As mentioned in the intro i need to dynamically create something like this
[1] as RoomName_1, [2] as RoomName_1 -- RoomColumns
[1] as StudentName1, [2] as StudentName2, ... ,[4] as Amy -- StudentColumns
But all my tries with the stuff statement failed.
All SQL Statements to create the tables and data
CREATE TABLE [dbo].[Teachers](
[Id] [int] IDENTITY(1,1) NOT NULL,
[TeacherName] [nvarchar](120) NULL,
CONSTRAINT PK_Teachers PRIMARY KEY CLUSTERED (Id))
CREATE TABLE [dbo].[Students](
[Id] [int] IDENTITY(1,1) NOT NULL,
[StudentName] [nvarchar](120) NULL,
CONSTRAINT PK_Students PRIMARY KEY CLUSTERED (Id))
CREATE TABLE [dbo].[Courses](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CourseName] [nvarchar](120) NULL,
CONSTRAINT PK_Courses PRIMARY KEY CLUSTERED (Id))
CREATE TABLE [dbo].[Rooms](
[Id] [int] IDENTITY(1,1) NOT NULL,
[RoomName] [nchar](120) NULL,
CONSTRAINT PK_Rooms PRIMARY KEY CLUSTERED (Id))
CREATE TABLE [dbo].[CourseDetails](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CourseId] [int] NOT NULL,
[TeacherId] [int] NOT NULL,
[RoomId] [int] NOT NULL,
CONSTRAINT PK_CourseDetails PRIMARY KEY CLUSTERED (Id),
CONSTRAINT FK_CourseDetails_Teachers_Id FOREIGN Key (TeacherId)
REFERENCES dbo.Teachers (Id),
CONSTRAINT FK_CourseDetails_Courses_Id FOREIGN Key (CourseId)
REFERENCES dbo.Courses (Id),
CONSTRAINT FK_CourseDetails_Rooms_Id FOREIGN Key (RoomId)
REFERENCES dbo.Rooms (Id)
)
CREATE TABLE [dbo].[CourseMember](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CourseDetailsId] [int] NOT NULL,
[StudentId] [int] NOT NULL,
CONSTRAINT PK_CourseMember PRIMARY KEY CLUSTERED (Id),
CONSTRAINT FK_CourseMember_CourseDetails_Id FOREIGN Key (CourseDetailsId)
REFERENCES dbo.CourseDetails (Id),
CONSTRAINT FK_CourseMember_Students_Id FOREIGN Key (StudentId)
REFERENCES dbo.Students (Id)
)
INSERT INTO dbo.Courses (CourseName)
VALUES ('SQL 1 - Basics'),
('SQL 2 - Intermediate'),
('SQL 3 - Advanced'),
('C# 1 - Basics'),
('C# 2 - Intermediate'),
('C# 3 - Advanced')
INSERT INTO dbo.Students (StudentName)
VALUES
('Koothrappali'),
('Cooper'),
('Penny'),
('Amy')
INSERT INTO dbo.Teachers (TeacherName)
VALUES
('gbn '),
('Sam S.'),
('Marc G.'),
('Reed C.'),
('John S.')
INSERT INTO dbo.Rooms (RoomName)
VALUES ('A3'), ('E7')
INSERT [dbo].[CourseDetails] (CourseId, TeacherId, RoomId)
VALUES (4, 3, 2),(5, 2, 2),
(6, 5, 1),(6, 4, 2),
(1,3,1),(2,3,1),(3,3,2),
(3,1,1)
INSERT [dbo].[CourseMember] (CourseDetailsId, StudentId)
VALUES (1,3),(2,3),(2,1),(3,2),(4,1),(7,2),(7,4),(8,1)
I personally would do this a bit different. Since you are trying to pivot two separate columns that screams to use the UNPIVOT function.
The unpivot will convert your multiple columns into rows to then pivot.
Since you have SQL Server 2008, you can use CROSS APPLY and values:
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
See Demo. The unpivot generates a result similar to this:
| ID | COURSE | TEACHER | COL | FLAG |
-------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | E7 | 1 |
| 1 | C# 1 - Basics | Marc G. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Koothrappali | 1 |
| 3 | C# 3 - Advanced | John S. | A3 | 1 |
| 3 | C# 3 - Advanced | John S. | Cooper | 1 |
You will see that the col data contains all the values that you want to pivot. Once the data is in the rows, if will be easy to apply one pivot:
select id, course, teacher,
coalesce(A3, '') A3,
coalesce(E7, '') E7,
coalesce(Koothrappali, '') Koothrappali,
coalesce(Cooper, '') Cooper,
coalesce(Penny, '') Penny,
coalesce(Amy, '') Amy
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (A3, E7, Koothrappali, Cooper, Penny, Amy)
) piv
See SQL Fiddle with Demo.
Then to convert this to dynamic SQL, you are only pivoting one column, so you will use the following to get the list of columns:
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
This will get the list of distinct rooms and students that are then used in the pivot. So the final code will be:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT ', coalesce(' + QUOTENAME(col)+', '''') as '+QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT
id, course, teacher,' + #colsNull + '
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, ''1'' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values (''roomname'', roomname),(''student'',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Note I implemented a flag to be used in the pivot, this basically generates a Y/N if there is a value for the room or student.
This gives a final result:
| ID | COURSE | TEACHER | A3 | E7 | KOOTHRAPPALI | COOPER | PENNY | AMY |
---------------------------------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | | 1 | | | 1 | |
| 2 | C# 2 - Intermediate | Sam S. | | 1 | 1 | | 1 | |
| 3 | C# 3 - Advanced | John S. | 1 | | | 1 | | |
| 4 | C# 3 - Advanced | Reed C. | | 1 | 1 | | | |
| 5 | SQL 1 - Basics | Marc G. | 1 | | | | | |
| 6 | SQL 2 - Intermediate | Marc G. | 1 | | | | | |
| 7 | SQL 3 - Advanced | Marc G. | | 1 | | 1 | | 1 |
| 8 | SQL 3 - Advanced | gbn | 1 | | 1 | | | |
As a side note, this data can also be unpivoted using the unpivot function in sql server. (See Demo with unpivot)
You can create alias string for both pivot columns using dynamic sql query,
For example, for student columns :
DECLARE #colsStudents AS NVARCHAR(MAX),
#colsstudentalias AS NVARCHAR(MAX),
#colsRooms AS NVARCHAR(MAX),
#colsRoomsalias AS NVARCHAR(MAX)
SELECT #colsStudents = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT #colsstudentalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(StudentName)))
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT #colsRooms = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
SELECT #colsRoomsalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(RoomName)))
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
--SELECT #colsStudents, #colsstudentalias, #colsRooms, #colsRoomsalias
DECLARE #sql varchar(max)
set #sql = ';With PivotData as (
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,r.Id as RoomId, r.RoomName as RoomName
,100 + s.Id as StudentId, s.StudentName as Student
FROM CourseDetails cd
Left JOIN Courses c ON cd.CourseId = c.Id
Left JOIN Teachers t ON cd.TeacherId = t.Id
Left JOIN CourseMember cm ON cd.Id = cm.CourseDetailsId
Left JOIN Students s ON cm.StudentId = s.Id
Left JOIN Rooms r ON cd.RoomId = r.Id
)
Select Course, Teacher
, ' + #colsRoomsalias + '
, ' + #colsstudentalias + '
FROM (
Select Course, Teacher, RoomName, RoomId,Student, StudentId
From PivotData) src
PIVOT( Max(RoomName) FOR RoomId IN (' + #colsRooms + ')) as P1
PIVOT( Count(Student) FOR StudentId IN (' + #colsStudents + ') ) as P2'
exec (#sql)
SQL DEMO
I am going to take a deeper look at both answers above and compare them with the one below.
My problem was in filling the local variables #RoomNames and #StudentNames with the Stuff() Function. One reason was that i had choosen the datatype nchar(120) instead of
nvarchar(120) for the columns StudentName, RoomName.
Another problem i had was that the new columnNames (Student instead of StudentName) where not recognized; therefore i replaced them with * in this statement: Select * From (' + #PivotSrc + N') src
Philip Kelley suggested to use SELECT #RoomIds = isnull(#RoomIds + ',', '') + '[' + Cast(Id as nvarchar(20))+ ']' FROM Rooms instead of STUFF() and since i find it shorter and easier to read i am using it now.
Working Solution
DECLARE #StudentNames NVARCHAR(2000),
#RoomIds NVARCHAR(2000),
#RoomNames NVARCHAR(2000),
#PivotSrc NVARCHAR(MAX),
#PivotBase NVARCHAR(MAX);
SELECT #StudentNames = isnull(#StudentNames + ',', '') + '[' + StudentName + ']' FROM Students
SELECT #RoomIds = isnull(#RoomIds + ',', '') + '[' + Cast(Id as nvarchar(20))+ ']' FROM Rooms
SELECT #RoomNames = isnull(#RoomNames + ',', '') + '[' + RoomName + ']' FROM Rooms
SET #PivotSrc = N'Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,r.Id as RoomId, r.RoomName as RoomName
,100 + s.Id as StudentId, s.StudentName as Student
FROM CourseDetails cd
Left JOIN Courses c ON cd.CourseId = c.Id
Left JOIN Teachers t ON cd.TeacherId = t.Id
Left JOIN CourseMember cm ON cd.Id = cm.CourseDetailsId
Left JOIN Students s ON cm.StudentId = s.Id
Left JOIN Rooms r ON cd.RoomId = r.Id'
SET #PivotBase = N' Select Course, Teacher, '
+ #RoomNames + N', '
+ #StudentNames + N' FROM (
Select * From (' + #PivotSrc + N') src
PIVOT( Max(RoomName) FOR RoomName IN ('+#RoomNames+ N')) as P1
PIVOT( Count(Student) FOR Student IN ('+#StudentNames+N') ) as P2) as T'
execute(#PivotBase)