8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

SQL Server:列转行

Tran To 1月前

44 0

寻找优雅的(或任何)解决方案将列转换为行。这是一个例子:我有一张具有以下模式的表:[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicat...

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

这是一个例子:我有一张具有以下模式的表:

[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 中查找的位置和如何完成它有什么建议吗?

帖子版权声明 1、本帖标题:SQL Server:列转行
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Tran To在本站《t-sql》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您可以使用 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 版本,您甚至可以将 CROSS APPLY 与 VALUES 子句一起使用:

    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;
    
  • 对于那些想了解更多有关 UNPIVOT 和/或 APPLY 细节的人来说,Brad Schulz 的这篇 2010 年博客文章(以及后续文章)非常精彩。

  • 消息 8167,级别 16,状态 1,第 147 行列 \'blahblah\' 的类型与 UNPIVOT 列表中指定的其他列的类型冲突。

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

  • xml 方法有个缺陷,就是无法对 >、<、& 等 xml 代码进行转义。另外,通过改写如下代码可以显著提高性能:select @colsUnpivot = stuff((select ','+quotename(C.column_name) as [text()] from information_schema.columns as C where C.table_name = 'yourtable' and C.column_name like 'Indicator%' for xml path(''), type).value('text()[1]','nvarchar(max)'), 1, 1, '')

  • 如果你有 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 演示

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

    更新
    由于评论中争论很大,我想我会补充一些 xml/动态 SQL 的优缺点。我会尽量客观,不提优雅和丑陋。如果你有任何其他优缺点,请编辑答案或在评论中写下

    缺点

    • 没有动态 SQL 那么快 ,粗略的测试告诉我,xml 比动态 SQL 慢大约 2.5 倍(它是对 ~250000 行表的一个查询,因此这个估计值绝对不准确)。如果您愿意,您可以自己比较一下,这是 sqlfiddle 示例,在 100000 行上,它是 29 秒(xml)vs 14 秒(动态);
    • 对于不熟悉 xpath 的人来说 可能比较 难理解

    优点

    • 它的 范围与你的其他查询相同 ,这可能非常方便。我想到了一些例子
      • you could query inserted and deleted tables inside your trigger (not possible with dynamic at all);
      • user don't have to have permissions on direct select from table. What I mean is if you have stored procedures layer and user have permissions to run sp, but don't have permissions to query tables directly, you still could use this query inside stored procedure;
      • you could query table variable you have populated in your scope (to pass it inside the dynamic SQL you have to either make it temporary table instead or create type and pass it as a parameter into dynamic SQL;
    • 您可以 在函数 (标量或表值)内执行此查询。无法在函数内使用动态 SQL;
  • Susy 1月前 0 只看Ta
    引用 8

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

  • 如果您希望用户能够执行代码,那么您必须授予他们执行代码所需的任何访问权限。不要编造不存在的要求来让您的答案听起来更好(您也不必评论竞争答案来查看您的答案 - 如果他们找到了那个答案,他们也可以找到您的答案)。

  • 此外,如果您使用 XML 的理由是您可以将其放入存储过程以避免授予对表的直接访问权限,那么您的示例可能应该展示如何将其放入存储过程以及如何向用户授予权限,以便他们无需读取底层表即可执行它。对我来说,这是范围蔓延,因为大多数针对表编写查询的人都具有对表的读取权限。

  • 是的,我认为 10 倍的持续时间差异确实很重要。而且约 8,000 行并不是“大量数据”——我们应该看看 800,000 行会发生什么吗?

  • 引用 12

    为了帮助新读者,我创建了一个示例来更好地理解@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;
    
  • 只是因为我没有看到提到它。

    如果是 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
    

    返回

    ID  Item    Value
    1   Col1    A
    1   Col2    B
    2   Col1    R
    2   Col2    C
    3   Col1    X
    3   Col2    D
    
  • 我喜欢它。最巧妙的破解/解决方案奖颁给你。它有效而且简单。如果它有效,那是不是一个坏主意?!:-)

  • 此外,如果列名符合某种模式,此方法还允许轻松选择列。除了排除(如示例所示),您还可以使用以下形式包含:Where [Key] like 'Indicator%'

  • 我需要一个解决方案来将 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(子查询选择 i,* for xml raw,这会将所有列转换为一个 xml 列)

    然后我将一个函数交叉应用到该列的每个 XML 属性,这样每个属性就得到一行。

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

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

    我在这里留下这条评论,因为我想在我关于完整审计触发器的帖子中引用这个解释,你可以在这里找到: https://.com/a/43800286/4160788

  • 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 的表列表。您可以在解决方案中以行的形式查看表列表。

  • 引用 18

    与此相反的是将一列展平为 csv 例如

    选择 STRING_AGG ([value],',') 从 STRING_SPLIT('Akio,Hiraku,Kazuo', ',')

返回
作者最近主题: