Sql Server 2008 Row to Column - sql-server

I have two tables, tempUsers and tempItems. These two tables have a one to many relationship.
When I use an inner join on these two tables the result looks like this:
**user | Category**
Jack | Shoes
Jack | Tie
Jack | Glass
Peggy | Shoe
Peggy | Skirt
Peggy | Bat
Peggy | Cat
Bruce | Laptop
Bruce | Beer
Chuck | Cell Phone
I would instead like a result that looks like this:
**User | Category1 | Category2 | Category3 | Category4**
Jack | Shoes | Tie | Glass | .....
Peggy | Shoe | Skirt | Bat | Cat
Bruce | Laptop | Beer |..... |......
Chuck | Cell Phone | ..... |....... |
The number of distinct categories in the category is dynamic - there can be any number of them for a given item.
How can I produce this result?

There are a few ways that you can transform the data from rows into columns.
Since you are using SQL Server 2008, then you can use the PIVOT function.
I would suggest using the row_number() function to assist in pivoting the data. If you have a known number of values, then you could hard-code the query:
select user, category1, category2, category3, category4
from
(
select [user], category,
'Category'+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)) rn
from yt
) d
pivot
(
max(category)
for rn in (category1, category2, category3, category4)
) piv;
See SQL Fiddle with Demo.
For your situation you stated that you will have an unknown number of values that need to be columns. In that case, you will want to use dynamic SQL to generate the query string to execute:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)))
from yt
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [user],' + #cols + '
from
(
select [user], category,
''Category''+cast(row_number() over(partition by [user]
order by [user]) as varchar(3)) rn
from yt
) d
pivot
(
max(category)
for rn in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give a result:
| USER | CATEGORY1 | CATEGORY2 | CATEGORY3 | CATEGORY4 |
----------------------------------------------------------
| Bruce | Laptop | Beer | (null) | (null) |
| Chuck | Cell Phone | (null) | (null) | (null) |
| Jack | Shoes | Tie | Glass | (null) |
| Peggy | Shoe | Skirt | Bat | Cat |

Sql Server does allow you to pivot data. However, like other relational database, it still requires that you know at the outset of a query how many columns (and of what type) the results will be, even with the PIVOT. The best you can hope for here is to use queries, combined with dynamic sql (building the query string in code at runtime), to first find out who has the most categories, and then build a query that PIVOTs your data to look for that many items.
The normal solution to pivoting with an unknown number of columns is do the pivot client side, from the code that calls into the server.

Here is the solution using multiple tables. This solution is entirely based on bluefeet's solution. I have just added user id.
create table #tmpUsers
(user_id int, user_name varchar(255));
insert into #tmpUsers values (1,'Jack');
insert into #tmpUsers values (2,'Peggy');
insert into #tmpUsers values (3,'Bruce');
insert into #tmpUsers values (4,'Chuck');
create table #tmpItems
(user_id int, category varchar(255));
insert into #tmpItems values(1,'Shoes');
insert into #tmpItems values(1,'Tie');
insert into #tmpItems values(1,'Glass');
insert into #tmpItems values(2,'Shoe');
insert into #tmpItems values(2,'Skirt');
insert into #tmpItems values(2,'Bat');
insert into #tmpItems values(2,'Cat');
insert into #tmpItems values(3,'Laptop');
insert into #tmpItems values(3,'Beer');
insert into #tmpItems values(4,'Cell Phone');
select TU.user_name,TI.category from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Category'+cast(row_number() over(partition by TU.[user_id]
order by TU.[user_id]) as varchar(3)))
from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [user_name],' + #cols + '
from
(
select TU.[user_name], TI.category,
''Category''+cast(row_number() over(partition by TU.[user_id]
order by TU.[user_id] ) as varchar(3)) rn
from #tmpUsers TU inner join #tmpItems TI on TU.user_id=TI.user_id
) d
pivot
(
max(category)
for rn in (' + #cols + ')
) p '
execute(#query)
drop table #tmpUsers
drop table #tmpItems

Related

MS SQL - Concatenating results in one joined table/column where the rest are duplicate values

EDIT: Adding Code reduction to make query clearer:
Select
AMS.arecno As [RecNo],
Convert(DATE,AMS.adate) As [Date],
ac.acommentno as [commentno],
ac.acomment as [comments]
From
amain As AMS
left Join
asfat As ASF On AMS.arecno = ASF.afatrecno
left join
acomments as ac on ac.areportno = asf.afatrecno
Order By
AMS.arecno Desc
My first table has this type of info:
recno | date
1234 | 2017
6548 | 2018
I am then left joining on a table called comments.
Per record number (recno) there are multiple comments.
like this:
recno | commentno | comments
1234 | 1 | blah blah...
1234 | 2 | doot doot...
6548 | 1 | jib jab...
6548 | 2 | flib flob...
I'd like to show this:
recno | date | comments |
1234 | 2017 | Comment 1: blah blah... Comment 2: doot doot...
6548 | 2018 | Comment 1:jib jab... Comment 2: flib flob...
I've looked up and tried a few solutions but am really struggling. Any help would be much appreciated.
Note: I can't create any tables or stored procedures due to limitations on our ODBC setup and have a very limited sql knowledge compared to most.
you can try the following query statement
CREATE TABLE #RecordInfo(RecNo INT,RecDate VARCHAR(10))
INSERT INTO #RecordInfo
SELECT 1234,'2017' UNION ALL SELECT 6548,'2018'
CREATE TABLE #CommentsInfo(RecNo INT,CommentNo INT,Comments VARCHAR(MAX))
INSERT INTO #CommentsInfo
SELECT 1234, 1, 'blah blah' UNION ALL SElECT 1234, 2, 'doot doot'
UNION ALL SELECT 6548, 1, 'jib jab' UNION ALL SELECT 6548 ,2,'flib flob'
;With CTE AS
(
SELECT R.RecNo,R.RecDate,CONCAT('Comment ',C.CommentNo,': ', C.Comments) AS Comments
FROM #RecordInfo R
LEFT JOIN #CommentsInfo C ON R.RecNo = C.RecNo
)
SELECT
t1.RecNo, t1.RecDate,Comments=
STUFF((
SELECT ',' + t2.Comments
FROM CTE t2
WHERE t1.RecNo = t2.RecNo
FOR XML PATH(''), TYPE).value('.','varchar(1000)'),1,1,'')
FROM CTE as t1
GROUP BY t1.RecNo,t1.RecDate;
DROP TABLE #RecordInfo
DROP TABLE #CommentsInfo
I think your problem will be solved by this. Thanks.

Reverse order of a XML Column in SQL Server

In a SQL Server table, I have a XML column where status are happened (first is oldest, last current status).
I have to write a stored procedure that returns the statuses: newest first, oldest last.
This is what I wrote:
ALTER PROCEDURE [dbo].[GetDeliveryStatus]
#invoiceID nvarchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #xml xml
SET #xml = (SELECT statusXML
FROM Purchase
WHERE invoiceID = #invoiceID )
SELECT
t.n.value('text()[1]', 'nvarchar(50)') as DeliveryStatus
FROM
#xml.nodes('/statuses/status') as t(n)
ORDER BY
DeliveryStatus DESC
END
Example of value in the statusXML column:
<statuses>
<status>A</status>
<status>B</status>
<status>A</status>
<status>B</status>
<status>C</status>
</statuses>
I want the procedure to return:
C
B
A
B
A
with ORDER BY .... DESC it return ALPHABETIC reversed (C B B A A)
How should I correct my procedure ?
Create a sequence for the nodes based on the existing order then reverse it.
WITH [x] AS (
SELECT
t.n.value('text()[1]', 'nvarchar(50)') as DeliveryStatus
,ROW_NUMBER() OVER (ORDER BY t.n.value('..', 'NVARCHAR(100)')) AS [Order]
FROM
#xml.nodes('/statuses/status') as t(n)
)
SELECT
DeliveryStatus
FROM [x]
ORDER BY [x].[Order] DESC
... results ...
DeliveryStatus
C
B
A
B
A
There is no need to declare a variable first. You can (and you should!) read the needed values from your table column directly. Best was an inline table valued function (rather than a SP just to read something...)
Better performance
inlineable
You can query many InvoiceIDs at once
set-based
Try this (I drop the mock-table at the end - carefull with real data!):
CREATE TABLE Purchase(ID INT IDENTITY,statusXML XML, InvocieID INT, OtherValues VARCHAR(100));
INSERT INTO Purchase VALUES('<statuses>
<status>A</status>
<status>B</status>
<status>A</status>
<status>B</status>
<status>C</status>
</statuses>',100,'Other values of your row');
GO
WITH NumberedStatus AS
(
SELECT ID
,InvocieID
, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr
,stat.value('.','nvarchar(max)') AS [Status]
,OtherValues
FROM Purchase
CROSS APPLY statusXML.nodes('/statuses/status') AS A(stat)
WHERE InvocieID=100
)
SELECT *
FROM NumberedStatus
ORDER BY Nr DESC
GO
--Clean-Up
--DROP TABLE Purchase;
The result
+---+-----+---+---+--------------------------+
| 1 | 100 | 5 | C | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 4 | B | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 3 | A | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 2 | B | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 1 | A | Other values of your row |
+---+-----+---+---+--------------------------+

How to display data horizontally in SQL Server?

How do I display my table data horizontally?
This is my table definition
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(10)
)
This is the data I have in my SQL Server table
+====+=======+========+
| Id | Name | Gender |
+====+=======+========+
| 1 | Fahad | Male |
+----+-------+--------+
| 2 | Saad | Male |
+----+-------+--------+
| 3 | Asif | Male |
+====+=======+========+
and I want to show it horizontally like this
+========+=======+======+======+
| Id | 1 | 2 | 3 |
+========+=======+======+======+
| Name | Fahad | Saad | Asif |
+--------+-------+------+------+
| Gender | Male | Male | Male |
+========+=======+======+======+
Perhaps a combination of UNPIVOT and PIVOT?
(Although your columns need to be of the same type for this to work, which I've changed in your table, or you can just CAST in a SELECT/CTE etc)
CREATE table [User](
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50)
)
SET IDENTITY_INSERT [User] ON
INSERT INTO [User](Id,Name,Gender) VALUES
(1, 'Fahad','Male'),
(2,'Saad','Male'),
(3,'Asif','Male')
SELECT * FROM [User]
UNPIVOT ([Value] FOR Cols IN ([Name],[Gender])) Unp
PIVOT (MAX([Value]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
(2 row(s) affected)
CASE can also be used to achieve the same - there are tons of examples on SO.
Edit: Excellent example Simple way to transpose columns and rows in Sql?
(and this is probably a dup of that question)
Yes, it seems we might need to do combination of UNPIVOT and PIVOT.
Try below, It may provide you the exact result as what you expect. Please change your design first
Gender varchar(10) to Gender varchar(50)
Try below,
;WITH cte AS(
SELECT *
FROM [User]
UNPIVOT([Value] FOR Cols IN ([Name], [Gender])) Unp
PIVOT(MAX([Value]) FOR Id IN ([1], [2], [3])) Piv
)
SELECT Cols AS Id,
[1],
[2],
[3]
FROM cte
ORDER BY
Id DESC
Here is a stored procedure that works on any given table. It presumes that the table key is in the first column.
IF OBJECT_ID(N'[Invert]','P') IS NOT NULL
DROP PROCEDURE [Invert]
GO
CREATE PROCEDURE dbo.[Invert] #tbl sysname, #top int=1000 AS
DECLARE #key sysname SELECT #key=COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#tbl AND ORDINAL_POSITION=1
DECLARE #sql nvarchar(max), #ids varchar(max)
SET #sql='SELECT TOP '+CAST(#top as varchar(9))+' #out=COALESCE(#out+'','','''')+QUOTENAME('
+QUOTENAME(#key)+') FROM '+QUOTENAME(#tbl)+' ORDER BY '+QUOTENAME(#key)
EXECUTE sp_executesql #sql, N'#out varchar(max) OUTPUT', #out=#ids OUTPUT
SET #sql=NULL
SELECT #sql=COALESCE(#sql+' UNION ALL ','')+'SELECT '''+COLUMN_NAME+''' AS '+QUOTENAME(#key)
+ ',* FROM (SELECT TOP '+CAST(#top as varchar(9))+' '+QUOTENAME(#key)+' k,CAST('
+ QUOTENAME(COLUMN_NAME)+'as varchar(8000)) m FROM '+QUOTENAME(#tbl)
+' ORDER BY '+QUOTENAME(#key)+') t PIVOT (MAX(m) FOR k IN ('+#ids+')) x'+CHAR(13)
FROM INFORMATION_SCHEMA.COLUMNS c WHERE TABLE_NAME=#tbl AND c.ORDINAL_POSITION>1
ORDER BY c.ORDINAL_POSITION
EXECUTE(#sql)
GO
The stored procedure uses PIVOT to pivot each column. UNPIVOT is nice, but can only be used if all the columns have the same type (including length). The procedure generates a dynamic SELECT that uses UNION ALL operator to combine PIVOTs for each column (except the key). The list of key values (#ids) is also dynamically generated because the PIVOT command expects an explicit column list.
Then you can call it like this:
EXEC Invert [User]
The second optional parameter is the top clause (the default is 1000). Below is an example that returns a maximum of 5 rows:
EXEC Invert [User], 5
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50),sal varchar(50)
)SET IDENTITY_INSERT [User] ON
--give same data type and size to all of field
INSERT INTO [User](Id,Name,Gender,sal) VALUES
(1, 'Fahad','Male',10000),
(2,'Saad','Male',20000),
(3,'Asif','Male',30000)
SELECT * FROM [User]
UNPIVOT ([Val] FOR Cols IN (name,gender,sal)) Unp
PIVOT (MAX([Val]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
sal 10000 20000 30000

How to replace a functional (many) OUTER APPLY (SELECT * FROM)

Applies to Microsoft SQL Server 2008 R2.
The problem is
If we have a few dozen Outer Apply (30) then they begin to work pretty slowly. In the middle of the Outer Apply I have something more complicated than a simple select, a view.
Details
I'm writing a sort of attributes assigned to tables (in the database). Generally, a few tables, holds a reference to a table of attributes (key, value).
Pseudo structure looks like this:
DECLARE #Lot TABLE (
LotId INT PRIMARY KEY IDENTITY,
SomeText VARCHAR(8))
INSERT INTO #Lot
OUTPUT INSERTED.*
VALUES ('Hello'), ('World')
DECLARE #Attribute TABLE(
AttributeId INT PRIMARY KEY IDENTITY,
LotId INT,
Val VARCHAR(8),
Kind VARCHAR(8))
INSERT INTO #Attribute
OUTPUT INSERTED.* VALUES
(1, 'Foo1', 'Kind1'), (1, 'Foo2', 'Kind2'),
(2, 'Bar1', 'Kind1'), (2, 'Bar2', 'Kind2'), (2, 'Bar3', 'Kind3')
LotId SomeText
----------- --------
1 Hello
2 World
AttributeId LotId Val Kind
----------- ----------- -------- --------
1 1 Foo1 Kind1
2 1 Foo2 Kind2
3 2 Bar1 Kind1
4 2 Bar2 Kind2
5 2 Bar3 Kind3
I can now run a query such as:
SELECT
[l].[LotId]
, [SomeText]
, [Oa1].[AttributeId]
, [Oa1].[LotId]
, 'Kind1Val' = [Oa1].[Val]
, [Oa1].[Kind]
, [Oa2].[AttributeId]
, [Oa2].[LotId]
, 'Kind2Val' = [Oa2].[Val]
, [Oa2].[Kind]
, [Oa3].[AttributeId]
, [Oa3].[LotId]
, 'Kind3Val' = [Oa3].[Val]
, [Oa3].[Kind]
FROM #Lot AS l
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind1') AS Oa1
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind2') AS Oa2
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind3') AS Oa3
LotId SomeText AttributeId LotId Kind1Val Kind AttributeId LotId Kind2Val Kind AttributeId LotId Kind3Val Kind
----------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- --------
1 Hello 1 1 Foo1 Kind1 2 1 Foo2 Kind2 NULL NULL NULL NULL
2 World 3 2 Bar1 Kind1 4 2 Bar2 Kind2 5 2 Bar3 Kind3
The simple way to get the pivot table of attribute values ​​and results for Lot rows that do not have attribute such a Kind3.
I know Microsoft PIVOT and it is not simple and do not fits here.
Finally, what will be faster and will give the same results?
In order to get the result you can unpivot and then pivot the data.
There are two ways that you can perform this. First, you can use the UNPIVOT and the PIVOT function:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
unpivot
(
value
for col in (attributeid, a_Lotid, val, kind)
) unpiv
) d
pivot
(
max(value)
for col in (attributeid_1, a_LotId_1, Val_1, Kind_1,
attributeid_2, a_LotId_2, Val_2, Kind_2,
attributeid_3, a_LotId_3, Val_3, Kind_3)
) piv
See SQL Fiddle with Demo.
Or starting in SQL Server 2008+, you can use CROSS APPLY with a VALUES clause to unpivot the data:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values ('attributeid', attributeid),('LotId', a_LotId), ('Value', Val), ('Kind', Kind)
) c (col, value)
) d
pivot
(
max(value)
for col in (attributeid_1, LotId_1, Value_1, Kind_1,
attributeid_2, LotId_2, Value_2, Kind_2,
attributeid_3, LotId_3, Value_3, Kind_3)
) piv
See SQL Fiddle with Demo.
The unpivot process takes the multiple columns for each LotID and SomeText and converts it into rows giving the result:
| LOTID | SOMETEXT | COL | VALUE |
--------------------------------------------
| 1 | Hello | attributeid_1 | 1 |
| 1 | Hello | LotId_1 | 1 |
| 1 | Hello | Value_1 | Foo1 |
| 1 | Hello | Kind_1 | Kind1 |
| 1 | Hello | attributeid_2 | 2 |
I added a row_number() to the inner subquery to be used to create the new column names to pivot. Once the names are created the pivot can be applied to the new columns giving the final result
This could also be done using dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+rn)
from
(
select
cast(ROW_NUMBER() over(partition by l.lotid order by a.attributeid) as varchar(10)) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) t
cross apply (values ('attributeid', 1),
('LotId', 2),
('Value', 3),
('Kind', 4)) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT LotId,
SomeText,' + #cols + '
from
(
select LotId,
SomeText,
col+''_''+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values (''attributeid'', attributeid),(''LotId'', a_LotId), (''Value'', Val), (''Kind'', Kind)
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
All three versions will give the same result:
| LOTID | SOMETEXT | ATTRIBUTEID_1 | LOTID_1 | VALUE_1 | KIND_1 | ATTRIBUTEID_2 | LOTID_2 | VALUE_2 | KIND_2 | ATTRIBUTEID_3 | LOTID_3 | VALUE_3 | KIND_3 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | Hello | 1 | 1 | Foo1 | Kind1 | 2 | 1 | Foo2 | Kind2 | (null) | (null) | (null) | (null) |
| 2 | World | 3 | 2 | Bar1 | Kind1 | 4 | 2 | Bar2 | Kind2 | 5 | 2 | Bar3 | Kind3 |

Create query with dynamic columns from different tables

I'm trying to create a query with dynamic columns, based on data from three tables.
This is the database structure:
STUDENT
studentID int,
studentNumber int,
studentName nvarchar(100).
EXAM:
examID int,
examName varchar(100),
examenDate datetime,
EXAM_REGISTRATION:
studentID int,
examID int,
A record is added to the EXAM_REGISTRATION table when a student has registered for an exam.
What I'm trying to get is a list of all the exams and all the students in a pivot table to see which students have registered for which exams, like this:
Quite frankly I don't know where to start.
I can query everything individually and put it all together but how can I combine it into one query?
I've been researching pivot tables, but every example seems to query only from one table and uses numbers and functions like MIN, AVG etc.
Can someone help me along?
ok lets go
some data to play with
create table #student
(studentID int, studentNumber int, studentName nvarchar(100))
create table #exam
(examID int, examName nvarchar(100), examDate datetime)
create table #examReg
(studentID int, examID int)
insert into #student
values (1, 787878, 'pierwszy')
,(2, 89898, 'drugi')
,(3, 343434, 'trzeci')
,(4, 121212, 'czwarty')
insert into #exam
values (1, 'exPierwszy', GETDATE())
,(2, 'exDrugi', GETDATE())
,(3, 'exTrzeci', GETDATE())
insert into #examReg
values (1,2),(1,3)
, (2,2),(2,3)
,(3,1),(3,2)
,(4,1),(4,2),(4,3)
and now the main part, and explanation
first of all you have to get pivot query
select examName, examDate , min([1]) , min([2]), min([3]) ,min([4])--studentID as studentID, examDate --,studentNumber
from
(select a.studentID , studentNumber, examDate, examName
from #student a
join #examReg b on a.studentID = b.studentID
join #exam c on c.examID = b.examID ) as m
pivot
(min(studentNumber) FOR studentID in ([1],[2],[3],[4])) as t
group by examName, examDate
as you have it , just change it select statement and studentID list in pivot declaration, you have to generate those parts dynamicly , so we just copy previously written query and replace columns with our token
declare #sqlTemplate nvarchar(max) =
'select examName, examDate ##sqlColumnList##
from
(select a.studentID , studentNumber, examDate, examName
from #student a
join #examReg b on a.studentID = b.studentID
join #exam c on c.examID = b.examID ) as m
pivot
(min(studentNumber) FOR studentID in (##sqlStudentIDList##)) as t
group by examName, examDate
'
after that you generate column list and studentID list by concatenting strings in tsql
declare #sqlColumnList nvarchar(max) = ''
select #sqlColumnList += ',min([' + cast(studentID as nvarchar(10)) + ']) as [' + studentName +'(' + cast(studentNumber as nvarchar(10)) + ')]'
from #student
declare #sqlStudentIDList nvarchar(max) = ''
select #sqlStudentIDList += '[' + CAST(studentID as nvarchar(10)) + '],'
from #student
set #sqlStudentIDList = SUBSTRING(#sqlStudentIDList, 0, LEN(#sqlStudentIDList))
select #sqlStudentIDList
once you have it , all you have to do is to replace tokens in previous template
set #sqlTemplate = REPLACE(#sqlTemplate, '##sqlColumnList##', #sqlColumnList)
set #sqlTemplate = REPLACE(#sqlTemplate, '##sqlStudentIDList##', #sqlStudentIDList)
select #sqlTemplate
exec sp_executesql #sqlTemplate
and thats it
if you want to read more about pivot go for msdn
if you want to read about dynamic go for this link
edit: to adjust the query for the question from comment you would have to change #sqlColumnList like that
select #sqlColumnList += ',min(' + QUOTENAME(studentID) + ') as Student' + CAST(studentID as nvarchar(10)) + '_REG,
'''+ studentName + ''' as Student' + cast(studentID as nvarchar(10)) + '_NAME,
'+ cast(studentID as nvarchar(10)) + ' as Student' + cast(studentID as nvarchar(10)) + '_ID'
from #student
This is a pivot of the data. I would perform this slightly different than the other answer. If you know all of the values, then you can hard-code the values.
A static version will be:
select examname,
examendate,
IsNull([Smith, John (14323)], 'false') [Smith, John (14323)],
IsNull([Craft, Peter (14334)], 'false') [Craft, Peter (14334)],
IsNull([Davis, Alan (13432)], 'false') [Davis, Alan (13432)],
IsNull([Newman, Ted (133123)], 'false') [Newman, Ted (133123)]
from
(
select e.examname,
e.examenDate,
s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')' studentNameNum,
'true ' as Flag
from exam e
left join exam_registration er
on e.examid = er.examid
right join student s
on er.studentid = s.studentid
) src
pivot
(
max(flag)
for studentNameNum in ([Smith, John (14323)], [Craft, Peter (14334)],
[Davis, Alan (13432)], [Newman, Ted (133123)])
) piv
See SQL Fiddle with Demo
If your values are unknown then the query will be:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')')
from student s
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT distinct ',IsNull(' + QUOTENAME(s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')')+', ''false'')'+' as '+QUOTENAME(s.studentName+' ('+cast(s.studentnumber as varchar(50))+')')
from student s
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT examname,
examenDate,' + #colsNull + ' from
(
select e.examname,
e.examenDate,
s.studentName + '' (''+cast(s.studentnumber as varchar(50))+'')'' studentNameNum,
''true '' as Flag
from exam e
left join exam_registration er
on e.examid = er.examid
right join student s
on er.studentid = s.studentid
) x
pivot
(
max(flag)
for studentNameNum in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result will be:
| EXAMNAME | EXAMENDATE | CRAFT, PETER (14334) | DAVIS, ALAN (13432) | NEWMAN, TED (133123) | SMITH, JOHN (14323) |
----------------------------------------------------------------------------------------------------------------------------
| Exam 1 | 2013-01-01 12:00:00 | false | false | true | false |
| Exam 2 | 2013-01-01 14:00:00 | true | false | false | true |
| Exam 3 | 2013-01-02 12:00:00 | true | true | false | false |
| Exam 4 | 2013-01-02 14:00:00 | false | false | true | false |
| Exam 5 | 2013-01-03 12:00:00 | false | false | false | true |

Resources