SQL Server:列到行

Ser*_*gei 117 sql t-sql sql-server unpivot

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

这是一个例子:我有一个包含以下模式的表:

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]
Run Code Online (Sandbox Code Playgroud)

以下是我想得到的结果:

[ID] [EntityId] [IndicatorName] [IndicatorValue]
Run Code Online (Sandbox Code Playgroud)

结果值将是:

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'
Run Code Online (Sandbox Code Playgroud)

等等..

这有意义吗?您对在T-SQL中查看的位置以及如何完成它有什么建议吗?

Tar*_*ryn 222

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

select id, entityId,
  indicatorname,
  indicatorvalue
from yourtable
unpivot
(
  indicatorvalue
  for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;
Run Code Online (Sandbox Code Playgroud)

请注意,要取消激活的列的数据类型必须相同,因此在应用unpivot之前可能必须转换数据类型.

您还可以使用CROSS APPLYUNION 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);
Run Code Online (Sandbox Code Playgroud)

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

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

最后,如果您有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;
Run Code Online (Sandbox Code Playgroud)

  • 对于那些想要更多关于`UNPIVOT`和/ vs的章节的人来说.`APPLY`,[来自Brad Schulz的2010年博客文章](http://bradsruminations.blogspot.com/2010/02/spotlight-on-unpivot-part-1.html)(以及[后续](http ://bradsruminations.blogspot.com/2010/02/spotlight-on-unpivot-part-2.html))是美丽的. (4认同)
  • @JDPeckham如果您有不同的数据类型,那么您需要在执行逆透视之前将它们转换为相同的类型和长度。这是[更多信息](https://dba.stackexchange.com/questions/54353/why-does-sql-server-require-the-datatype-length-to-be-the-same-when-using -unpivo)。 (3认同)
  • 消息8167,级别16,状态1,行147列“ blahblah”的类型与UNPIVOT列表中指定的其他列的类型冲突。 (2认同)

Rom*_*kar 20

如果您有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%'
Run Code Online (Sandbox Code Playgroud)

sql fiddle demo

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

更新
由于评论中有一个很大的火焰,我想我会添加一些xml /动态SQL的优点和缺点.我会尽量做到尽可能客观,不要提到优雅和丑陋.如果您有任何其他优点和缺点,请编辑答案或写下评论

缺点

  • 它不像动态SQL 那么快,粗略的测试给了我xml比动态慢大约2.5倍(这是~250000行表上的一个查询,所以这个估计并不准确).你可以自己比较一下,如果你想要,这里是sqlfiddle的例子,在100000行上它是29s(xml)vs 14s(动态);
  • 对于不熟悉xpath的人来说可能更难理解 ;

利弊

  • 它与您的其他查询的范围相同,这可能非常方便.想到几个例子
    • 你可以在你的触发器内查询inserteddeleted表(根本不可能动态);
    • 用户不必拥有直接从表中选择的权限.我的意思是,如果你有存储过程层,用户有权运行sp,但是没有直接查询表的权限,你仍然可以在存储过程中使用这个查询;
    • 您可以查询已在作用域中填充的表变量(要将其传递到动态SQL中,而不是将其作为临时表或创建类型,并将其作为参数传递给动态SQL;
  • 你可以在函数内部执行此查询(标量或表值).在函数内部不可能使用动态SQL;

  • 如果您希望您的用户能够执行代码,那么您必须为他们提供执行代码所需的任何访问权限.不要弥补不存在的要求,使你的答案听起来更好(你也不必评论竞争的答案来看你的答案 - 如果他们找到答案,他们也可以找到你的答案). (3认同)
  • 您使用XML选择哪些数据不需要从表中选择数据? (2认同)
  • 此外,如果您使用XML的理由是您可以将其放在存储过程中以避免直接访问表,那么您的示例可能会显示如何将其放入存储过程以及如何向用户授予权限以便它们可以执行它而无需对基础表的读访问权.对我来说,这是范围蔓延,因为大多数人对表编写查询都具有对表的读访问权. (2认同)
  • 我说持续时间的10倍差异很重要,是的.而且~8,000行不是"大量数据" - 我们应该看看800,000行会发生什么? (2认同)

Joh*_*tti 12

只是因为我没有看到它被提及。

如果是 2016 年以上,这里还有另一个选项可以动态反转数据,而无需实际使用动态 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
Run Code Online (Sandbox Code Playgroud)

退货

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
Run Code Online (Sandbox Code Playgroud)


Dmy*_*yan 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;
Run Code Online (Sandbox Code Playgroud)


小智 5

我需要一个解决方案来将 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)
Run Code Online (Sandbox Code Playgroud)

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

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

总的来说,这会将列转换为行,而无需知道列名,也无需使用动态 SQL。对于我的目的来说它已经足够快了。

(编辑:我刚刚看到Roman Pekar在上面回答,他也在做同样的事情。我首先使用带有游标的动态sql触发器,这比这个解决方案慢10到100倍,但也许这是由游标引起的,而不是由游标引起的动态sql。无论如何,这个解决方案非常简单且通用,因此它绝对是一个选项)。

我在此位置留下此评论,因为我想在有关完整审核触发器的帖子中引用此解释,您可以在这里找到:/sf/answers/3066020051/