Remove duplicates with row pivot - sql

I want to get rid of duplicates in one column (device_name) but keep related data from another column (app_id). Each device can have couple of applications (1 -> x, usually between 1-5) so I want to put those app identifiers into a new columns which I want to call [APP1],[APP2],[APP3] and so on. The best option would be kind of dynamic Pivot, but any static solution will be welcome as well.
Thanks for help in advance.
PS
I came up with below code what so ever, but in only concatenate APP id's separated by comas into one column.
USE tempdb;
SELECT DEVICE_NAME,
NoOfApps,
STUFF(( SELECT ', ' + APP_ID
FROM dbo.Aperture_full_test apps
WHERE apps.DEVICE_NAME = Aperture_full_test.DEVICE_NAME
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 2, '') AS Appid
FROM ( SELECT DEVICE_NAME, COUNT(DEVICE_NAME) AS NoOfApps
FROM dbo.Aperture_full_test
GROUP BY DEVICE_NAME
) Aperture_full_test
ORDER BY NoOfApps DESC
Data sample:
USE tempdb;
GO
IF OBJECT_ID('dbo.Aperture_full_test') IS NOT NULL
DROP TABLE dbo.Aperture_full_test;
GO
CREATE TABLE dbo.Aperture_full_test
(
DEVICE_NAME varchar(30) NOT NULL,
APP_ID varchar(10) NOT NULL
);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 157848);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 155439);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('LDNSQLF700', 635533);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('NYSQL502', 189164);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('NYSQL502', 188641);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('AUSSQL140', 537990);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('AUSSQL140', 1349605);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('JAP543X2', 5646789);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EU456CLX', 6545789);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 5637965);
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 6464367) ;
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 1323123) ;
INSERT INTO dbo.Aperture_full_test(DEVICE_NAME, APP_ID)
VALUES('EUCTX654', 1004326) ;
GO

