149 votes

SQL Server : Colonnes en rangs

Je recherche une solution élégante (ou toute autre solution) pour convertir les colonnes en lignes.

Voici un exemple : J'ai une table avec le schéma suivant :

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]

Voici ce que je veux obtenir comme résultat :

[ID] [EntityId] [IndicatorName] [IndicatorValue]

Et les valeurs du résultat seront :

1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'

Et ainsi de suite..

Est-ce que cela a un sens ? Avez-vous des suggestions sur l'endroit où chercher et sur la manière de le faire en T-SQL ?

295voto

bluefeet Points 105508

Vous pouvez utiliser le UNPIVOT pour convertir les colonnes en lignes :

select id, entityId,
  indicatorname,
  indicatorvalue
from yourtable
unpivot
(
  indicatorvalue
  for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;

Notez que les types de données des colonnes que vous dépivotez doivent être les mêmes. Il se peut donc que vous deviez convertir les types de données avant d'appliquer le dépiquage.

Vous pouvez également utiliser CROSS APPLY avec UNION ALL pour convertir les colonnes :

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  select 'Indicator1', Indicator1 union all
  select 'Indicator2', Indicator2 union all
  select 'Indicator3', Indicator3 union all
  select 'Indicator4', Indicator4 
) c (indicatorname, indicatorvalue);

