SQL Server - Complex Dynamic Pivot multiple columns - sql

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

Related

How to filter a pivoted query to bring the records that has at least one count in a specific date range

Now, I have a pivoted query that will bring a set of events that took place on all the towns registered in a table and a date range.
This is my query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
CREATE TABLE #tempDates
(
[FechaSt] Datetime
);
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
INSERT INTO #tempDates
SELECT
[Result] AS [Fecha]
FROM
(
SELECT
DISTINCT CAST([Result] AS DATE) [Result] FROM
(
SELECT
TOP (DATEDIFF(day, #f1, #f2) + 1)
[Result] = DATEADD(day, ROW_NUMBER() OVER(ORDER BY S) - 1, #f1)
FROM cte3
) AS res
) AS [listaFechas];
select #cols =
STUFF
(
(
SELECT
DISTINCT ',' + QUOTENAME(convert(CHAR(10), d.FechaSt, 120))
FROM #tempDates d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''
)
SET #query =
'
SELECT
IdMunicipio,
Ciudad,
Region,
Base,
' + #cols + '
FROM
(
SELECT
[Jc].[ID],
[Jc].[IdMunicipio],
[Wc].[Ciudad],
[Wr].[Region],
IIF
(
[Mk].[EsBase] = 1,
''<i class="fas fa-star fa-fw textoAmarilloSm"></i>'',
''''
) AS [Base],
CAST([Jc].[Fecha] AS date) AS [FechaJc]
FROM [dbo].[Jornadas_Cronograma] [Jc]
INNER JOIN
[dbo].[L_MunicipiosCapacitacion] [Mk]
ON [Jc].[IdMunicipio] = [Mk].[ID]
INNER JOIN
[dbo].[L_RegionesMundo] [Wr]
ON [Mk].[RegionId] = [Wr].[RegionID]
INNER JOIN
[dbo].[L_CiudadesMundo] [Wc]
ON [Mk].[CiudadId] = [Wc].[CityID]
) X
PIVOT
(
COUNT([ID])
FOR [FechaJc] IN (' + #cols + ')
) pvt
ORDER BY Ciudad ASC
'
execute(#query);
So, according to the specified date range (#f1 and #f2), I'm creating a temp table that stores all the dates within that range, to stuff that data afterwards to achive the pivot.
This is a sample of the data returned:
IdMunicipio | Ciudad | ... | 2022-10-02 | 2022-10-03 | 2022-10-04
1 | Bogotá | ... | 0 | 1 | 3
2 | Cali | ... | 0 | 12 | 3
3 | Medell.| ... | 0 | 0 | 0
4 | Buga | ... | 0 | 10 | 0
5 | Chía | ... | 0 | 0 | 0
6 | Cota | ... | 0 | 0 | 0
7 | Cajicá | ... | 0 | 0 | 2
What I need is to filter this query, so that it will only bring the towns that had at least 1 event within the date range, for this example it will be:
IdMunicipio | Ciudad | ... | 2022-10-02 | 2022-10-03 | 2022-10-04
1 | Bogotá | ... | 0 | 1 | 3
2 | Cali | ... | 0 | 12 | 3
4 | Buga | ... | 0 | 10 | 0
7 | Cajicá | ... | 0 | 0 | 2
What I have tryed:
DROP TABLE #tempDates
DECLARE #f1 datetime = '2022-10-02';
DECLARE #f2 datetime = '2022-10-08';
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
CREATE TABLE #tempDates
(
[FechaSt] Datetime
);
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
INSERT INTO #tempDates
SELECT
[Result] AS [Fecha]
FROM
(
SELECT
DISTINCT CAST([Result] AS DATE) [Result] FROM
(
SELECT
TOP (DATEDIFF(day, #f1, #f2) + 1)
[Result] = DATEADD(day, ROW_NUMBER() OVER(ORDER BY S) - 1, #f1)
FROM cte3
) AS res
) AS [listaFechas];
select #cols =
STUFF
(
(
SELECT
DISTINCT ',' + QUOTENAME(convert(CHAR(10), d.FechaSt, 120))
FROM #tempDates d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,''
)
SET #query =
'
SELECT
IdMunicipio,
Ciudad,
Region,
Base,
' + #cols + '
FROM
(
SELECT
[Jc].[ID],
[Jc].[IdMunicipio],
[Wc].[Ciudad],
[Wr].[Region],
IIF
(
[Mk].[EsBase] = 1,
''<i class="fas fa-star fa-fw textoAmarilloSm"></i>'',
''''
) AS [Base],
CAST([Jc].[Fecha] AS date) AS [FechaJc]
FROM [dbo].[Jornadas_Cronograma] [Jc]
INNER JOIN
[dbo].[L_MunicipiosCapacitacion] [Mk]
ON [Jc].[IdMunicipio] = [Mk].[ID]
INNER JOIN
[dbo].[L_RegionesMundo] [Wr]
ON [Mk].[RegionId] = [Wr].[RegionID]
INNER JOIN
[dbo].[L_CiudadesMundo] [Wc]
ON [Mk].[CiudadId] = [Wc].[CityID]
WHERE
(
(
SELECT
COUNT(*)
FROM [dbo].[Jornadas_Cronograma] [Jb]
WHERE
(CAST([Jb].[Fecha] as date) BETWEEN ' + CAST(#f1 as date) + ' AND ' + CAST(#f2 as date) + ')
)
) > 0;
) X
PIVOT
(
COUNT([ID])
FOR [FechaJc] IN (' + #cols + ')
) pvt
ORDER BY Ciudad ASC
'
execute(#query);
But this will break into an error:
Msg 402, Level 16, State 1, Line 92
The data types nvarchar(max) and date are incompatible in the add operator.
This is breaking in the line where I declare the WHERE Clause:
(CAST([Jb].[Fecha] as date) BETWEEN ' + CAST(#f1 as date) + ' AND ' + CAST(#f2 as date) + ')
I've thinked that maybe if a somehow SUM the results for the dates columns, I will be able to filter where that sum is > 0, but I'm not being able to solve the syntax.

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

How to combine multiple SQL rows into columns dynamically

I have a table with
+-------+-------+-----------------+
| P1_ID | P2_ID | Relationship_ID |
+-------+-------+-----------------+
| 1 | 21 | 3 |
| 1 | 32 | 3 |
| 2 | 45 | 2 |
| 2 | 65 | 1 |
| 3 | 98 | 3 |
| 3 | 94 | 4 |
+-------+-------+-----------------+
I want the final table to look like:
+-------+--------+--------+------+------+
| P1_ID | P2_ID1 | P2_ID2 | RID1 | RID2 |
+-------+--------+--------+------+------+
| 1 | 21 | 32 | 3 | 3 |
| 2 | 45 | 65 | 2 | 1 |
| 3 | 98 | 94 | 3 | 4 |
+-------+--------+--------+------+------+
I am not sure which direction to go with this. I am trying to use a pivot but I can not seem to make it work. Maybe I am doing it wrong.
You can use conditional aggregation for this. This isn't exactly what you stated for output because the ordering of your data is a little funky. But this should point you in the right direction.
declare #Something table
(
P1_ID int
, P2_ID int
, Realationship_ID int
)
insert #Something values
(1,21,3)
, (1,32,3)
, (2,45,2)
, (2,65,1)
, (3,98,3)
, (3,94,4)
select P1_ID
, P2_ID1 = MAX(Case when RowNum = 1 then P2_ID end)
, P2_ID2 = max(case when RowNum = 2 then P2_ID end)
, RID1 = MAX(Case when RowNum = 1 then Realationship_ID end)
, RID2 = max(case when RowNum = 2 then Realationship_ID end)
from
(
select *
, RowNum = ROW_NUMBER() over(partition by s.P1_ID order by Realationship_ID)
from #Something s
) x
group by x.P1_ID
--EDIT--
Here is a fully dynamic solution for this. I switched to using a temp table because a table variable would be out of scope for dynamic sql. Obviously in your situation you would be using a persistent table. This will order the output by P1_ID and the columns within each row by Realationship_ID.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
P1_ID int
, P2_ID int
, Realationship_ID int
)
insert #Something values
(1,21,3)
, (1,32,3)
, (2,45,2)
, (2,65,1)
, (3,98,3)
, (3,94,4)
;
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by P1_ID order by P1_ID';
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *
, RowNum = ROW_NUMBER() over(partition by s.P1_ID order by Realationship_ID)
from #Something s
)
select P1_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 cross join E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then P2_ID end) as P2_ID' + CAST(N as varchar(6)) + CHAR(10) +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Realationship_ID end) as RID' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from #Something
group by P1_ID
order by COUNT(*) desc
)
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute

Pivot table to turn rows into columns

I currently run the query
SELECT [PriceAttributeID]
,[PriceID]
,[AttributeID]
,[PriceAttributeComparator]
,[PriceAttributeMin]
,[PriceAttributeMax]
FROM [PriceAttribute]
Which gives the output
1 2 1 1 S NULL
2 3 1 1 M NULL
3 4 1 1 L NULL
4 5 1 1 L NULL
5 5 2 1 Black NULL
I would like to get the output (where _Comp, _Min and _Max relate to PriceAttributeComparator, PriceAttributeMin and PriceAttributeMax)
PriceID 1_Comp 1_Min 1_Max 2_Comp 2_Min 2_Max
2 1 S NULL NULL NULL NULL
3 1 M NULL NULL NULL NULL
4 1 L NULL NULL NULL NULL
5 1 L NULL 1 Black NULL
The same query would also be expected to have 1_ and 2_ prefixes as 4_, 5_, 19_ and 32_ or any other indeterminate number of ID's based on what is in the table at the time.
I have attempted a PIVOT table, but i am new to them and haven't the first clue on how to create what it is i am looking to do.
Part of the problem you are probably having with the PIVOT function is due to the fact you have multiple columns that you want to apply the function to. If you want to use the PIVOT function, then I would suggest first unpivoting the columns PriceAttributeComparator, PriceAttributeMin and PriceAttributeMax. When you unpivot the data you will no longer have multiple columns, you will have multiple rows, then you can apply the pivot to all of the appropriate values.
You did not specify what version of SQL Server you are using but you can use CROSS APPLY with a UNION ALL to unpivot the columns:
select priceid,
col = cast(attributeid as varchar(10))+'_'+ col,
value
from
(
select PriceID,
AttributeID,
comp = cast(PriceAttributeComparator as varchar(10)),
[min] = cast(PriceAttributeMin as varchar(10)),
[max] = cast(PriceAttributeMax as varchar(10))
from PriceAttribute
) d
cross apply
(
select 'comp', comp union all
select 'min', [min] union all
select 'max', [max]
) c (col, value)
See Demo. This process will convert your data into the following format:
| PRICEID | COL | VALUE |
-----------------------------
| 2 | 1_comp | 1 |
| 2 | 1_min | S |
| 2 | 1_max | (null) |
| 3 | 1_comp | 1 |
| 3 | 1_min | M |
| 3 | 1_max | (null) |
Once the data is in multiple rows, then you can apply the PIVOT function to the values in col:
select priceid,
[1_comp], [1_min], [1_max], [2_comp], [2_min], [2_max]
from
(
select priceid,
col = cast(attributeid as varchar(10))+'_'+ col,
value
from
(
select PriceID,
AttributeID,
comp = cast(PriceAttributeComparator as varchar(10)),
[min] = cast(PriceAttributeMin as varchar(10)),
[max] = cast(PriceAttributeMax as varchar(10))
from PriceAttribute
) d
cross apply
(
select 'comp', comp union all
select 'min', [min] union all
select 'max', [max]
) c (col, value)
) src
pivot
(
max(value)
for col in ([1_comp], [1_min], [1_max], [2_comp], [2_min], [2_max])
) piv;
See SQL Fiddle with Demo.
The above versions work great if you have a known number of values but if the values are unknown, 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 ',' + QUOTENAME(cast(attributeid as varchar(10))+'_'+ col)
from
(
select distinct attributeid
from priceattribute
) d
cross apply
(
select 'comp', 1 union all
select 'min', 2 union all
select 'max', 3
) c (col, so)
group by attributeid, col, so
order by attributeid, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT priceid, ' + #cols + '
from
(
select priceid,
col = cast(attributeid as varchar(10))+''_''+ col,
value
from
(
select PriceID,
AttributeID,
comp = cast(PriceAttributeComparator as varchar(10)),
[min] = cast(PriceAttributeMin as varchar(10)),
[max] = cast(PriceAttributeMax as varchar(10))
from PriceAttribute
) d
cross apply
(
select ''comp'', comp union all
select ''min'', [min] union all
select ''max'', [max]
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. These solutions will give a result:
| PRICEID | 1_COMP | 1_MIN | 1_MAX | 2_COMP | 2_MIN | 2_MAX |
----------------------------------------------------------------
| 2 | 1 | S | (null) | (null) | (null) | (null) |
| 3 | 1 | M | (null) | (null) | (null) | (null) |
| 4 | 1 | L | (null) | (null) | (null) | (null) |
| 5 | 1 | L | (null) | 1 | Black | (null) |
It might be simplest to do this using conditional aggregation rather than pivot:
SELECT PriceID,
max(case when AttributeID = 1 then PriceAttributeComparator end) as comp_1,
max(case when AttributeID = 1 then PriceAttributeMin end) as min_1,
max(case when AttributeID = 1 then PriceAttributeMax end) as max_1,
max(case when AttributeID = 2 then PriceAttributeComparator end) as comp_2,
max(case when AttributeID = 2 then PriceAttributeMin end) as min_2,
max(case when AttributeID = 2 then PriceAttributeMax end) as max_2
FROM PriceAttribute pa
group by PriceId;

how to convert row to column if unknown number of columns SQL server

I have data like this:
MaGiangVienID | SoTiet1 | SoTiet2 | DateID
79000G07.000206 | 60 | 60.00 | t11
79000G07.000206 | 54 | 54.00 | t12
I want to my result like this:
MaGiangVienID | SoTiet1_t11 | SoTiet2_t11 | SoTiet1_t12 | SoTiet2_t12
79000G07.000206 | 60 | 60.00 | 54 | 54.00
I don't know how many columns because MaGiangVienID have a lot of DateID
Please help me!!Thanks a lot.
For this type of data transformation you need to apply both the UNPIVOT and PIVOT functions. The UNPIVOT takes the data from the multiple columns and places it into rows and then the PIVOT takes the rows and converts it back to columns.
To perform the UNPIVOT all of the values that you convert to rows must be the same datatype so conversion might be needed.
Unpivot:
select MaGiangVienID,
value,
col +'_'+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in (SoTiet1, SoTiet2)
) unpiv
See SQL Fiddle with Demo. The result of the unpivot is:
| MAGIANGVIENID | VALUE | COL |
-----------------------------------------
| 79000G07.000206 | 60 | SoTiet1_t11 |
| 79000G07.000206 | 60.00 | SoTiet2_t11 |
| 79000G07.000206 | 54 | SoTiet1_t12 |
| 79000G07.000206 | 54.00 | SoTiet2_t12 |
As you see the UNPIVOT generates the new column names with the DateId appended to the end of it. Now you apply the PIVOT function.
Static PIVOT:
select MaGiangVienID, SoTiet1_t11, SoTiet2_t11, SoTiet1_t12, SoTiet2_t12
from
(
select MaGiangVienID,
value,
col +'_'+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in (SoTiet1, SoTiet2)
) unpiv
) src
pivot
(
max(value)
for col in (SoTiet1_t11, SoTiet2_t11, SoTiet1_t12, SoTiet2_t12)
) piv
See SQL Fiddle with Demo
Now the above version works great if you have a known number of DateId values but you stated that you don't so you will want to implement this same query using dynamic sql.
Dynamic PIVOT:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('MaGiangVienID', 'DateID')
for xml path('')), 1, 1, '')
select #colsPivot = STUFF((SELECT DISTINCT ','
+ quotename(c.name + '_'+t.DateId)
from yourtable t
cross apply sys.columns as C
where C.object_id = object_id('yourtable') and
C.name not in ('MaGiangVienID', 'DateID')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select MaGiangVienID, '+#colsPivot+'
from
(
select MaGiangVienID, value, col +''_''+DateId col
from
(
select MaGiangVienID,
cast(SoTiet1 as varchar(50)) SoTiet1,
cast(SoTiet2 as varchar(50)) SoTiet2,
DateID
from yourtable
) src
unpivot
(
value
for col in ('+#colsUnpivot+')
) unpiv
) src
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
See SQL Fiddle with Demo
Both versions produce the same result:
The result is:
| MAGIANGVIENID | SOTIET1_T11 | SOTIET2_T11 | SOTIET1_T12 | SOTIET2_T12 |
---------------------------------------------------------------------------
| 79000G07.000206 | 60 | 60.00 | 54 | 54.00 |
If you do not have access to the UNPIVOT/PIVOT functions then you can replicate the query. The UNPIVOT function can be replicated using a UNION ALL and the PIVOT can be produced using a CASE statement with an aggregate function:
select MaGiangVienID,
max(case when col = 'SoTiet1_t11' then value end) SoTiet1_t11,
max(case when col = 'SoTiet2_t11' then value end) SoTiet2_t11,
max(case when col = 'SoTiet1_t12' then value end) SoTiet1_t12,
max(case when col = 'SoTiet2_t12' then value end) SoTiet2_t12
from
(
select MaGiangVienID, 'SoTiet1_t11' col, cast(SoTiet1 as varchar(50)) value
from yourtable
where DateID = 't11'
union all
select MaGiangVienID, 'SoTiet2_t11' col, cast(SoTiet2 as varchar(50)) value
from yourtable
where DateID = 't11'
union all
select MaGiangVienID, 'SoTiet1_t12' col, cast(SoTiet1 as varchar(50)) value
from yourtable
where DateID = 't12'
union all
select MaGiangVienID, 'SoTiet2_t12' col, cast(SoTiet2 as varchar(50)) value
from yourtable
where DateID = 't12'
) src
group by MaGiangVienID
See SQL Fiddle with Demo
All versions will produce identical results.