2017-09-14 6 views
1

j'ai une table où chaque enregistrement représente une personne et il y a beaucoup de colonnes utilisées pour indiquer quels événements ils ont assisté:Conversion ligne unique en plusieurs lignes basées sur des valeurs dans les colonnes

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' 

qui ressemble à ceci par exemple :

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

Je veux générer une liste des personnes qui a assisté à quels événements, donc ma sortie désirée est:

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

En réalité, j'ai beaucoup plus de 3 événements, mais ce qui précède est pour faciliter l'explication (Ceci est pas une question de devoirs btw). Comme les événements pourraient changer à l'avenir et que je n'ai aucun contrôle sur les données que je suis en train de transmettre, j'ai vraiment besoin d'une solution dynamique capable de gérer n'importe quel nombre de colonnes d'événements possibles.

Je suppose que je peux faire quelque chose avec UNPIVOT, mais je ne peux tout simplement pas comprendre, ou trouver un bon exemple sur SO ou ailleurs de travailler - quelqu'un peut-il aider?

Répondre

1

Essayez quelque chose comme

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 

Pour sql dynamique, vous pouvez essayer quelque chose comme ceci:

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

Mais si j'ai 200 colonnes Event, j'ai besoin de 200 sous-requêtes 'UNION'ed ensemble? Je cherche un moyen de le faire dynamiquement si possible. – 3N1GM4

+0

générez-vous une nouvelle colonne pour chaque nouvel événement? – Yeou

+0

Je ne suis pas personnellement, mais l'ensemble de données que je suis passé le fait de cette façon, oui. – 3N1GM4

1

Je le fais en utilisant 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

Selon la réponse de [Yeou] (https://stackoverflow.com/users/8564491/yeou), cela nécessiterait une ligne distincte dans 'values' pour chaque événement - j'ai des centaines d'événements, alors préférez ne pas avoir à mettre ensemble cette grande déclaration si possible. En outre, les événements pourraient changer à l'avenir, il doit donc être dynamique. Je vais mettre à jour la question pour être plus clair. – 3N1GM4

+0

@ 3N1GM4. . . Vos données d'exemple comportent trois colonnes d'événements. Vous avez un problème avec votre application si vous ajoutez régulièrement des colonnes à une table. –

+0

Je suis d'accord @GordonLinoff, mais malheureusement c'est en dehors de mon contrôle. – 3N1GM4

1

Vous pouvez le faire avec UNPIVOT Comme vous l'avez dit, vous devez simplement vous assurer que vous lui dites quel est l'événement, sinon vous obtenez juste un 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

Comme pour les autres réponses, cela nécessite de réécrire la requête chaque fois que les colonnes d'événements changent (ajouter de nouveaux événements, renommer des événements ou supprimer des événements), donc cela ne va pas m'aider. – 3N1GM4

+0

Compris, ce n'était pas dans votre question d'origine d'où le code ci-dessus. J'aurai un pense. – Leonidas199x

0

cernées la solution que je pensais, mais oui, il ne nécessite SQL dynamique pour obtenir les noms de colonnes pertinentes pour alimenter le 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) 

Dans cet exemple, je fais l'hypothèse que les colonnes Event sont à partir de la deuxième colonne dans le tableau, mais évidemment, je pourrais utiliser une logique différente dans la sous-requête pour identifier les colonnes pertinentes si nécessaire.

Sur mes données par exemple, cela donne le résultat souhaité:

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

Je pense que je préfère cela à l'aide d'un curseur, même si je ne l'ai pas fait confirmé quelle différence la performance (le cas échéant), il y a entre la deux approches dynamiques.

Merci pour l'aide de tout le monde et des suggestions sur cette question si, grandement apprécié comme toujours!