Converting single row into multiple rows based on values in columns

旧时模样 提交于 2020-04-16 04:05:01

问题


I have a table where each record represents a person and there are many columns used to indicate what events they attended:

CREATE TABLE EventAttendees
(
    Person VARCHAR(100),
    [Event A] VARCHAR(1),
    [Event B] VARCHAR(1),
    [Event C] VARCHAR(1)
)

INSERT INTO EventAttendees
SELECT 'John Smith','x',NULL,NULL
UNION
SELECT 'Jane Doe',NULL,'x','x'
UNION
SELECT 'Phil White','x',NULL,'x'
UNION
SELECT 'Sarah Jenkins','x','x','x'

Which looks like this for example:

SELECT * FROM Event Attendees

/---------------|---------|---------|---------\
| Person        | Event A | Event B | Event C |
|---------------|---------|---------|---------|
| John Smith    |    x    |   NULL  |   NULL  |
| Jane Doe      |   NULL  |    x    |    x    |
| Phil White    |    x    |   NULL  |    x    |
| Sarah Jenkins |    x    |    x    |    x    |
\---------------|---------|---------|---------/

I want to generate a list of who attended which events, so my desired output is:

/---------------|---------|
| Person        | Event   |
|---------------|---------|
| John Smith    | Event A |
| Jane Doe      | Event B |
| Jane Doe      | Event C |
| Phil White    | Event A |
| Phil White    | Event C |
| Sarah Jenkins | Event A |
| Sarah Jenkins | Event B |
| Sarah Jenkins | Event C |
\---------------|---------/

In reality I have many more than 3 events, but the above is for ease of explanation (This is not a homework question btw). As the Events might change in the future and I have no control over the data I am being passed, I really need a dynamic solution which can handle any number of possible event columns.

I'm assuming I can do something with UNPIVOT, but I just can't figure it out, or find a good example on SO or elsewhere to work from - can someone help?


回答1:


I do this using outer apply:

select ea.person, v.EventName
from EventAttendees ea outer apply
     (values ('Event A', [Event A]),
             ('Event B', [Event B]),
             ('Event C', [Event C])
     ) v(EventName, EventFlag)
where v.EventFlag = 'x'



回答2:


You can do it with unpivot as you said, you would just need to ensure you are telling it what event it is for, otherwise you just get an X:

CREATE TABLE #tmpEventAttendees
(
    Person VARCHAR(100),
    [Event A] VARCHAR(1),
    [Event B] VARCHAR(1),
    [Event C] VARCHAR(1)
)
INSERT INTO #tmpEventAttendees
SELECT 'John Smith','x',NULL,NULL
UNION
SELECT 'Jane Doe',NULL,'x','x'
UNION
SELECT 'Phil White','x',NULL,'x'
UNION
SELECT 'Sarah Jenkins','x','x','x'

SELECT Person, [Event]
FROM
(
  SELECT    Person                                                                                  , 
            CASE WHEN [Event A] IS NOT NULL THEN 'Event A' END AS [Event A]                             , 
            CASE WHEN [Event B] IS NOT NULL THEN 'Event B' END AS [Event B]                             ,
            CASE WHEN [Event C] IS NOT NULL THEN 'Event C' END AS [Event C]
  FROM #tmpEventAttendees
) AS cp
UNPIVOT 
(
  [Event] FOR [Events] IN ([Event A], [Event B], [Event C])
) AS up;

DROP TABLE #tmpEventAttendees



回答3:


Try something like

SELECT * FROM (
    SELECT Person, CASE WHEN [Event A] = 'x' THEN 'Event A' END AS [Event] FROM EventAttendees
    UNION 
    SELECT Person, CASE WHEN [Event B] = 'x' THEN 'Event B' END AS [Event] FROM EventAttendees
    UNION 
    SELECT Person, CASE WHEN [Event C] = 'x' THEN 'Event C' END AS [Event] FROM EventAttendees
    ) AS EventAttendees
    WHERE Event is not null
    order by Person

For dynamic sql you can try something like this:

    DECLARE @name varchar(30)
    DECLARE @sql varchar(1000) = 'SELECT * FROM (';
    DECLARE NameCursor CURSOR
        FOR select name from sys.all_columns where object_id = (select object_id from sys.tables where name='EventAttendees') and name!='Person'
    OPEN NameCursor
    FETCH NEXT FROM NameCursor INTO @name

    WHILE @@FETCH_STATUS = 0
    BEGIN

        SET @sql += 'SELECT Person, CASE WHEN [' + @name+'] = ''x'' THEN ''' + @name +''' END AS [Event] FROM EventAttendees'   
        FETCH NEXT FROM NameCursor INTO @name

        IF(@@FETCH_STATUS = 0)
        BEGIN
            SET @sql += ' UNION ';
        END

    END;
    CLOSE NameCursor;  
    DEALLOCATE NameCursor;
    SET @sql += ') AS EventAttendees
            WHERE Event is not null
            order by Person';

    execute (@sql);



回答4:


Figured out the solution I was thinking of, but yes, it does require dynamic SQL to get the relevant column names to feed into the UNPIVOT:

declare @sql varchar(max)
set @sql = 
    'select Person, EventName
    from EventAttendees
    unpivot
    (
        Attended for EventName in (' + (select
                                        stuff((
                                            select ',' + QUOTENAME(c.[name])
                                            from sys.columns c
                                            join sys.objects o on c.object_id = o.object_id
                                            where o.[name] = 'EventAttendees'
                                            and c.column_id > 1
                                            order by c.[name]
                                            for xml path('')
                                        ),1,1,'') as colList) + ')
    ) unpiv
    where unpiv.Attended = ''x''
    order by Person, EventName'

exec (@sql)

In this example, I am making the assumption that the Event columns are from the second column in the table onwards, but obviously I could use some different logic within the subquery to identify the relevant columns if necessary.

On my example data, this gives the desired result:

/---------------------------\
| Person        | EventName |
|---------------|-----------|
| Jane Doe      | Event B   |
| Jane Doe      | Event C   |
| John Smith    | Event A   |
| Phil White    | Event A   |
| Phil White    | Event C   |
| Sarah Jenkins | Event A   |
| Sarah Jenkins | Event B   |
| Sarah Jenkins | Event C   |
\---------------------------/

I think I prefer this to using a cursor, although I haven't actually confirmed what performance difference (if any) there is between the two dynamic approaches.

Thanks for everyone's help and suggestions on this question though, greatly appreciated as always!



来源:https://stackoverflow.com/questions/46217564/converting-single-row-into-multiple-rows-based-on-values-in-columns

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!