En fonction de votre version de SQL Server, vous pouvez même utiliser CROSS APPLY avec la clause VALUES :

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  values
  ('Indicator1', Indicator1),
  ('Indicator2', Indicator2),
  ('Indicator3', Indicator3),
  ('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);

Enfin, si vous avez 150 colonnes à déplier et que vous ne voulez pas coder en dur l'ensemble de la requête, vous pouvez générer l'instruction SQL à l'aide de SQL dynamique :

DECLARE @colsUnpivot AS NVARCHAR(MAX),
   @query  AS NVARCHAR(MAX)

select @colsUnpivot 
  = stuff((select ','+quotename(C.column_name)
           from information_schema.columns as C
           where C.table_name = 'yourtable' and
                 C.column_name like 'Indicator%'
           for xml path('')), 1, 1, '')

set @query 
  = 'select id, entityId,
        indicatorname,
        indicatorvalue
     from yourtable
     unpivot
     (
        indicatorvalue
        for indicatorname in ('+ @colsunpivot +')
     ) u'

exec sp_executesql @query;

25voto

Roman Pekar Points 31863

Si vous avez 150 colonnes, je pense que UNPIVOT n'est pas une option. Vous pouvez donc utiliser l'astuce xml

;with CTE1 as (
    select ID, EntityID, (select t.* for xml raw('row'), type) as Data
    from temp1 as t
), CTE2 as (
    select
         C.id, C.EntityID,
         F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
         F.C.value('.', 'nvarchar(max)') as IndicatorValue
    from CTE1 as c
        outer apply c.Data.nodes('row/@*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'

<a href="http://sqlfiddle.com/#!3/67f9a/2" rel="noreferrer">sql fiddle demo</a>

Vous pouvez également écrire du SQL dynamique, mais je préfère le xml. Pour le SQL dynamique, vous devez avoir les autorisations nécessaires pour sélectionner les données directement dans la table, ce qui n'est pas toujours possible.

UPDATE
Comme il y a une grande flamme dans les commentaires, je pense que je vais ajouter quelques avantages et inconvénients de xml/dynamic SQL. Je vais essayer d'être aussi objectif que possible et de ne pas mentionner l'élégance et la laideur. Si vous avez d'autres avantages et inconvénients, modifiez la réponse ou écrivez dans les commentaires.

contras

  • c'est pas aussi rapide En ce qui concerne le SQL dynamique, des tests grossiers m'ont donné que le xml est environ 2,5 fois plus lent que le dynamique (il s'agissait d'une requête sur une table de ~250000 lignes, donc cette estimation n'est pas du tout exacte). Vous pouvez comparer vous-même si vous le souhaitez. sqlfiddle Par exemple, sur 100000 lignes, il a fallu 29s (xml) contre 14s (dynamique) ;
  • c'est peut-être le cas plus difficile à comprendre pour les personnes qui ne sont pas familières avec xpath ;

pros

  • c'est le même périmètre comme vos autres requêtes, et cela pourrait être très pratique. Quelques exemples me viennent à l'esprit
    • vous pouvez interroger inserted y deleted à l'intérieur de votre déclencher (pas du tout possible avec dynamic) ;
    • l'utilisateur n'a pas besoin d'avoir permissions on direct select from table. Ce que je veux dire, c'est que si vous avez une couche de procédures stockées et que l'utilisateur a les permissions d'exécuter les procédures, mais n'a pas les permissions d'interroger directement les tables, vous pouvez toujours utiliser cette requête dans la procédure stockée ;
    • vous pourriez variable de la table de requête que vous avez rempli dans votre champ d'application (pour le passer dans le SQL dynamique, vous devez soit en faire une table temporaire, soit créer un type et le passer comme paramètre dans le SQL dynamique) ;
  • vous pouvez le faire dans la fonction (scalaire ou valeur de tableau). Il n'est pas possible d'utiliser du SQL dynamique dans les fonctions ;

11voto

Dmyan Points 735

Pour aider les nouveaux lecteurs, j'ai créé un exemple pour mieux comprendre la réponse de @bluefeet sur UNPIVOT.

 SELECT id
        ,entityId
        ,indicatorname
        ,indicatorvalue
  FROM (VALUES
        (1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
        (2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
        (3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
        (4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
       ) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
    indicatorvalue
    FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;

6voto

John Cappelletti Points 43460

Juste parce que je ne l'ai pas vu mentionné.

Si 2016+, voici encore une autre option pour dépiquer dynamiquement des données sans utiliser réellement Dynamic SQL.

Exemple

Declare @YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into @YourTable Values 
 (1,'A','B')
,(2,'R','C')
,(3,'X','D')

Select A.[ID]
      ,Item  = B.[Key]
      ,Value = B.[Value]
 From  @YourTable A
 Cross Apply ( Select * 
                From  OpenJson((Select A.* For JSON Path,Without_Array_Wrapper )) 
                Where [Key] not in ('ID','Other','Columns','ToExclude')
             ) B

Renvoie à

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D

4voto

flack Points 101

J'avais besoin d'une solution pour convertir des colonnes en lignes dans Microsoft SQL Server, sans connaître le nom des colonnes (utilisé dans un trigger) et sans SQL dynamique (le SQL dynamique est trop lent pour être utilisé dans un trigger).

J'ai finalement trouvé cette solution, qui fonctionne bien :

SELECT
    insRowTbl.PK,
    insRowTbl.Username,
    attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
    attr.insRow.value('.', 'nvarchar(max)') as FieldValue 
FROM ( Select      
          i.ID as PK,
          i.LastModifiedBy as Username,
          convert(xml, (select i.* for xml raw)) as insRowCol
       FROM inserted as i
     ) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow)

Comme vous pouvez le voir, je convertis la ligne en XML (Sous-requête select i,* for xml raw, ceci convertit toutes les colonnes en une colonne xml)

Ensuite, j'applique une fonction par croisement à chaque attribut XML de cette colonne, de manière à obtenir une ligne par attribut.

Globalement, cela convertit les colonnes en lignes, sans connaître les noms des colonnes et sans utiliser le sql dynamique. C'est suffisamment rapide pour mes besoins.

(Edit : Je viens de voir la réponse de Roman Pekar ci-dessus, qui fait la même chose. J'ai d'abord utilisé le trigger sql dynamique avec des curseurs, ce qui était 10 à 100 fois plus lent que cette solution, mais peut-être que c'était causé par le curseur, pas par le sql dynamique. Quoi qu'il en soit, cette solution est très simple et universelle, donc c'est définitivement une option).

Je laisse ce commentaire à cet endroit, car je veux faire référence à cette explication dans mon post sur le déclencheur d'audit complet, que vous pouvez trouver ici : https://stackoverflow.com/a/43800286/4160788

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X