Since you are using SQL Server you can implement the PIVOT function.
If you have a known number of values then you can hard code the query using the following:
select device_name, App1, App2, App3, App4, App5
from
(
select device_name, app_id,
'App'+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col
from Aperture_full_test
) d
pivot
(
max(app_id)
for col in (App1, App2, App3, App4, App5)
) piv;
See SQL Fiddle with Demo.
But if you are going to have an unknown number of app_ids for each device, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ', ' + QUOTENAME('App'+cast(rn as varchar(10)))
from
(
select row_number() over(partition by device_name
order by device_name) rn
from Aperture_full_test
) d
group by rn
order by rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT device_name, ' + #cols + ' from
(
select device_name, app_id,
''App''+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col
from Aperture_full_test
) x
pivot
(
max(app_id)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give the result:
| DEVICE_NAME | APP1 | APP2 | APP3 | APP4 |
-------------------------------------------------------
| AUSSQL140 | 537990 | 1349605 | (null) | (null) |
| EU456CLX | 6545789 | (null) | (null) | (null) |
| EUCTX654 | 5637965 | 6464367 | 1323123 | 1004326 |
| JAP543X2 | 5646789 | (null) | (null) | (null) |
| LDNSQLF700 | 157848 | 155439 | 635533 | (null) |
| NYSQL502 | 189164 | 188641 | (null) | (null) |
Edit, If you want to count the total number of devices for each server, then you can use count() over(). The hard-coded version will be:
select device_name, TotalDevices, App1, App2, App3, App4, App5
from
(
select device_name, app_id,
'App'+
cast(row_number() over(partition by device_name
order by device_name) as varchar(10)) col,
count(app_id) over(partition by device_name) TotalDevices -- add this line
from Aperture_full_test
) d
pivot
(
max(app_id)
for col in (App1, App2, App3, App4, App5)
) piv;
See SQL Fiddle with Demo

This is a pivot query, which can be done in the group by. What you need is a sequence number for the Apps.
select aft.deviceName, COUNT(*) as NumApps,
MAX(case when seqnum = 1 then App_id end) as App1,
MAX(case when seqnum = 2 then App_id end) as App2,
MAX(case when seqnum = 3 then App_id end) as App3,
MAX(case when seqnum = 4 then App_id end) as App4,
MAX(case when seqnum = 5 then App_id end) as App5
from (select aft.*,
ROW_NUMBER() over (partition by device_name order by (select NULL)) as seqnum
from Aperture_full_test aft
) aft
group by aft.deviceName
order by NumApps desc
This version does not have the Apps in a particular order becasue the xml code did not have them in a particular order.

Related

Transpose rows into columns dynamically in SQL Server

I want to transpose the below rows dynamically into columns.
**Process Id Attribute Values**
1 Equipment Normal
1 Complaints No
1 Availability 30 min
2 Phone1 123456789
2 Phone2 987654321
I have tried to pivot it but I am unable to get the desired results. I need the below output
Process ID Attribute1 Value1 Arrtibute2 Value2 Attribute3 Value3
1 Equipment Normal Complaints No Availability 30 min
2 Phone1 123456789 Phone2 987654321 NULL NULL
One process can have one or more attributes. So if a process have 10 attributes, it should create 10 columns in the desired output. Any suggestions?
All the newest ANSI compliant databases should support this. Witn no DBMS, I stick to the newest ANSI standard.
WITH
-- your input
input(Process_Id,Attribute,Values) AS (
SELECT 1,'Equipment','Normal'
UNION ALL SELECT 1,'Complaints','No'
UNION ALL SELECT 1,'Availability','30 min'
UNION ALL SELECT 2,'Phone1','123456789'
UNION ALL SELECT 2,'Phone2','987654321'
)
,
-- need a sequence counter ...
with_seq AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY process_id) AS seq
, *
FROM input
)
SELECT
process_id
, MAX(CASE seq WHEN 1 THEN attribute END) AS attrib1
, MAX(CASE seq WHEN 1 THEN values END) AS val1
, MAX(CASE seq WHEN 2 THEN attribute END) AS attrib2
, MAX(CASE seq WHEN 2 THEN values END) AS val2
, MAX(CASE seq WHEN 3 THEN attribute END) AS attrib3
, MAX(CASE seq WHEN 3 THEN values END) AS val3
FROM with_seq
GROUP BY process_id;
-- out process_id | attrib1 | val1 | attrib2 | val2 | attrib3 | val3
-- out ------------+-----------+-----------+------------+-----------+--------------+-------
-- out 1 | Equipment | Normal | Complaints | No | Availability | 30 min
-- out 2 | Phone1 | 123456789 | Phone2 | 987654321 | |
Try this below code. It will provide desired output.
My sample Algorithm
1. Create manual named columns by using ROW_NUMBER()
2. Create Dynamic columns for Attribute,Value named as #columnsAttribute, #columnsValue
3. Create Dynamic group by columns for both Attribute & Value named as #Allcolumns
4. Dynamic query creation with pivot for both Attribute & Value
declare #tblAttribute as
table(processid int,
attribute NVARCHAR(100),
value NVARCHAR(200))
insert into #tblAttribute(processid,attribute,value)
values(1,'Equipment','Normal'),
(1,'Complaints','No'),
(1,'Availability','30 min'),
(1,'test','testvalue'),
(2,'Phone1','123456789'),
(2,'Phone2','987654321')
;with ctetbl as
(
select ROW_NUMBER() over (partition by processid order by processid) rno,* from #tblAttribute
)
,ctetbl1 as
(
select processid,
'Attribute'+ cast(rno as NVARCHAR(2)) as DynamicAttribute,
'Value'+cast(rno as NVARCHAR(2)) as DyanamicValue
,attribute,value from ctetbl
)
select * into #tblDynamicAttribute from ctetbl1
declare #Allcolumns as NVARCHAR(max),
#columnsAttribute as NVARCHAR(max),
#columnsValue as NVARCHAR(max),
#sql as NVARCHAR(MAX) = ''
select #Allcolumns=coalesce(#Allcolumns+',','')+'max('+QUOTENAME(B.DynamicAttribute)+') as '+ QUOTENAME(B.DynamicAttribute) +',max('+QUOTENAME(B.DyanamicValue)+') as '+ QUOTENAME(B.DyanamicValue)
from (select distinct DynamicAttribute,DyanamicValue from #tblDynamicAttribute) as B
order by b.DynamicAttribute
select #columnsAttribute=coalesce(#columnsAttribute+',','')+QUOTENAME(B.DynamicAttribute)
from (select distinct DynamicAttribute,DyanamicValue from #tblDynamicAttribute) as B
order by b.DynamicAttribute
select #columnsValue=coalesce(#columnsValue+',','')+QUOTENAME(B.DyanamicValue)
from (select distinct DynamicAttribute,DyanamicValue from #tblDynamicAttribute) as B
order by b.DynamicAttribute
-- construct dynamic SQL
SET #sql ='
select x.processid,' + #Allcolumns +' from (
SELECT processid,' + #columnsAttribute+','+ #columnsValue +' FROM
(
SELECT
processid,attribute,value,dynamicattribute,DyanamicValue
FROM
#tblDynamicAttribute p
) t
PIVOT(
max(attribute)
FOR dynamicattribute IN ('+ #columnsAttribute +')
) AS pivot_table
PIVOT(
max(value)
FOR DyanamicValue IN ('+ #columnsValue +')
) AS pivot_table1
) x group by processid;';
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
drop table #tblDynamicAttribute
Sample Output

Generate list number

I have a table look like this:
+-------+--------+--------+
| Grp | Party | Member |
+-------+--------+--------+
| FC | Party1 | Tom |
| FC | Party1 | Alice |
| FC | Party2 | John |
| FC | Party3 | Mary |
| GC | Party2 | Anna |
| GC | Party4 | Alex |
| GC | Party5 | Diana |
+-------+--------+--------+
I want to transform the table into list like this:
+-------+--------+
| ID | Text |
+-------+--------+
| 1 | FC |
| 1.1 | Party1 |
| 1.1.1 | Tom |
| 1.1.2 | Alice |
| 1.2 | Party2 |
| 1.2.1 | John |
| 1.3 | Party3 |
| 1.3.1 | Mary |
| 2 | GC |
| 2.1 | Party2 |
| 2.1.1 | Anna |
| 2.2 | Party4 |
| 2.2.1 | Alex |
| 2.3 | Party5 |
| 2.3.1 | Diana |
+-------+--------+
I have tried rollup with row_number, but the result still far away what I want
;with ctx as (
select * from #test
group by rollup(Grp, Party, Member)
)
select row_number() over (partition by grp order by grp, party, member) as g,
row_number() over (partition by grp, party order by grp, party, member) as p,
row_number() over (partition by grp, party, member order by grp, party, member) as m,
grp, party, member
from ctx
where grp is not null
order by grp, party, member
Thanks in advance.
EDIT
Here is the SQL to generate the table, hope this can help
declare #test table (Grp varchar(10), Party varchar(10), Member varchar(20))
insert into #test values ('FC', 'Party1', 'Tom')
insert into #test values ('FC', 'Party1', 'Alice')
insert into #test values ('FC', 'Party2', 'John')
insert into #test values ('FC', 'Party3', 'Mary')
insert into #test values ('GC', 'Party2', 'Anna')
insert into #test values ('GC', 'Party4', 'Alex')
insert into #test values ('GC', 'Party5', 'Diana')
This uses DENSE_RANK to get the correct numbering for the ID. Then CROSS APPLY to unpivot the data and mark which row is for the Grp, Party, or Member. Finally use WHERE to filter only those rows you need:
WITH CteUnpivot AS(
SELECT *
FROM (
SELECT *,
rnGrp = DENSE_RANK() OVER(ORDER BY Grp),
rnParty = DENSE_RANK() OVER(PARTITION BY Grp ORDER BY Party),
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY Member)
FROM test
) t
CROSS APPLY(VALUES
('Grp', Grp),
('Party', Party),
('Member', Member)
) x (col, [Text])
)
SELECT
ID = CASE
WHEN col = 'Grp' THEN CAST(rnGrp AS VARCHAR(3))
WHEN col = 'Party' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3))
WHEN col = 'Member' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3)) + '.' + CAST(rnMember AS VARCHAR(3))
END,
[Text]
FROM CteUnpivot
WHERE
(col = 'Grp' AND rnParty = 1 AND rnMember = 1)
OR (col = 'Party' AND rnMember = 1)
OR (col = 'Member')
ORDER BY rnGrp, rnParty, rnMember;
ONLINE DEMO
If order does not matter for Member, replace rnMember with:
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY (SELECT NULL))
ONLINE DEMO
Here is one way
;WITH cte
AS (SELECT Dense_rank()OVER (ORDER BY grp) AS g,
Dense_rank()OVER (partition BY grp ORDER BY party) AS p,
Row_number()OVER (partition BY grp, party ORDER BY member) AS m,
grp,
party,
member
FROM #test
WHERE grp IS NOT NULL)
SELECT DISTINCT grp,
Cast(g AS VARCHAR(10)) AS [Text]
FROM cte
UNION ALL
SELECT DISTINCT party,
Concat(g, '.', p)
FROM cte
UNION ALL
SELECT member,
Concat(g, '.', p, '.', m)
FROM cte
ORDER BY [Text]
You need to use DENSE_RANK for parents to generate hierarchy numbers properly. If you have duplicates in Member as well then change the ROW_NUMBER to DENSE_RANK inside CTE and add distinct to the final select query
Note : If you are using anything less than SQL SERVER 2012 then use + operator for concatenation instead of CONCAT
Frankly, I would not do this at the database level. Instead I would ensure the output is sorted by {Grp, Party, Member} and then assign "Id" values in a single pass through as you display the data.
However, if you're determined to do this in the database server for whatever reason, you could use the dense_rank() function to get each individual id:
;with cte as (
select dense_rank() over (order by Grp) id0,
dense_rank() over (partition by Grp order by Party) id1,
dense_rank() over (partition by Grp, Party order by Member) id2,
Grp, Party, Member
from Table1
), grps as (select distinct id0, Grp from cte),
parties as (select distinct id0, id1, Party from cte),
members as (select distinct id0, id1, id2, Member from cte),
[list] as (
select cast(id0 as varchar(50)) as id, Grp as [Text] from grps
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)), Party from parties
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)) + '.' + cast(id2 as varchar(50)), Member from members
)
select id, [Text]
from [list]
order by id
This option doesn't use DENSE_RANK() but ROW_NUMBER() but is essentially similar to other answers posted.
With grps As (
Select Grp, GrpNo = Row_Number() Over (Order By Grp)
From (Select Distinct Grp From MyTable) As MyTable),
parties As (
Select MyTable.Grp, MyTable.Party, grps.GrpNo, PrtyNo = Row_Number() Over (Partition By MyTable.Grp Order By MyTable.Party)
From (Select Distinct Grp, Party From MyTable) As MyTable
Join grps On MyTable.Grp = grps.Grp),
members As (
Select MyTable.Grp, MyTable.Party, MyTable.Member,
parties.GrpNo, parties.PrtyNo, MbrNo = Row_Number() Over (Partition By MyTable.Grp, MyTable.Party Order By #groups.Member)
From MyTable
Join parties On MyTable.Grp = parties.Grp And MyTable.Party = parties.Party)
Select ID = Convert(char(5), GrpNo),
[Text] = Grp
From grps
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo),
[Text] = Party
From parties
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo) + '.' + Convert(char(1), MbrNo),
[Text] = Member
From members;

