Reputation: 71187
I'm trying to pivot without aggregation, and running into a bit of a wall.
Here's the sample T-SQL I'm trying to get to work:
declare @optionalFields table (ParentId int, Name nvarchar(50), Value nvarchar(50));
insert into @optionalFields values (1, 'Field1', 'Foo');
insert into @optionalFields values (1, 'Field2', 'Bar');
insert into @optionalFields values (1, 'Field3', '42');
insert into @optionalFields values (2, 'Field1', 'Bar');
insert into @optionalFields values (2, 'Field2', 'Foo');
insert into @optionalFields values (2, 'Field3', '24');
declare @data table (Id int, Name nvarchar(50));
insert into @data values (1, 'Test record 1');
insert into @data values (2, 'Test record 2');
declare @joined table (Id int, Name nvarchar(50), OptionalFieldName nvarchar(50), OptionalFieldValue nvarchar(50));
insert into @joined
select
data.Id
,data.Name
,opt.Name
,opt.Value
from @data data
inner join @optionalFields opt on data.Id = opt.ParentId
declare @cols as nvarchar(max) =
stuff((select distinct ',' + quotename(OptionalFieldName) from @joined for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '');
select * into #tmp from @joined
-- just to see that it's returning the expected values (it does)
select
Id
,Name
,OptionalFieldName
,OptionalFieldValue
,row_number() over (partition by Id order by Id) RN
from #tmp -- this is the FROM clause in the below dynamic-sql query
declare @query as nvarchar(max) = '
select Id, Name, ' + @cols + '
from (select Id, Name, OptionalFieldName, OptionalFieldValue, row_number() over (partition by Id order by Id) RN from #tmp) src
pivot (max(OptionalFieldName) for RN in (' + @cols + ')) pvt';
execute(@query);
drop table #tmp;
SSMS is giving me 2 errors:
- Msg 8114, Level 16, State 1, Line 4 Error converting data type nvarchar to bigint.
- Msg 473, Level 16, State 1, Line 4 The incorrect value "Field1" is supplied in the PIVOT operator.
The "debug" select statement is returning this:
The article (link above) seemed very promising, however I can't seem to be able to get it to work. What am I doing wrong? Or is this article outright wrong and what I'm trying to do is impossble?
I've seen a number of similar SO questions, but either they involved all-numeric fields that could "just work" with aggregation, or they involved known columns that could be implemented as simple joins - I don't know what OptionalFieldName
values I'm going to be selecting, and the OptionalFieldValue
values are strings that simply can't be aggregated, at least AFAIK.
Upvotes: 4
Views: 169
Reputation: 15987
Hope I understand what you need right:
declare @optionalFields table (ParentId int, Name nvarchar(50), Value nvarchar(50));
insert into @optionalFields values (1, 'Field1', 'Foo');
insert into @optionalFields values (1, 'Field2', 'Bar');
insert into @optionalFields values (1, 'Field3', '42');
insert into @optionalFields values (2, 'Field1', 'Bar');
insert into @optionalFields values (2, 'Field2', 'Foo');
insert into @optionalFields values (2, 'Field3', '24');
declare @data table (Id int, Name nvarchar(50));
insert into @data values (1, 'Test record 1');
insert into @data values (2, 'Test record 2');
select
data.Id
,data.Name
,opt.Name as Name1
,opt.Value into #tmp
from @data data
inner join @optionalFields opt on data.Id = opt.ParentId
declare @cols as nvarchar(max) =
stuff((select distinct ',' + quotename(Name1) from #tmp for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '');
DECLARE @cols1 as nvarchar(max) =
stuff((select distinct +',MAX(CASE WHEN (pvt1.'+quotename(Name1) +' = ros.RN AND pvt1.id = ros.id) THEN ros.Value ELSE NULL END) as '+quotename(Name1) from #tmp for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '');
declare @query as nvarchar(max) = '
;WITH cte AS(
SELECT * FROM #tmp
),
ros AS (
SELECT ROW_NUMBER() OVER (partition by Id order by Id) AS [RN],id,Value
FROM cte),
pvt1 AS (
select *
from (select Id, Name, Name1, row_number() over (partition by Id order by Id) RN
from cte) src
pivot (max(RN) for Name1 in ('+@cols+')) pvt)
SELECT pvt1.ID,
pvt1.Name,
'+@cols1+'
FROM pvt1
CROSS JOIN ros
GROUP BY pvt1.ID,
pvt1.Name'
execute(@query);
drop table #tmp
Result:
ID Name Field1 Field2 Field3
1 Test record 1 Foo Bar 42
2 Test record 2 Bar Foo 24
And if you add more Fields like insert into @optionalFields values (2, 'Field4', '15');
, you will get:
ID Name Field1 Field2 Field3 Field4
1 Test record 1 Foo Bar 42 NULL
2 Test record 2 Bar Foo 24 15
Upvotes: 0
Reputation: 247720
I'm a bit confused on why you are trying to trick this using row_number()
. Even though you have string values, you can still aggregate it - you just need to use max
or min
to get the result.
I'd always recommend trying to write your query with hard-coded values first, especially when using PIVOT before even attempting to use dynamic SQL. I'm unsure why you can't just write the query this way:
select Id, Name, Field1, Field2, Field3
from
(
select
Id
,Name
,OptionalFieldName
,OptionalFieldValue
from #tmp
) d
pivot
(
max(OptionalFieldValue)
for OptionalFieldName in (Field1, Field2, Field3)
) piv;
Then if you really need dynamic SQL, you would just write it:
declare @optionalFields table (ParentId int, Name nvarchar(50), Value nvarchar(50));
insert into @optionalFields values (1, 'Field1', 'Foo');
insert into @optionalFields values (1, 'Field2', 'Bar');
insert into @optionalFields values (1, 'Field3', '42');
insert into @optionalFields values (2, 'Field1', 'Bar');
insert into @optionalFields values (2, 'Field2', 'Foo');
insert into @optionalFields values (2, 'Field3', '24');
declare @data table (Id int, Name nvarchar(50));
insert into @data values (1, 'Test record 1');
insert into @data values (2, 'Test record 2');
declare @joined table (Id int, Name nvarchar(50), OptionalFieldName nvarchar(50), OptionalFieldValue nvarchar(50));
insert into @joined
select
data.Id
,data.Name
,opt.Name
,opt.Value
from @data data
inner join @optionalFields opt on data.Id = opt.ParentId
declare @cols as nvarchar(max);
set @cols = stuff((select distinct ',' + quotename(OptionalFieldName)
from @joined
for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '');
select * into #tmp from @joined
DECLARE @query AS NVARCHAR(MAX)
set @query = 'SELECT Id, Name,' + @cols + '
from
(
select Id
,Name
,OptionalFieldName
,OptionalFieldValue
from #tmp
) x
pivot
(
max(OptionalFieldValue)
for OptionalFieldName in (' + @cols + ')
) p '
execute(@query);
See Demo. Both versions appear to give the result that you have requested.
Upvotes: 5