在SQL中转置列和行的简单方法?


110

如何在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 在这种情况下似乎太复杂了。


1
stackoverflow.com/questions/10699997/… 是一个有点类似的问题。
蒂姆·迪尔伯恩

Answers:


145

您可以通过多种方式转换此数据。在您的原始文章中,您说过PIVOT这种情况似乎太复杂了,但是可以使用SQL Server中的UNPIVOTPIVOT函数很容易地应用它。

但是,如果您无权访问这些功能,则可以使用UNION ALL来复制UNPIVOT该功能,然后使用以下CASE语句将其复制到以下集合功能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);

合并所有,汇总和CASE版本:

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执行与UNPIVOT通过变换列中的数据Paul, John, Tim, Eric进行分离。然后,将聚合函数sum()与该case语句一起使用,以获取每个函数的新列color

Unpivot和Pivot静态版本:

SQL Server中的UNPIVOTPIVOT函数都使此转换更加容易。如果知道要转换的所有值,则可以将它们硬编码为静态版本以得到结果:

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表来生成物品的清单UNPIVOTPIVOT。然后将其添加到要执行的查询字符串中。动态版本的加号是如果您具有更改的列表colors和/或names它将在运行时生成列表。

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

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

我应该强调,我想要一种将查询结果转置到数据库上的方法。一个简单的转置类型命令,可以对查询的结果集执行该命令,而无需了解有关查询本身或从中提取信息的列,表等的任何信息。像这样:(TRANSPOSE(SELECT ......))在上面给出的示例中,假设第一个表是一个非常复杂的查询所生成的结果集,该查询涉及多个表,联合,数据透视等。但是结果是简单表。我只想在此结果中翻转带有行的列。
edezzie 2012年

我将结果传递给ac#DataTable。我可以构建一个简单的方法'Transpose(Datatable dt)'来执行此操作,但是SQL中有什么可以执行此操作。
edezzie 2012年

@edezzie在SQL中没有简单的方法可以做到这一点。您可能会更改动态版本以传递表名,并从系统表中提取信息。
塔林

您为什么没有将这个绝妙的答案标记为答案呢?给@bluefeet一块勋章!
Fandango68

可以很好地从表unpivot([[Paul],[John],[Tim],[Eric])中的名称值)向上枢轴([[Red],[Green]中的颜色的max(value))中选择* ,[[Blue]))p,但是可以将它从Table unpivot([[Paul],[John],[Tim],[Eric])中名称的值)向上枢轴(max(value)的值)写为select *。 (从表名中选择颜色)p中的颜色(而不是输入带引号的值)无法直接使用选择查询来检索值。
Mogli

20

这通常需要您事先了解所有列和行标签。正如您在下面的查询中看到的那样,所有标签均在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批处理以处理任何表。

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-transpose-rows-into-columns/


8

基于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'

Argg。但是我想要一个所有字段都为nvarchar(max)的字段
hamish

1
对于所有字段,nvarchar(max)只需在末尾的第6行中将sum(value)更改为max(value)
Paco Zarate

2

UnPivot首先要做的是将结果存储在in中CTE并使用CTEin Pivot操作。

演示版

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;

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的,请运行以下命令:

select name, system_type_id from sys.types order by name

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

我将此解决方案与有关如何排序行而不排序的信息(SQLAuthority.com)和MSDN上的split函数(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

1

这样,将表中Filelds(Columns)中的所有数据转换为Record(行)。

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

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


我想出了一些对我有用的东西。我不知道这是否是最佳答案。
CraigD
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.