February 26, 2010 at 8:00 am
Hello, not sure if this can be done in T-SQL, but is there way to pivot data to one column? ie:
Here is data from a SQL result for 2 enrollees:
EnrolleeID, LanguageID, LanguageName
20, 1, English
20, 2, Spanish
20, 3, Portugese
5, 1, English
5, 2, Spanish
I would like the language name to be in one column in my select statement so the results look something like:
EnrolleeID, HouseholdLanguages
20, English-Spanish-Portugese
5, English-Spanish
Can something like this be done? Also note that I know this can be done if I create a stored procedure and do some looping, but I am trying to avoid that and do it in a select statement.
Thanks,
Strick
February 26, 2010 at 10:53 am
try a CTE
CREATE TABLE [dbo].[test](
[EnrolleeID] [int] NULL,
[LanguageID] [int] NULL,
[LanguageName] [varchar](20) NULL
)
GO
INSERT INTO [dbo].[test]([EnrolleeID], [LanguageID], [LanguageName])
SELECT 20, 1, N'English' UNION ALL
SELECT 20, 2, N'Spanish' UNION ALL
SELECT 20, 3, N'Portugese' UNION ALL
SELECT 5, 1, N'English' UNION ALL
SELECT 5, 2, N'Spanish'
go
;with cte_1 (EnrolleeID,LanguageName,languageID)
as (select EnrolleeID, cast(LanguageName as varchar(256)), languageID
from test where languageID = 1
union all
select c.EnrolleeID,
cast(c.LanguageName + Case when t.LanguageName is not null then '-' + t.LanguageName else '' end as varchar(256)),
t.languageID
from test t inner join cte_1 c
on c.EnrolleeID = t.EnrolleeID
and c.LanguageID = t.LanguageID - 1
where t.languageID <> 1)
select EnrolleeID, max(LanguageName) LanguageName
from cte_1 CurrRow
group by EnrolleeID
For better, quicker answers, click on the following...
http://www.sqlservercentral.com/articles/Best+Practices/61537/
For better answers on performance questions, click on the following...
http://www.sqlservercentral.com/articles/SQLServerCentral/66909/
February 26, 2010 at 11:17 am
A recursive cte will cause performance issues on larger data sets.
A common way to resolve this issue is the usage of FOR XML PATH.
Something like
SELECT [EnrolleeID],
STUFF(
(SELECT '-' + LanguageName
FROM #test t2
WHERE t1.[EnrolleeID]=t2.[EnrolleeID]
FOR XML PATH('')
),
1,
1,''
) AS t
FROM #test t1
GROUP BY [EnrolleeID]
February 26, 2010 at 8:37 pm
lmu92 (2/26/2010)
A recursive cte will cause performance issues on larger data sets.A common way to resolve this issue is the usage of FOR XML PATH.
Something like
SELECT [EnrolleeID],
STUFF(
(SELECT '-' + LanguageName
FROM #test t2
WHERE t1.[EnrolleeID]=t2.[EnrolleeID]
FOR XML PATH('')
),
1,
1,''
) AS t
FROM #test t1
GROUP BY [EnrolleeID]
Agreed.... and this isn't a "Pivot"... this is "concatenation".
--Jeff Moden
Change is inevitable... Change for the better is not.
February 26, 2010 at 10:31 pm
lmu92 (2/26/2010)
A recursive cte will cause performance issues on larger data sets.A common way to resolve this issue is the usage of FOR XML PATH...
Just to refine that a little, to:
Try:
DECLARE @Result
TABLE (
enrollee_id INTEGER NOT NULL,
language_id INTEGER NOT NULL,
language_name NVARCHAR(50) NOT NULL
)
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 1, N'English');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 2, N'Spanish');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 3, N'Portugese');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (5, 1, N'English');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (5, 2, N'Spanish');
SELECT R1.enrollee_id,
languages =
STUFF
(
(
SELECT N'-' + R2.language_name
FROM @Result R2
WHERE R2.enrollee_id = R1.enrollee_id
ORDER BY
R2.language_name ASC
FOR XML PATH(''), TYPE
).value('./text()[1]', 'NVARCHAR(MAX)')
, 1, 1, N'')
FROM @Result R1
GROUP BY
R1.enrollee_id
ORDER BY
R1.enrollee_id ASC;
Results:
enrollee_id languages
5 English-Spanish
20 English-Portugese-Spanish
Paul
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
February 27, 2010 at 3:18 am
I added the ORDER BY statements to my solution to get the same output like you did and compared both execution plans.
Unless I'm overlooking something I'd prefer my solution over yours:
- approx. 10-15% faster
- doesn't use a TVF (XML Read with XPATH filter)
It seems like using xml inside the STUFF function just will produce some overhead.
Test scenario: your sample code followed by my query modified to include ORDER BY and to match your table and col def.
February 27, 2010 at 4:14 am
lmu92 (2/27/2010)
Unless I'm overlooking something...
The reason for TYPE and the XQuery outside is to solve entitization issues.
Try including sample data embedded characters such as '&', '<', '>' (and so on)...you'll get oddness 🙂
The query plan improvement relates to the normal query used to solve entitization issues. Simply adding the '/text()' part improves the plan quite a bit, I wasn't comparing the finished plan with yours - sorry that wasn't clear.
Sure, there is a small cost to avoiding entitization issues, but the code isn't really finished without it.
Paul
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
February 27, 2010 at 4:22 am
Some code to illustrate my point:
DECLARE @Result
TABLE (
enrollee_id INTEGER NOT NULL,
language_id INTEGER NOT NULL,
language_name NVARCHAR(50) NOT NULL
)
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 1, N'English');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 2, N'Spanish');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (20, 3, N'Portugese');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (5, 1, N'English');
INSERT @Result (enrollee_id, language_id, language_name)
VALUES (5, 2, N'Spanish & French');
-- Me
SELECT R1.enrollee_id,
languages =
STUFF
(
(
SELECT N'>' + R2.language_name
FROM @Result R2
WHERE R2.enrollee_id = R1.enrollee_id
ORDER BY
R2.language_name ASC
FOR XML PATH(''), TYPE
).value('./text()[1]', 'NVARCHAR(MAX)')
, 1, 1, N'')
FROM @Result R1
GROUP BY
R1.enrollee_id
ORDER BY
R1.enrollee_id ASC;
-- Original, with all modifications except the one for the entitization issue
SELECT R1.enrollee_id,
languages =
STUFF
(
(
SELECT N'>' + R2.language_name
FROM @Result R2
WHERE R2.enrollee_id = R1.enrollee_id
ORDER BY
R2.language_name ASC
FOR XML PATH('')
)
, 1, 1, N'')
FROM @Result R1
GROUP BY
R1.enrollee_id
ORDER BY
R1.enrollee_id ASC;
I changed one of the sample data items to include an ampersand, and also changed the output separator from a hyphen to a greater-than sign.
Results:
My version
enrollee_id languages
5 English>Spanish & French
20 English>Portugese>Spanish
Modified version, but without entitization fix
enrollee_id languages
5 gt;English& gt;Spanish & amp; French
20 gt;English& gt;Portugese& gt;Spanish
Paul
edit: had to add spaces to the 'modified version' results, the site was converting some of the XML!
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
February 27, 2010 at 5:01 am
Ok, I see your point.
Didn't have to deal with those "special characters" in my concatenation scenarios (yet), so I wasn't aware of the side effects. Thanks for clarification. Learned something new. Again.
February 27, 2010 at 5:23 am
lmu92 (2/27/2010)
Ok, I see your point.Didn't have to deal with those "special characters" in my concatenation scenarios (yet), so I wasn't aware of the side effects. Thanks for clarification. Learned something new. Again.
Hey no worries! My apologies if my original post back there wasn't brilliantly worded - I was just adding a little 'shine' to your good solution, I wasn't intending to come off as criticizing it 🙁
Paul
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
February 27, 2010 at 6:45 am
Paul White (2/27/2010)
Hey no worries! My apologies if my original post back there wasn't brilliantly worded - I was just adding a little 'shine' to your good solution, I wasn't intending to come off as criticizing it 🙁Paul
No reason at all to apologize! Even after rereading your posts a dozen times I couldn't find a single word or phrase that could qualify as critics! All I could find were valid, true and helpful statements. You just didn't leave a "good solution" (thanx for the compliment :blush: ) as it is but made it a "better solution". And isn't that what this community is all about (maybe excluding THE THREAD)?
So, again: Thank you for pointing at some side effects I didn't know before. But now I know. And that's "your fault" ;-). I hope it's nothing you feel sorry for...
February 27, 2010 at 6:52 am
Good, I am genuinely pleased you took it all the right way. Thanks Lutz!
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi
Viewing 12 posts - 1 through 11 (of 11 total)
You must be logged in to reply to this topic. Login to reply