125

如何在 SQL 中简单地用行切换列?有没有简单的转置命令?

即转这个结果:

        Paul  | John  | Tim  |  Eric
Red     1       5       1       3
Green   8       4       3       5
Blue    2       2       9       1

进入这个:

        Red  | Green | Blue
Paul    1       8       2
John    5       4       2
Tim     1       3       9
Eric    3       5       1

PIVOT对于这种情况似乎太复杂了。

4

9 回答 9

164

有几种方法可以转换此数据。在您原来的帖子中,您说这PIVOT对于这种情况来说似乎太复杂了,但是可以很容易地使用SQL Server 中的UNPIVOT和函数来应用它。PIVOT

但是,如果您无权访问这些函数,则可以使用UNION ALLto复制此UNPIVOT函数,然后使用带有CASE语句 to的聚合函数PIVOT

创建表:

CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);

INSERT INTO yourTable
    ([color], [Paul], [John], [Tim], [Eric])
VALUES
    ('Red', 1, 5, 1, 3),
    ('Green', 8, 4, 3, 5),
    ('Blue', 2, 2, 9, 1);

联合所有、聚合和案例版本:

select name,
  sum(case when color = 'Red' then value else 0 end) Red,
  sum(case when color = 'Green' then value else 0 end) Green,
  sum(case when color = 'Blue' then value else 0 end) Blue
from
(
  select color, Paul value, 'Paul' name
  from yourTable
  union all
  select color, John value, 'John' name
  from yourTable
  union all
  select color, Tim value, 'Tim' name
  from yourTable
  union all
  select color, Eric value, 'Eric' name
  from yourTable
) src
group by name

请参阅带有演示的 SQL Fiddle

通过将列转换为单独的行来UNION ALL执行数据。然后将聚合函数与语句一起应用以获取每个.UNPIVOTPaul, John, Tim, Ericsum()casecolor

Unpivot 和 Pivot 静态版本:

SQL Server 中的UNPIVOT和函数都使这种转换变得更加容易。PIVOT如果您知道要转换的所有值,则可以将它们硬编码为静态版本以获得结果:

select name, [Red], [Green], [Blue]
from
(
  select color, name, value
  from yourtable
  unpivot
  (
    value for name in (Paul, John, Tim, Eric)
  ) unpiv
) src
pivot
(
  sum(value)
  for color in ([Red], [Green], [Blue])
) piv

请参阅带有演示的 SQL Fiddle

带有 的内部查询UNPIVOT执行与 相同的功能UNION ALL。它获取列列表并将其转换为行,PIVOT然后执行最终转换为列。

动态枢轴版本:

如果您有未知数量的列(Paul, John, Tim, Eric在您的示例中),然后有未知数量的颜色要转换,您可以使用动态 sql 生成列表UNPIVOT,然后PIVOT

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

