Pivot to 1 column

  • 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

  • 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/

  • 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]



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

  • 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


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • 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:

  • Give a defined order to the concatenated strings
  • Overcome problems with XML entitization
  • Produce a faster query plan
  • Order the final results
  • 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-2:

    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.



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

  • 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

  • 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!

  • 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.



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

  • 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 (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...



    Lutz
    A pessimist is an optimist with experience.

    How to get fast answers to your question[/url]
    How to post performance related questions[/url]
    Links for Tally Table [/url] , Cross Tabs [/url] and Dynamic Cross Tabs [/url], Delimited Split Function[/url]

  • Good, I am genuinely pleased you took it all the right way. Thanks Lutz!

  • Viewing 12 posts - 1 through 11 (of 11 total)

    You must be logged in to reply to this topic. Login to reply