Dynamic pivot with similar column names

I'm looking for a way to pivot a varying amount of rows to columns in sql server 2008 R2. I created the data column RANK in the query because, ultimately I want the pivoted column names to be labeled the value in the RANK column. Then, if somehow I can STUFF the other 3 field values together into one field I would be able to iterate over the row in my backend lang and split the field results appropriately.
Here is the current data set:
And I would like the end result of the pivot to produce a result like this:
I haven't found anything about being able to pivot in this "dynamic" way. Any help would be much appreciated.
As I mentioned above, you need to distinguish each of your Rank values. You've said that this is a calculated value that you can add a number to the end of each one. Once you've added that number, then you still need to pivot it.
The easiest way to first see this would be to write a hard-coded version of the query first.
Sample Data:
create table yourdata
(
id int,
code varchar(50),
created datetime,
[rank] varchar(50)
);
insert into yourdata
select 285856, 'J7609', '2015-01-19', 'Principle' union all
select 285856, 'J7613', '2015-01-19', 'Other' union all
select 285856, 'J0456', '2015-01-19', 'Other' union all
select 285856, 'J0694', '2015-01-19', 'Other' union all
select 285856, 'J1885', '2015-01-19', 'Other' union all
select 285856, 'J2060', '2015-01-19', 'Other' union all
select 285856, 'J2930', '2015-01-19', 'Other';
Static Query:
select Principle_1, Other_1,
Other_2, Other_3, Other_4,
Other_5, Other_6
from
(
-- using row_number to get unique id for each rank
select
data = cast(id as varchar(10)) +' | '+ code +' | '+ convert(varchar(10), created, 112),
[rank] = [rank] + '_' +cast(row_number() over(partition by id, [rank]
order by id) as varchar(10))
from yourdata
) d
pivot
(
max(data)
for [rank] in (Principle_1, Other_1, Other_2, Other_3, Other_4,
Other_5, Other_6)
) p;
Now to do this dynamic, you will create a sql string with the column names and then execute that string:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME([rank] + '_' +cast(rn as varchar(10)))
from
(
select [rank],
rn = row_number() over(partition by id, [rank]
order by id)
from yourdata
) d
group by [rank], rn
order by rn, [rank] desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select
data = cast(id as varchar(10)) +'' | ''+ code +'' | ''+ convert(varchar(10), created, 112),
[rank] = [rank] + ''_'' +cast(row_number() over(partition by id, [rank]
order by id) as varchar(10))
from yourdata
) x
pivot
(
max(data)
for [rank] in (' + #cols + ')
) p '
exec sp_executesql #query;
This gets you a result:
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+
| Principle_1 | Other_1 | Other_2 | Other_3 | Other_4 | Other_5 | Other_6 |
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+
| 285856 | J7609 | 20150119 | 285856 | J7613 | 20150119 | 285856 | J0456 | 20150119 | 285856 | J0694 | 20150119 | 285856 | J1885 | 20150119 | 285856 | J2060 | 20150119 | 285856 | J2930 | 20150119 |
+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+

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

