2017-09-14 32 views
1

我有每個記錄代表一個人的桌子以及用於指示他們參加什麼活動的列列基於價值觀多行:轉換單列成

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' 

它看起來像這樣的例子:

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

我要生成誰出席了活動,所以我期望的輸出是一個列表:

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

在現實中,我有超過3個事件,但以上是爲了便於解釋(這是而不是作業問題btw)。由於事件將來可能會發生變化,而且我無法控制傳遞的數據,所以我真的需要一個動態解決方案,它可以處理任意數量的可能事件列。

我假設我可以用UNPIVOT做一些事情,但我無法弄清楚,或找到一個很好的例子,在SO或其他地方工作 - 有人可以幫忙嗎?

回答

1

試着這麼做

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 

動態SQL你可以嘗試這樣的事:

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); 
+0

但是如果我有200個事件列,我需要200個子查詢'UNION'ed在一起?我正在尋找一種方法,如果可能的話,動態地做到這一點。 – 3N1GM4

+0

您是否爲每個新事件生成新列? – Yeou

+0

我不是個人,但我正在傳遞的數據集是這樣做的,是的。 – 3N1GM4

1

我做到這一點使用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' 
+0

根據[Yeou](https://stackoverflow.com/users/8564491/yeou)的回答,這將需要在每個事件的'values'內單獨一行 - 我有數百個事件,所以寧願如果可能的話,不必把這麼大的聲明放在一起。此外,事件可能會在未來發生變化,所以它確實需要動態。我會更新這個問題,以便更清楚。 – 3N1GM4

+0

@ 3N1GM4。 。 。您的示例數據有三個事件列。如果您經常向表中添加列,那麼您的應用程序出現問題。 –

+0

我同意@GordonLinoff,但不幸的是,這超出了我的控制範圍。 – 3N1GM4

1

你可以用逆透視做到這一點正如你所說的,你只需要確保你告訴它是什麼事件,否則你只會得到一個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 
+0

與其他答案一樣,這需要我每次更改Event列(添加新事件,重命名事件或移除事件)時重新編寫查詢,所以這不會對我有所幫助。 – 3N1GM4

+0

瞭解,這不是你原來的問題,因此上面的代碼。會有一個想法。 – Leonidas199x

0

想通了,我想的解決方案,但肯定的,它確實需要動態SQL來獲得相關列名送入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) 

在這個例子中,我提出假設Event列是從表中的第二列開始的,但顯然我可以在子查詢中使用一些不同的邏輯來確定相關列(如有必要)。

在我的示例數據,這給期望的結果:

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

我想我更喜歡這對使用遊標,雖然我還沒有真正證實了性能上的差異(如果有的話)存在之間兩種動態方法。

感謝大家對這個問題的幫助和建議,儘管如此,非常感謝!