web-dev-qa-db-fra.com

SQL Server: Colonnes en lignes

Vous recherchez une solution élégante (ou une solution quelconque) 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 de 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'

Etc..

Est-ce que ça a du sens? Avez-vous des suggestions sur où chercher et comment le faire dans T-SQL?

105
Sergei

Vous pouvez utiliser la fonction UNPIVOT pour convertir les colonnes en lignes:

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

Veuillez noter que les types de données des colonnes pour lesquelles vous êtes non pivotés doivent être identiques, de sorte que vous devrez peut-être convertir les types de données avant d'appliquer l'impivot.

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écomposer et que vous ne souhaitez pas coder de manière irréversible l'intégralité 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;
207
Taryn

eh bien, si vous avez 150 colonnes, alors je pense que UNPIVOT n'est pas une option. Donc, vous pouvez 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%'

sql fiddle demo

Vous pouvez également écrire du SQL dynamique, mais j'aime davantage xml. Pour le SQL dynamique, vous devez avoir le droit de sélectionner des données directement à partir d'une table, ce qui n'est pas toujours une option.

UPDATE
Comme il ya une grosse flamme dans les commentaires, je pense que je vais ajouter quelques avantages et inconvénients du code XML/dynamique. Je vais essayer d'être aussi objectif que possible et de ne pas parler d'élégance et de laideur. Si vous avez d'autres avantages et inconvénients, modifiez la réponse ou écrivez des commentaires.

contre

  • n'est pas aussi rapide que le SQL dynamique, des tests approximatifs m'ont montré que xml est environ 2,5 fois plus lent que dynamique (il s'agissait d'une requête sur une table d'environ 250000 lignes, alors cette estimation n’est pas exacte). Vous pouvez le comparer vous-même si vous le souhaitez, voici ( sqlfiddle exemple, sur 100 000 lignes, il était de 29s (xml) vs 14s ( dynamique);
  • peut-être pourrait-il être plus difficile à comprendre pour les personnes ne connaissant pas xpath;

pros

  • c'est la même portée que vos autres requêtes, et cela pourrait être très pratique. Quelques exemples me viennent à l’esprit
    • vous pouvez interroger inserted et deleted tables à l'intérieur de votre trigger (impossible avec dynamic du tout);
    • l'utilisateur n'a pas besoin de permissions pour la sélection directe dans la table. Ce que je veux dire, c'est que si vous avez une couche de procédures stockées et que l'utilisateur dispose des autorisations nécessaires pour exécuter sp, mais ne dispose pas des autorisations nécessaires pour interroger directement les tables, vous pouvez toujours utiliser cette requête dans une procédure stockée;
    • vous pouvez la variable de table de requête que vous avez renseignée dans votre étendue (pour la passer à l'intérieur du SQL dynamique, vous devez soit en faire une table temporaire, soit créer un type et transmettez-le en tant que paramètre dans SQL dynamique;
  • vous pouvez faire cette requête dans la fonction (scalaire ou valeur de table). Il n'est pas possible d'utiliser du SQL dynamique dans les fonctions;
19
Roman Pekar

Juste 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;
6
Dmyan

J'avais besoin d'une solution pour convertir des colonnes en lignes dans Microsoft SQL Server, sans connaître les noms de colonnes (utilisés dans le déclencheur) ni sans SQL dynamique (SQL dynamique est trop lent pour être utilisé dans un déclencheur).

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 constater, je convertis la ligne en XML (Subquery select i, * pour xml raw, cela convertit toutes les colonnes en une seule colonne xml).

Ensuite, je CROIS APPLIQUE une fonction à 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 SQL dynamique. C'est assez rapide pour mon but.

(Edit: Je viens de voir Roman Pekar répondre ci-dessus, qui fait la même chose. J'ai utilisé le déclencheur SQL dynamique avec des curseurs en premier, qui était 10 à 100 fois plus lent que cette solution, mais peut-être que cela a été causé par le curseur, pas par la dynamique sql. En tout cas, cette solution est très simple, universelle, donc c’est définitivement une option).

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

3
flack
DECLARE @TableName nvarchar(50)
DECLARE column_to_row CURSOR FOR

--List of tables that we want to unpivot columns as row
SELECT DISTINCT t.name FROM sys.tables t
JOIN sys.schemas s ON t.schema_id=t.schema_id
WHERE t.name like '%_CT%'
AND s.name='cdc'

OPEN  column_to_row
FETCH NEXT FROM column_to_row INTO @TableName
WHILE @@FETCH_STATUS = 0
BEGIN

DECLARE @script nvarchar(max) = null
DECLARE @columns nvarchar(2000) = null

-- keep the table's column list
select @columns = COALESCE(@columns + ',','') + c.name from sys.tables  t
join sys.columns c on t.object_id = c.object_id
where t.name = @TableName

set @script = 'SELECT '+@columns+' FROM [cdc].['+@TableName+'] (nolock)'
--print (@script)
exec (@script)

FETCH NEXT FROM column_to_row INTO @TableName
END
CLOSE column_to_row
DEALLOCATE column_to_row

Voici une autre méthode pour les colonnes aux lignes, combien de table et combien de colonnes que vous avez, n'est pas important. Il suffit de définir les paramètres et d’obtenir le résultat. J'ai écrit ceci, car parfois j'ai besoin du résultat d'une table A (qui est un ensemble de résultats de colonne), en tant que champs d'une autre table B (qui doit être des champs de lignes). Dans ce cas, je ne sais pas combien de champs j'ai définis pour ma table B.

3
cunay

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

Si 2016+, il existe encore une autre option permettant de dé-dynamiser dynamiquement des données sans utiliser réellement le SQL dynamique.

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

Retours

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