Pivot Table with many to many table - sql

My SQL Fiddle is here: http://sqlfiddle.com/#!3/d5c60
CREATE TABLE customer
(
id int identity primary key,
name varchar(20),
);
CREATE TABLE warehouse
(
id int identity primary key,
name varchar(20),
);
CREATE TABLE customerwarehouse
(
id int identity primary key,
customerid int,
warehouseid int
);
INSERT INTO customer (name)
VALUES
('CustA'),
('CustB'),
('CustC');
INSERT INTO warehouse (name)
VALUES
('wh01'),
('wh02'),
('wh03');
INSERT INTO customerwarehouse (customerid, warehouseid)
VALUES
(1,1),
(2,1),
(2,2),
(3,1),
(3,2),
(3,3);
I would like to write a query to return the customer/warehouse data in the following format:
Customer WH1 WH2 WH3
CustA wh01
CustB wh01 wh02
CustC wh01 wh02 wh03
My attempt to do this returns null for all warehouses.
How can I construct my query to return the data in the required format?

In order to get the result, you will want to JOIN the tables and apply the PIVOT function. I would also suggest using the row_number() windowing function to get the number of warehouses for each customer - this will be the value that will be used as your new column headers.
select customername, wh1, wh2, wh3
from
(
select w.name warehousename,
c.name customername,
'wh'+cast(row_number() over(partition by c.id
order by w.id) as varchar(10)) seq
from customer c
inner join customerwarehouse cw
on c.id = cw.customerid
inner join warehouse w
on cw.warehouseid = w.id
) d
pivot
(
max(warehousename)
for seq in (wh1, wh2, wh3)
) piv;
See SQL Fiddle with Demo. If you have an unknown number of values, then you will need to use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('wh'+cast(row_number() over(partition by customerid
order by warehouseid) as varchar(10)))
from customerwarehouse
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT customername, ' + #cols + '
from
(
select w.name warehousename,
c.name customername,
''wh''+cast(row_number() over(partition by c.id
order by w.id) as varchar(10)) seq
from customer c
inner join customerwarehouse cw
on c.id = cw.customerid
inner join warehouse w
on cw.warehouseid = w.id
) x
pivot
(
max(warehousename)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both give a result:
| CUSTOMERNAME | WH1 | WH2 | WH3 |
| CustA | wh01 | (null) | (null) |
| CustB | wh01 | wh02 | (null) |
| CustC | wh01 | wh02 | wh03 |

Here's what I came up with after viewing the Complex PIVOT Example on this MSDN page:
SELECT
CustomerName,
case when [wh01] is null then null else 'wh01' end,
case when [wh02] is null then null else 'wh02' end,
case when [wh03] is null then null else 'wh03' end
FROM (
SELECT
c.Name AS CustomerName,
cw.id AS cwid,
w.name AS WarehouseName
FROM Customer c
JOIN CustomerWarehouse cw
ON c.id = cw.customerId
JOIN Warehouse w
ON w.id = cw.warehouseId
) AS SourceTable
pivot (
max(cwid)
FOR WarehouseName IN (
[wh01], [wh02], [wh03]
)
) AS PivotTable
On SQLFiddle: http://sqlfiddle.com/#!3/d5c60/42

Related

How can I join the results of multiple pivot queries together horizontally?

I may not have phrased the question clearly so I'll clarify it here.
I've got a VB.NET system that displays data. To display data, I have the following SQL stored procedure that makes use of pivot:
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(form_column_id) from
GetFormColumns(#formTemplateId) FOR XML PATH(''),
TYPE).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set #query = N' select *
from
(select row_number as Row,fc.form_column_id, fdd.data
from
form_data_h fdh
inner join form_data_d fdd on fdd.form_data_h_id = fdh.form_data_h_id
inner join form_column fc on fc.form_column_id = fdd.form_column_id
inner join column_header c on c.column_header_id=fc.column_header_id
where fdh.is_active = 1 and fdh.form_data_h_id= ' +
CONVERT(varchar(10),#formDataHId) + ' and fc.is_active = 1
) src
pivot(
min(data)
for form_column_id in (' + #cols + N')
) piv'
execute(#query);
I had to make use of pivot because user data entry needs to be dynamic. So from this...
table results
The results now look like this.
pivot results
This data has different results filtered by particular parameters. Right now I've displayed the crop data for 2017. I want to join it with the crop data for 2018 (will change based on the Stored Procedure parameter #formDataHId).
That should result in something that looks like this...
2017 and 2018 results
Is there any way I can go about this in SQL or do I have to do that in VB.net? If so, how can I go about it?
Any ideas would be welcome because I'm a bit stumped right now. If users need to see data from let's say 2016 to 2019, it should be presented like that.
Pivot in SQL should be enough. If the structure of data in each year is the same, you can use UNION ALL between each year's data. Then use your query to pivot the combined years' data.
In my pivot function I use a table named Test to generate the pivot. I have 2 other tables Source2017 and Source2018. I insert both Source2017 and Source2018 using this query.
INSERT #Test
SELECT Date, Item, Quantity FROM #Source2017
UNION ALL
SELECT Date, Item, Quantity FROM #Source2018
If I only want data from 2017, I remove the Source2018 from the insert statement.
INSERT #Test
SELECT Date, Item, Quantity FROM #Source2017
Let's say I have more tables, Source2015 and Source2016. If I want to pivot all of them, just add the tables using UNION ALL.
INSERT #Test
SELECT Date, Item, Quantity FROM #Source2015
UNION ALL
SELECT Date, Item, Quantity FROM #Source2016
UNION ALL
SELECT Date, Item, Quantity FROM #Source2017
UNION ALL
SELECT Date, Item, Quantity FROM #Source2018
Full query:
IF OBJECT_ID('tempdb..#Test') IS NOT NUll DROP TABLE #Test
IF OBJECT_ID('tempdb..#Source2017') IS NOT NUll DROP TABLE #Source2017
IF OBJECT_ID('tempdb..#Source2018') IS NOT NUll DROP TABLE #Source2018
CREATE TABLE #Test
(
Date DATE,
Item VARCHAR(100),
Quantity INT
)
CREATE TABLE #Source2017
(
Date DATE,
Item VARCHAR(100),
Quantity INT
)
CREATE TABLE #Source2018
(
Date DATE,
Item VARCHAR(100),
Quantity INT
)
INSERT #Source2017 VALUES
('2017/01/01', 'Mango', 5),
('2017/01/01', 'Orange', 6),
('2017/01/02', 'Mango', 7),
('2017/01/02', 'Orange', 8),
('2017/01/02', 'Cherry', 9)
INSERT #Source2018 VALUES
('2018/01/01', 'Durian', 15),
('2018/01/02', 'Orange', 28),
('2018/01/03', 'Cherry', 19)
INSERT #Test
SELECT Date, Item, Quantity FROM #Source2017
UNION ALL
SELECT Date, Item, Quantity FROM #Source2018
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #Columns AS VARCHAR(MAX)
DECLARE #Columns2 AS VARCHAR(MAX)
SELECT #Columns = COALESCE(#Columns + ',','') + QUOTENAME(Date)
FROM (SELECT DISTINCT Date FROM #Test) AS B
ORDER BY B.Date
SELECT #Columns2 = COALESCE(#Columns2 + ',','') + 'ISNULL(' + QUOTENAME(Date) + ', 0) AS [' + CAST(Date AS VARCHAR(100)) + ']'
FROM (SELECT DISTINCT Date FROM #Test) AS B
ORDER BY B.Date
SET #SQL = '
WITH PivotData AS
(
SELECT Date, Item, Quantity FROM #Test
)
SELECT
Item, ' + #Columns2 + '
FROM PivotData
PIVOT
(
SUM(Quantity)
FOR Date
IN (' + #Columns + ')
) AS PivotResult
ORDER BY Item'
EXEC(#SQL);
DROP TABLE #Test
DROP TABLE #Source2017
DROP TABLE #Source2018
Result:
+--------+------------+------------+------------+------------+------------+
| Item | 2017-01-01 | 2017-01-02 | 2018-01-01 | 2018-01-02 | 2018-01-03 |
+--------+------------+------------+------------+------------+------------+
| Cherry | 0 | 9 | 0 | 0 | 19 |
| Durian | 0 | 0 | 15 | 0 | 0 |
| Mango | 5 | 7 | 0 | 0 | 0 |
| Orange | 6 | 8 | 0 | 28 | 0 |
+--------+------------+------------+------------+------------+------------+

SQL: Display the table and its job by yes and no

This is my table:
User Table
Id | Username |
---------------
1 | jdoe |
Job Table
Id | Job |
----------------
1 | Waiter |
2 | Office |
3 | Freelance |
User Job Table
Id |UserId | JobId |
--------------------
1 | 1 | 2 |
2 | 1 | 3 |
How to select this table and display it to look like this below:
Id | Username | Waiter| Office | Freelance|
-------------------------------------------
1 | jdoe | No | Yes | Yes |
This is a pretty standard pivot query question, with an additional slight twist. In case a given user does not have a certain type of job assigned, you want to display 'No'. One way to do this is to make use of COALESCE and replace NULL job aggregates.
SELECT u.Id,
u.Username,
COALESCE(MAX(CASE WHEN j.Job = 'Waiter' THEN 'Yes' END), 'No') AS Waiter,
COALESCE(MAX(CASE WHEN j.Job = 'Office' THEN 'Yes' END), 'No') AS Office,
COALESCE(MAX(CASE WHEN j.Job = 'Freelance' THEN 'Yes' END), 'No') AS Freelance
FROM User u
LEFT JOIN User_Job uj
ON u.Id = uj.UserId
LEFT JOIN Job j
ON uj.JobId = j.Id
GROUP BY u.Id,
u.Username
Try this..
WITH cte AS
(
SELECT u.username,job,CASE WHEN uj.jobid IS NULL THEN 'No' ELSE 'yes' END AS jobid
FROM USER u INNER JOIN UserJob uj on u.id = uj.Userid
RIGHT JOIN Job j on j.id = uj.jobid
)
SELECT username,ISNULL(waiter,'no') waiter,
ISNULL(Office,'no') Office,
ISNULL(Freelance,'no') Freelance
FROM cte
PIVOT
(
MAX(jobid) FOR job IN (Waiter, Office,Freelance)
) piv
WHERE username IS NOT NULL
DECLARE #cols AS NVARCHAR(MAX),#Listcols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + 'Isnull('+QUOTENAME(t.Job) +',''No'') as ' + t.Job
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #Listcols = STUFF((SELECT ',' + QUOTENAME(t.Job)
from Job t
order by Job desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT Id, Username,' + #cols + N' from
(
select D1.Id, D1.Username,
COALESCE(CASE WHEN isnull(D2.UserId,0) > 0 THEN ''Yes''
Else ''No''
End, ''No'') as UserId,
D3.Job
from [User] D1
Inner Join User_Job D2
On D1.Id = D2.UserId
Inner Join Job D3
On D2.JobId = D3.Id
) x
pivot
(
max(UserId)
for Job in (' + #Listcols + N')
) p '
exec sp_executesql #query;
If the job items are come from a result.
CREATE TABLE #T1(id INT ,Username VARCHAR(100))
INSERT INTO #T1 SELECT 1,'jdoe'
CREATE TABLE #T2(id INT ,Job VARCHAR(100))
INSERT INTO #T2 VALUES(1,'Waiter'),(2,'Office'),(3,'Freelance')
CREATE TABLE #T3(id INT ,UserId INT ,JobId INT )
INSERT INTO #T3 VALUES(1,1,2),(2,1,3)
DECLARE #col NVARCHAR(max),#sql NVARCHAR(max)
SELECT #col=ISNULL(#col+',[','[')+Job+']' FROM #t2
PRINT #col
SET #sql='
SELECT * FROM (
SELECT uj.Job,uj.Username,CASE WHEN t3.UserId IS NULL THEN ''No'' ELSE ''Yes'' END AS f FROM (
SELECT t2.id AS JobId, t2.Job,t1.id AS UserId, t1.Username
FROM #T1 AS t1,#t2 AS t2
) uj LEFT JOIN #T3 AS t3 ON t3.JobId = uj.JobId AND t3.UserId = uj.UserId
) AS t PIVOT(MAX(f) FOR job IN ('+#col+')) p'
EXEC(#sql)

Format Jagged data gained from dynamic pivot

I need to format and extract some data from a database. While I can extract the data successfully I am struggling with the jagged nature of it.
What I have is the following:
create table temp
(
QuestionID INT,
AnswerID INT,
AnswerValue NVARCHAR(50)
)
insert into temp values (1, 1, 'Ans C')
insert into temp values (1, 2, 'Ans B')
insert into temp values (1, 3, 'Ans A')
insert into temp values (2, 4, 'Ans D')
insert into temp values (2, 5, 'Ans E')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.AnswerID)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT QuestionID, ' + #cols + ' from
(
select QuestionID
, AnswerValue
, AnswerID
from temp
) x
pivot
(
max(AnswerValue)
for AnswerID in (' + #cols + ')
) p '
execute(#query)
drop table temp
Executed this produces
+------------+-------+-------+-------+-------+-------+
| QuestionID | 1 | 2 | 3 | 4 | 5 |
+------------+-------+-------+-------+-------+-------+
| 1 | Ans C | Ans B | Ans A | NULL | NULL |
| 2 | NULL | NULL | NULL | Ans D | Ans E |
+------------+-------+-------+-------+-------+-------+
I just need to format it like this
+------------+-------+-------+-------+
| QuestionID | Q1 | Q2 | Q3 |
+------------+-------+-------+-------+
| 1 | Ans C | Ans B | Ans A |
| 2 | NULL | Ans D | Ans E |
+------------+-------+-------+-------+
Note due to restrictions this needs to be done in SQL rather than an advanced language such as c#.
A few things are wrong with the code. First, you are creating your column list using the AnswerID so the data is being split across multiple columns instead of the Answer for each question.
In order to fix this, you'll want to use a windowing function like row_number() to create a sequence for each question/answer combination.
When creating your dynamic columns change the code to be:
SET #cols = STUFF((SELECT ',' + QUOTENAME('Q'+cast(rn as varchar(10)))
FROM
(
SELECT rn = row_number() over(partition by QuestionID
order by AnswerID)
FROM temp
) c
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
This will use row_number() and will create the column names based on the QuestionID. Then you'll include the row_number() in your subquery making your code:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT ',' + QUOTENAME('Q'+cast(rn as varchar(10)))
FROM
(
SELECT rn = row_number() over(partition by QuestionID
order by AnswerID)
FROM temp
) c
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT QuestionID, ' + #cols + '
from
(
select QuestionID
, AnswerValue
, col = ''Q''+ cast(row_number() over(partition by QuestionID
order by AnswerID) as varchar(10))
from temp
) x
pivot
(
max(AnswerValue)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| QUESTIONID | Q1 | Q2 | Q3 |
|------------|-------|-------|--------|
| 1 | Ans C | Ans B | Ans A |
| 2 | Ans D | Ans E | (null) |
You can use this part of code:
SELECT 'A' + CAST(ROW_NUMBER() OVER(PARTITION BY QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers
in order to generate the column names required. The above gives you sth like:
cName
-----
A1
A2
A3
A1
A2
You can subsequently use the above in your dynamic pivot to obtain the desired result:
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(a.cName)
FROM (
SELECT 'A' + CAST(ROW_NUMBER() OVER(PARTITION BY QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers
) a
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT Question, ' + #cols + ' ' +
'FROM (
SELECT q.Question, a.Answer,
''A'' + CAST(ROW_NUMBER() OVER(PARTITION BY a.QuestionID ORDER BY Answer) AS VARCHAR(10)) AS cName
FROM tblAnswers AS a
INNER JOIN tblQuestions AS q ON a.QuestionID = q.QuestionID
) t
PIVOT
(
MAX(t.Answer)
FOR cName in (' + #cols + ')
) Pvt '
execute(#query)
Output from above looks like:
Question A1 A2 A3
-----------------------------------
Q1 Answer1 Answer2 Answer3
Q2 Answer4 Answer5 NULL
SQL Fiddle demo here

SQL Server - Complex Dynamic Pivot multiple columns

FYI, this question is already answered but I have some new requirements which is very complex to implement so, I am posting it as a new question instead of editing the old question: (Previous Question)
I have two tables "Controls" and "ControlChilds" (in the ControlChilds table we have added a new column called ControlChildComments which we need to show in PIVOT output)
Parent Table Structure:
Create table Controls(
ProjectID Varchar(20) NOT NULL,
ControlID INT NOT NULL,
ControlCode Varchar(2) NOT NULL,
ControlPoint Decimal NULL,
ControlScore Decimal NULL,
ControlValue Varchar(50)
)
Sample Data
ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001 1 A 30.44 65 Invalid
P001 2 C 45.30 85 Valid
Child Table Structure:
Create table ControlChilds(
ControlID INT NOT NULL,
ControlChildID INT NOT NULL,
ControlChildValue Varchar(200) NULL,
ControlChildComments Varchar(200) NULL
)
Sample Data
ControlID | ControlChildID | ControlChildValue | ControlChildComments
1 100 Yes Something
1 101 No NULL
1 102 NA Others
1 103 Others NULL
2 104 Yes New one
2 105 SomeValue NULL
Based on my previous question (Previous Question) I got this output (You can refer to the PIVOT queries which produces this output in the answer given by #bluefeet. Thanks again #bluefeet.)
But now my requirement is changed and I need ControlChildComments after each Child values. For example, A_Child1, A_Child1Comments, A_Child2, A_Child2Comments etc...
Another tricky thing is I need to show the comments only when they are not null otherwise I shouldn't show the column. For example, in this case, it should be like this:
A_Child1, A_Child1Comments, A_Child2, A_Child3, A_Child3Comments, A_Child4, C_Child1, C_Child1Comments, C_Child2
Is this possible? I tried lot of things but the results are not accurate.
Since you now have multiple columns in your ControlChilds table that you need to PIVOT, you will need to use the similar method of unpivoting them first that you applied with the Controls table.
You will need to unpivot both the ChildControlValue and ChildControlComments using code similar to:
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value);
See SQL Fiddle with Demo. This gets your data in the format:
| PROJECTID | COL | VALUE |
|-----------|------------------|-----------|
| P001 | A_ChildValue1 | Yes |
| P001 | A_ChildComments1 | Something |
| P001 | A_ChildValue2 | No |
| P001 | A_ChildComments2 | (null) |
| P001 | A_ChildValue3 | NA |
You then use this code in your existing query:
select ProjectId,
A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2
from
(
select
ProjectId,
col = ControlCode +'_'+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select 'ControlPoint', cast(controlpoint as varchar(10)) union all
select 'ControlScore', cast(ControlScore as varchar(10)) union all
select 'ControlValue', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value)
) src
pivot
(
max(val)
for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2)
) piv;
See SQL Fiddle with Demo. Finally, you'll implement this in your dynamic SQL script:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select ControlCode,
col = ControlCode +'_'+col,
seq,
so
from controls
cross apply
(
select 'ControlPoint', 0, 0 union all
select 'ControlScore', 0, 1 union all
select 'ControlValue', 0, 2
) c (col, seq, so)
union all
select ControlCode,
col = ControlCode+'_'+subcol+cast(rn as varchar(10)),
rn,
so
from
(
select ControlCode,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', seq, 3 union all
select 'ChildComments', seq, 4
) c (subcol, rn, so)
) src
group by ControlCode, seq, col, so
order by ControlCode, seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ProjectId, ' + #cols + '
from
(
select ProjectId,
col = ControlCode +''_''+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select ''ControlPoint'', cast(controlpoint as varchar(10)) union all
select ''ControlScore'', cast(ControlScore as varchar(10)) union all
select ''ControlValue'', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+''_''+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select ''ChildValue'', ControlChildValue union all
select ''ChildComments'', ControlChildComments
) c (subCol, value)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both of these gives a result:
| PROJECTID | A_CONTROLPOINT | A_CONTROLSCORE | A_CONTROLVALUE | A_CHILDVALUE1 | A_CHILDCOMMENTS1 | A_CHILDVALUE2 | A_CHILDCOMMENTS2 | A_CHILDVALUE3 | A_CHILDCOMMENTS3 | A_CHILDVALUE4 | A_CHILDCOMMENTS4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILDVALUE1 | C_CHILDCOMMENTS1 | C_CHILDVALUE2 | C_CHILDCOMMENTS2 |
|-----------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|---------------|------------------|---------------|------------------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|
| P001 | 30.44 | 65.00 | Invalid | Yes | Something | No | (null) | NA | Others | Others | (null) | 45.30 | 85.00 | Valid | Yes | New one | SomeValue | (null) |
Here is an example of a dynamic crosstab. Since you have multiple columns you would need to adjust the dynamic portion of this to suit.
if OBJECT_ID('Something') is not null
drop table Something
create table Something
(
ID int,
Subject1 varchar(50)
)
insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
from Something
)
select ID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from Something
group by ID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
--declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
--exec sp_executesql #SqlToExecute

SQL one to many on one row

I have a table that records the diagnosis of patient for each attendance. Patients can have more than one diagnosis.
I want one row per attendance with all diagnosis (up to 12 diagnosis). The below brings back more than one row.
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
from
Diagnosis dg
AttendanceID PatientNumber DiagnosisDate Diagnosis
10001 123456 01-Oct-13 A
10001 123456 01-Oct-13 B
10002 123456 20-Oct-13 D
It results should look like this:
AttendanceID PatientNumber DiagnosisDate Diagnosis 1 Diagnosis 2 Diagnosis 3
10001 123456 01-Oct-13 A B
10002 123456 20-Oct-13 D
Can someone please help?
You can get the result by implementing the PIVOT function, but I would also suggest using the windowing function row_number() to generate the number of diagnosis for each attendanceid and patientnumber:
select attendanceid, patientnumber,
diagnosisdate,
Diagnosis1, Diagnosis2, Diagnosis3
from
(
select attendanceid, patientnumber,
diagnosisdate, diagnosis,
'diagnosis'+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) d
pivot
(
max(diagnosis)
for seq in (Diagnosis1, Diagnosis2, Diagnosis3)
) piv;
See SQL Fiddle with Demo. Since you know you will have up to 12 diagnosis per patient/attendance you can easily hard code the query to add the additional columns.
But if you needed a dynamic version of the code, then you could use something similar to the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(seq)
from
(
select 'diagnosis'+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT attendanceid, patientnumber,
diagnosisdate,' + #cols + '
from
(
select attendanceid, patientnumber,
diagnosisdate, diagnosis,
''diagnosis''+
cast(row_number() over(partition by attendanceid, patientnumber
order by diagnosis) as varchar(2)) seq
from diagnosis
) x
pivot
(
max(diagnosis)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo
Both versions give a result:
| ATTENDANCEID | PATIENTNUMBER | DIAGNOSISDATE | DIAGNOSIS1 | DIAGNOSIS2 | DIAGNOSIS3 |
|--------------|---------------|--------------------------------|------------|------------|------------|
| 10001 | 123456 | October, 01 2013 00:00:00+0000 | A | B | (null) |
| 10002 | 123456 | October, 20 2013 00:00:00+0000 | D | (null) | (null) |
I am not 100% sure but it should be something along those lines.
;with cte as(
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
,ROW_NUMBER() over (partition by dg.attendanceID order by attendanceID) as seq1
,ROW_NUMBER() over (partition by dg.attendanceID, patient_no order by diagnosis) as seq2
from
Diagnosis dg
)
select
t1.attendanceID
,t1.patientNumber
,t1.diagnosisDate
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='1') as diag1
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='2') as diag2
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='3') as diag3
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='4') as diag4
,(select Diagnosis from cte t2 where t1.attendanceID=t2.attendanceID and t1.patientNumber=t2.patientNumber and t2.seq2='5') as diag5
from cte t1
where seq1= 1
Below is a probable solution by using cursors i am not sure if you can show it as columns as it is variable for every patient but this will show diagnosis as comma separated in one single column
DECLARE #combinedString VARCHAR(MAX),
#id int,
#Diagnosis VARCHAR(MAX)
Declare #CONCATRESULT TABLE (AttendanceID int, Question VARCHAR(MAX) )
Declare Dia cursor for
SELECT AttendanceID FROM Diagnosis
open Dia
Fetch next from Dia into #id
while ##FETCH_STATUS=0
begin
SELECT #combinedString = COALESCE(#combinedString + ', ', '') + Diagnosis,
#id=AttendanceID FROM [Prod_PostData].[dbo].[KMSFPostDataDenorm] d
WHERE AttendanceID = #id
insert into #CONCATRESULT values ( #id ,#combinedString )
SET #combinedString = null
Fetch next from Dia into #id
end
close Dia
deallocate Dia
select
dg.AttendanceID
, dg.PatientNumber
, dg.DiagnosisDate
, dg.Diagnosis
from
Diagnosis dg join (select * from #CONCATRESULT) Y on dg.AttendanceID=Y.AttendanceID