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

时间:2012-11-14 02:44:20

标签: sql sql-server tsql pivot

如何在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似乎过于复杂。

9 个答案:

答案 0 :(得分:125)

您可以通过多种方式转换此数据。在您的原始帖子中,您声明PIVOT对于此方案似乎过于复杂,但可以使用SQL Server中的UNPIVOT and PIVOT函数轻松应用它。

但是,如果您无法访问这些功能,可以使用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);

Union All,Aggregate和CASE Version:

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 with Demo

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 with Demo

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

动态数据透视版

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

PIVOT

请参阅SQL Fiddle with Demo

动态版本同时查询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) ,然后查询yourtable表,以生成sys.columnsUNPIVOT的项目列表。然后将其添加到要执行的查询字符串中。动态版本的优点是,如果您有更改的PIVOT和/或colors列表,则会在运行时生成列表。

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

names

答案 1 :(得分:18)

这通常要求您事先知道所有列和行标签。正如您在下面的查询中所看到的,标签全部列在UNPIVOT和(重新)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

<强> Results

| 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 trickery确定所有列名。
  2. 您还可以使用FOR XML构建不同颜色(或一列的值)的列表。
  3. 以上内容可以组合成动态sql批处理来处理任何表。

答案 2 :(得分:10)

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

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

  • 垂直扩展

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

  • 横向扩展

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

  • 效果细分

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

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

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

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

  • 垂直扩展

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

  • 横向扩展

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

  • 效果细分

    就IO而言,脚本的XML版本的统计信息几乎与PIVOT类似 - 唯一的区别是XML具有dtTranspose表的第二次扫描,但这次是来自逻辑读取 - 数据缓存。 / p>

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

答案 3 :(得分:7)

基于来自solutionbluefeet,这是一个使用动态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'

答案 4 :(得分:2)

我先做UnPivot并将结果存储在CTE并使用CTE Pivot操作。

Demo

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;

答案 5 :(得分:2)

添加到@Paco Zarate上面的极好答案,如果你想转置一个包含多种类型列的表,然后将其添加到第39行的末尾,那么它只会转换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 ,请运行以下命令:

onClickNode: function (node) {
      $(document).keyup(function (e) {
                            if (e.which == 13) {
                                $.ajax({
                                        url: '@Url.Action("EditNode", "Admin")',
                                        type: 'POST',
                                        data: { id: node.data.id,name:node.data.name},
                                        success: function (data) {
                                          if (data.Success) {
                                             // do something
                                                             }
                                             //do something
                                          });

                                                                  },
                                      });     
                                                 }
                                      });
                        }

答案 6 :(得分: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上的拆分函数(social.msdn.microsoft.com

混合使用

执行prodecure

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

答案 7 :(得分:0)

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

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

答案 8 :(得分: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”帐户发出此命令: 创建模式授权

任何时候我参考“ tmpoutput”表时,都可以像下面的示例一样指定它:

删除表ro.tmpoutput