Display multiple rows and column values into a single row, multiple column values

I have to show multiple incomes, type of income and employer name values for a single individual in a single row. So, if 'A' has three different incomes from three different sources,
id | Name | Employer | IncomeType | Amount
123 | XYZ | ABC.Inc | EarningsformJob | $200.00
123 | XYZ | Self | Self Employment | $300.00
123 | XYZ. | ChildSupport| Support | $500.00
I need to show them as
id | Name | Employer1 | Incometype1| Amount1 | Employer2 | incometype2 | Amount2| Employer3 | Incometype3| Amount3.....
123 |XYZ | ABC.Inc |EarningsformJob | $200.00|Self | Self Employment | $300.00|ChildSupport| Support | $500.00.....
I need both 'fixed number of columns' (where we know how many times employer, incometype and amount colums are going to repeat)logic and 'dynamic display of columns' ( unknown number of times these columns are going to repeat)
Thanks.
Since you are using SQL Server there are several ways that you can transpose the rows of data into columns.
Aggregate Function / CASE: You can use an aggregate function with a CASE expression along with row_number(). This version would require that you have a known number of values to become columns:
select id,
name,
max(case when rn = 1 then employer end) employer1,
max(case when rn = 1 then IncomeType end) IncomeType1,
max(case when rn = 1 then Amount end) Amount1,
max(case when rn = 2 then employer end) employer2,
max(case when rn = 2 then IncomeType end) IncomeType2,
max(case when rn = 2 then Amount end) Amount2,
max(case when rn = 3 then employer end) employer3,
max(case when rn = 3 then IncomeType end) IncomeType3,
max(case when rn = 3 then Amount end) Amount3
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) src
group by id, name;
See SQL Fiddle with Demo.
PIVOT/UNPIVOT: You could use the UNPIVOT and PIVOT functions to get the result. The UNPIVOT converts your multiple columns of Employer, IncomeType and Amount into multiples rows before applying the pivot. You did not specific what version of SQL Server, assuming you have a known number of values then you could use the following in SQL Server 2005+ which uses CROSS APPLY with UNION ALL to unpivot:
select id, name,
employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select 'employer', employer union all
select 'incometype', incometype union all
select 'amount', cast(amount as varchar(50))
) c (col, value)
) src
pivot
(
max(value)
for col in (employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3)
) piv;
See SQL Fiddle with Demo.
Dynamic Version: Lastly, if you have an unknown number of values then you will need to use dynamic SQL to generate the result.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by id order by employer) rn
from yourtable
) d
cross apply
(
select 'employer', 1 union all
select 'incometype', 2 union all
select 'amount', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select ''employer'', employer union all
select ''incometype'', incometype union all
select ''amount'', cast(amount as varchar(50))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions give a result:
| ID | NAME | EMPLOYER1 | INCOMETYPE1 | AMOUNT1 | EMPLOYER2 | INCOMETYPE2 | AMOUNT2 | EMPLOYER3 | INCOMETYPE3 | AMOUNT3 |
-------------------------------------------------------------------------------------------------------------------------------------
| 123 | XYZ | ABC.Inc | EarningsformJob | 200 | ChildSupport | Support | 500 | Self | Self Employment | 300 |