select @colsUnpivot = stuff((select ','+quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('yourtable') and
               C.name <> 'color'
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT  ',' 
                      + quotename(color)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select name, '+@colsPivot+'
      from
      (
        select color, name, value
        from yourtable
        unpivot
        (
          value for name in ('+@colsUnpivot+')
        ) unpiv
      ) src
      pivot
      (
        sum(value)
        for color in ('+@colsPivot+')
      ) piv'

exec(@query)

请参阅带有演示的 SQL Fiddle

动态版本查询两者yourtable,然后查询sys.columns表以生成 和 的项目UNPIVOT列表PIVOT。然后将其添加到要执行的查询字符串中。动态版本的优点是如果您有一个不断变化的列表colors和/或names这将在运行时生成列表。

所有三个查询都将产生相同的结果:

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |
于 2012-11-14T10:33:34.497 回答
26

这通常需要您事先了解所有列和行标签。正如您在下面的查询中看到的那样,标签全部在 UNPIVOT 和 (re)PIVOT 操作中完全列出。

MS SQL Server 2012 架构设置

create table tbl (
    color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select 
    'Red' ,1 ,5 ,1 ,3 union all select
    'Green' ,8 ,4 ,3 ,5 union all select
    'Blue' ,2 ,2 ,9 ,1;

查询 1

select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p

结果

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

补充说明:

  1. 给定一个表名,您可以使用local-name()从sys.columns或 FOR XML技巧中确定所有列名。
  2. 您还可以使用 FOR XML 建立不同颜色的列表(或一列的值)。
  3. 以上可以组合成一个动态的sql批处理来处理任何表。
于 2012-11-14T02:58:55.903 回答
11

我想指出更多在 SQL 中转置列和行的解决方案。

第一个是 - 使用 CURSOR。尽管专业社区的普遍共识是远离 SQL Server 游标,但仍有一些情况建议使用游标。无论如何,Cursors 为我们提供了另一种将行转换为列的选项。

  • 纵向扩展

    与 PIVOT 类似,游标具有在数据集扩展以包含更多策略编号时追加更多行的动态能力。

  • 横向扩展

    与 PIVOT 不同,光标在此区域中表现出色,因为它能够扩展以包含新添加的文档,而无需更改脚本。

  • 性能细分

    使用 CURSOR 将行转换为列的主要限制是与使用游标相关的一个缺点——它们会带来显着的性能成本。这是因为 Cursor 会为每个 FETCH NEXT 操作生成一个单独的查询。

将行转换为列的另一种解决方案是使用 XML。

将行转换为列的 XML 解决方案基本上是 PIVOT 的最佳版本,因为它解决了动态列限制。

脚本的 XML 版本通过使用 XML 路径、动态 T-SQL 和一些内置函数(即 STUFF、QUOTENAME)的组合来解决此限制。

  • 纵向扩展

    与 PIVOT 和 Cursor 类似,可以在 XML 版本的脚本中检索新添加的策略,而无需更改原始脚本。

  • 横向扩展

    与 PIVOT 不同,新添加的文档可以在不更改脚本的情况下显示。

  • 性能细分

    在 IO 方面,XML 版本脚本的统计数据几乎与 PIVOT 相似——唯一的区别是 XML 对 dtTranspose 表进行了第二次扫描,但这次来自逻辑读取——数据缓存。

您可以在本文中找到有关这些解决方案的更多信息(包括一些实际的 T-SQL 示例): https ://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

于 2017-06-28T08:36:32.857 回答
10

基于bluefeet的这个解决方案,这里是一个使用动态 sql 生成转置表的存储过程。它要求所有字段都是数字,除了转置列(将成为结果表中的标题的列):

/****** Object:  StoredProcedure [dbo].[SQLTranspose]    Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for     transposing.
-- Parameters: @TableName - Table to transpose
--             @FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose] 
  -- Add the parameters for the stored procedure here
  @TableName NVarchar(MAX) = '', 
  @FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  DECLARE @colsUnpivot AS NVARCHAR(MAX),
  @query  AS NVARCHAR(MAX),
  @queryPivot  AS NVARCHAR(MAX),
  @colsPivot as  NVARCHAR(MAX),
  @columnToPivot as NVARCHAR(MAX),
  @tableToPivot as NVARCHAR(MAX), 
  @colsResult as xml

  select @tableToPivot = @TableName;
  select @columnToPivot = @FieldNameTranspose


  select @colsUnpivot = stuff((select ','+quotename(C.name)
       from sys.columns as C
       where C.object_id = object_id(@tableToPivot) and
             C.name <> @columnToPivot 
       for xml path('')), 1, 1, '')

  set @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename('+@columnToPivot+')
                  from '+@tableToPivot+' t
                  where '+@columnToPivot+' <> ''''
          FOR XML PATH(''''), TYPE)'

  exec sp_executesql @queryPivot, N'@colsResult xml out', @colsResult out

  select @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')

  set @query 
    = 'select name, rowid, '+@colsPivot+'
        from
        (
          select '+@columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+@columnToPivot+' order by '+@columnToPivot+') as rowid
          from '+@tableToPivot+'
          unpivot
          (
            value for name in ('+@colsUnpivot+')
          ) unpiv
        ) src
        pivot
        (
          sum(value)
          for '+@columnToPivot+' in ('+@colsPivot+')
        ) piv
        order by rowid'
  exec(@query)
END

您可以使用此命令提供的表对其进行测试:

exec SQLTranspose 'yourTable', 'color'
于 2015-11-11T02:21:47.590 回答
2

UnPivot首先将结果存储在CTE并使用CTEinPivot操作。

演示

with cte as
(
select 'Paul' as Name, color, Paul as Value 
 from yourTable
 union all
 select 'John' as Name, color, John as Value 
 from yourTable
 union all
 select 'Tim' as Name, color, Tim as Value 
 from yourTable
 union all
 select 'Eric' as Name, color, Eric as Value 
 from yourTable
 )

select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot 
(
  max(Value)
  for color IN ([Red], [Green], [Blue])
) as Dtpivot;
于 2017-08-02T10:32:28.110 回答
2

添加到上面@Paco Zarate 的绝妙答案,如果要转置具有多种类型列的表,则将其添加到第 39 行的末尾,因此它只转置int列:

and C.system_type_id = 56   --56 = type int

这是正在更改的完整查询:

select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
      C.name <> @columnToPivot and C.system_type_id = 56    --56 = type int
for xml path('')), 1, 1, '')

要查找其他system_type_id's,请运行以下命令:

select name, system_type_id from sys.types order by name
于 2018-01-19T18:25:38.237 回答
2

这种方式将表中文件(列)中的所有数据转换为记录(行)。

Declare @TableName  [nvarchar](128)
Declare @ExecStr    nvarchar(max)
Declare @Where      nvarchar(max)
Set @TableName = 'myTableName'
--Enter Filtering If Exists
Set @Where = ''

--Set @ExecStr = N'Select * From '+quotename(@TableName)+@Where
--Exec(@ExecStr)

Drop Table If Exists #tmp_Col2Row

Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)

Set @ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select @ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(@TableName)+@Where+Char(10)+' Union All '
         from sys.columns as C
         where (C.object_id = object_id(@TableName)) 
         for xml path(''))
Select @ExecStr = Left(@ExecStr,Len(@ExecStr)-Len(' Union All '))
--Print @ExecStr
Exec (@ExecStr)

Select * From #tmp_Col2Row
Go
于 2018-09-16T19:00:52.030 回答
1

我喜欢分享我用来转置基于 +bluefeet 答案的拆分文本的代码。在这种方法中,我被实现为 MS SQL 2005中的一个过程

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit @InputToSplit VARCHAR(8000)
    ,@Delimeter VARCHAR(8000) = ','
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @colsUnpivot AS NVARCHAR(MAX)
        ,@query AS NVARCHAR(MAX)
        ,@queryPivot AS NVARCHAR(MAX)
        ,@colsPivot AS NVARCHAR(MAX)
        ,@columnToPivot AS NVARCHAR(MAX)
        ,@tableToPivot AS NVARCHAR(MAX)
        ,@colsResult AS XML

    SELECT @tableToPivot = '#tempSplitedTable'

    SELECT @columnToPivot = 'col_number'

    CREATE TABLE #tempSplitedTable (
        col_number INT
        ,col_value VARCHAR(8000)
        )

    INSERT INTO #tempSplitedTable (
        col_number
        ,col_value
        )
    SELECT ROW_NUMBER() OVER (
            ORDER BY (
                    SELECT 100
                    )
            ) AS RowNumber
        ,item
    FROM [DB].[ESCHEME].[fnSplit](@InputToSplit, @Delimeter)

    SELECT @colsUnpivot = STUFF((
                SELECT ',' + quotename(C.NAME)
                FROM [tempdb].sys.columns AS C
                WHERE C.object_id = object_id('tempdb..' + @tableToPivot)
                    AND C.NAME <> @columnToPivot
                FOR XML path('')
                ), 1, 1, '')

    SET @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename(' + @columnToPivot + ')
                  from ' + @tableToPivot + ' t
                  where ' + @columnToPivot + ' <> ''''
          FOR XML PATH(''''), TYPE)'

    EXEC sp_executesql @queryPivot
        ,N'@colsResult xml out'
        ,@colsResult OUT

    SELECT @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')

    SET @query = 'select name, rowid, ' + @colsPivot + '
        from
        (
          select ' + @columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + @columnToPivot + ' order by ' + @columnToPivot + ') as rowid
          from ' + @tableToPivot + '
          unpivot
          (
            value for name in (' + @colsUnpivot + ')
          ) unpiv
        ) src
        pivot
        (
          MAX(value)
          for ' + @columnToPivot + ' in (' + @colsPivot + ')
        ) piv
        order by rowid'

    EXEC (@query)

    DROP TABLE #tempSplitedTable
END
GO

我正在将此解决方案与有关如何在没有 order by ( SQLAuthority.com ) 的情况下对行进行排序的信息以及 MSDN ( social.msdn.microsoft.com )上的拆分功能的信息混合使用

当你执行程序时

DECLARE @RC int
DECLARE @InputToSplit varchar(MAX)
DECLARE @Delimeter varchar(1)

set @InputToSplit = 'hello|beautiful|world'
set @Delimeter = '|'

EXECUTE @RC = [TransposeSplit] 
   @InputToSplit
  ,@Delimeter
GO

你得到下一个结果

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world
于 2016-05-03T15:33:59.203 回答
0

我能够使用 Paco Zarate 的解决方案,而且效果很好。我确实必须添加一行(“SET ANSI_WARNINGS ON”),但这可能是我使用或调用它的方式所独有的。我的使用有问题,希望有人可以帮助我:

该解决方案仅适用于实际的 SQL 表。我用一个临时表和一个内存(声明的)表尝试了它,但它不适用于这些表。所以在我的调用代码中,我在我的 SQL 数据库上创建了一个表,然后调用 SQLTranspose。再次,它工作得很好。这正是我想要的。这是我的问题:

为了使整体解决方案真正动态化,我需要创建该表,在其中临时存储准备“即时”发送到 SQLTranspose 的信息,然后在调用 SQLTranspose 后删除该表。表删除给我的最终实施计划带来了问题。代码需要从最终用户应用程序(Microsoft Access 表单/菜单上的按钮)运行。当我使用此 SQL 过程(创建 SQL 表、调用 SQLTranspose、删除 SQL 表)时,最终用户应用程序遇到错误,因为使用的 SQL 帐户没有删除表的权限。

所以我认为有几个可能的解决方案:

  1. 找到一种方法使 SQLTranspose 与临时表或声明的表变量一起工作。

  2. 找出另一种不需要实际 SQL 表的行和列转置方法。

  3. 找出允许我的最终用户使用的 SQL 帐户删除表的适当方法。它是编码到我的 Access 应用程序中的单个共享 SQL 帐户。似乎权限是无法授予的 dbo 类型特权。

我认识到其中一些可能需要另一个单独的线程和问题。但是,由于一种解决方案可能只是一种不同的方式来进行行和列的转置,因此我将在此线程中发表我的第一篇文章。

编辑:正如 Paco 建议的那样,我也确实在最后的第 6 行用 max(value) 替换了 sum(value)。

编辑:

我想出了一些对我有用的东西。我不知道这是否是最好的答案。

我有一个只读用户帐户,用于执行存储过程并因此从数据库生成报告输出。由于我创建的 SQLTranspose 函数仅适用于“合法”表(不是已声明的表,也不是临时表),因此我必须想办法让只读用户帐户创建(然后删除)表.

我推断,出于我的目的,允许用户帐户创建表是可以的。用户仍然无法删除该表。我的解决方案是创建一个授权用户帐户的架构。然后,每当我创建、使用或删除该表时,请使用指定的模式引用它。

我首先从“sa”或“sysadmin”帐户发出此命令:CREATE SCHEMA ro AUTHORIZATION

每当我提到我的“tmpoutput”表时,我都会像这个例子一样指定它:

删除表 ro.tmpoutput

于 2019-04-04T12:48:01.830 回答