SQL Server:列到行


128

寻找优雅的(或任何)解决方案以将列转换为行。

这是一个示例:我有一个具有以下架构的表:

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

这是我想要得到的结果:

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

结果值为:

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'

等等..

这有意义吗?您对在哪里看以及如何在T-SQL中完成任何建议?


2
您是否研究过枢轴/取消枢轴
乔什·杰伊

最后,它与bluefeet的解决方案一起使用。优雅而实用。非常感谢大家。
谢尔盖

Answers:


247

您可以使用UNPIVOT函数将列转换为行:

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

请注意,要取消透视的列的数据类型必须相同,因此您可能必须在应用取消透视之前转换数据类型。

您也可以CROSS APPLY与UNION ALL一起使用来转换列:

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

根据您的SQL Server版本,您甚至可以对VALUES子句使用CROSS APPLY:

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

最后,如果您有150列要取消透视,并且您不想对整个查询进行硬编码,则可以使用动态SQL生成sql语句:

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;

4
对于那些想要更多关于UNPIVOT和/或的螺母。APPLY布拉德·舒尔茨Brad Schulz)(及其后续文章)在2010年发表的这篇博客很美。
鲁芬2015年

2
消息8167,级别16,状态1,行147列“ blahblah”的类型与UNPIVOT列表中指定的其他列的类型冲突。
JDPeckham

@JDPeckham如果您具有不同的数据类型,则需要在执行取消数据透视之前将它们转换为相同的类型和长度。这是有关此的更多信息
塔林

xml方法有一个缺陷,因为它无法取消对>,&lt ;、和&。另外,可以通过如下重写来显着提高性能:select @colsUnpivot = stuff((select information,schema.columns中的[text()]选择[,, + quotename(C.column_name)as C,其中C.table_name ='yourtable'和C.column_name,例如xml path('')的'Indicator%',类型).value('text()[1]','nvarchar(max)'),1,1,'')
rrozema

24

好吧,如果您有150列,那么我认为UNPIVOT是不可行的。所以你可以使用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

您也可以编写动态SQL,但我更喜欢xml-对于动态SQL,您必须具有直接从表中选择数据的权限,但这并不总是一种选择。

更新
由于注释中有很大的火焰,我想我将添加xml /动态SQL的一些优缺点。我会尽量保持客观,不要提及优雅和丑陋。如果您还有其他利弊,请编辑答案或在评论中写

缺点

  • 没有动态SQL ,但粗略的测试使我发现xml比动态SQL慢2.5倍(这是对250000行表的一次查询,因此这种估算是不准确的)。如果需要,您可以自己比较它,这是sqlfiddle示例,在100000行上,它是29s(xml)对14s(dynamic);
  • 对于不熟悉xpath的人来说,可能难以理解

优点

  • 其他查询的作用域相同,因此非常方便。我想到了一些例子
    • 您可以查询触发器中的inserteddeleted表格(根本无法使用动态);
    • 用户不必具有直接从表中选择的权限。我的意思是,如果您具有存储过程层并且用户具有运行sp的权限,但是没有直接查询表的权限,则仍然可以在存储过程内部使用此查询。
    • 您可以查询在您的范围内填充的表变量(要在动态SQL中传递它,您必须使其成为临时表,或者创建类型并将其作为参数传递给动态SQL;
  • 您可以在函数(标量或表值)中进行此查询。在函数内部不能使用动态SQL。

2
您正在使用XML选择不需要从表中选择数据的哪些数据?
亚伦·伯特兰

1
例如,您可以决定不授予用户从表中选择数据的权限,而只能在使用表的存储过程上授予用户权限,因此我可以在过程内选择xml,但是如果要使用动态SQL,则必须使用一些解决方法
Roman Pekar

3
如果您希望用户能够执行代码,则必须为他们提供执行代码所需的任何访问权限。不要提出不存在的要求,以使您的答案听起来更好(您也不必评论竞争性答案以查看您的答案-如果他们找到了答案,他们也可以找到您的答案)。
亚伦·伯特兰

2
同样,如果您使用XML的理由是可以将其放入存储过程中以避免直接访问表,那么也许您的示例应显示如何将其放入存储过程中以及如何向用户授予权限,以便他们无需读取对基础表的访问就可以执行它。对我来说,这是范围的爬行,因为大多数针对表编写查询的人都具有对该表的读取权限。
亚伦·伯特兰

2
我会说,持续时间相差10倍确实很重要,是的。大约8,000行不是“大量数据”-我们应该看看800,000行会发生什么吗?
亚伦·伯特兰

7

为了帮助新读者,我创建了一个示例,以更好地了解@bluefeet关于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;

3

我需要一种解决方案,以在Microsoft SQL Server中将列转换为行,而又不知道列名(在触发器中使用)并且没有动态sql(动态sql在触发器中使用太慢了)。

我终于找到了这个解决方案,它运作良好:

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)

如您所见,我将行转换为XML(子查询为xml raw选择i,*,这会将所有列转换为一个xml列)

然后,将一个函数交叉应用到此列的每个XML属性,以便每个属性获得一行。

总体而言,这会将列转换为行,而无需知道列名并且不使用动态sql。就我的目的而言,它足够快。

(编辑:我只是看到上面的Roman Pekar回答,他也这样做。动态sql。无论如何,此解决方案非常简单,是通用的,因此绝对可以选择)。

我将在此发表评论,因为我想在我的帖子中引用有关完整审计触发器的解释,您可以在这里找到:https : //stackoverflow.com/a/43800286/4160788


3
DECLARE @TableName varchar(max)=NULL
SELECT @TableName=COALESCE(@TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
  FROM sysindexes AS i
  INNER JOIN sysobjects AS o ON i.id = o.id
  INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
 WHERE i.indid < 2
  AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
  AND i.rowcnt >350
  AND o.xtype !='TF'
 ORDER BY o.name ASC

 print @tablename

您可以获得具有行数> 350的表的列表。您可以在表的解决方案列表中看到行。


2

只是因为我没有提到它。

如果是2016年以后的版本,这是无需实际使用Dynamic SQL即可动态取消数据透视的另一种选择。

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

退货

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.