基于 SQL Server 中的一列透视多列

Posted

技术标签:

【中文标题】基于 SQL Server 中的一列透视多列【英文标题】:Pivot multiple columns based on one column in SQL Server 【发布时间】:2013-08-04 02:42:31 【问题描述】:

我在 SQL Server 2008R2 中有以下源表和目标表。如何在 TSQL 中进行数据透视以将 SourceTbl 转换为 DestTbl?希望 empIndex 能以某种方式在支点上有所帮助。

SourceTbl

empId    empIndex    empState    empStDate    empEndDate
========================================================
10        1           AL          1/1/2012     12/1/2012
10        2           FL          2/1/2012     2/1/2013
15        1           FL          3/20/2012    1/1/2099

DestTbl

empId    empState1  empState1StDate    empState1EndDt    empState2  empState2StDate    empState2EndDt
=========================================================================================================
10        AL         1/1/2012           12/1/2012         FL         2/1/2012           2/1/2013
15        FL         3/20/2012          1/1/2099          NULL       NULL               NULL

【问题讨论】:

pivot 在 msaccess 中也称为变换。您的问题是独一无二的,因为它在结果单元格中也有文本(不是整数)。聚合函数仍然必须应用,在这种情况下 MIN() 应该在 Text 值上运行良好,即使只有 1 个文本值。 【参考方案1】:

由于您使用的是 SQL Server,因此您可以通过多种不同的方式将行转换为列。您可以将聚合函数与 CASE 表达式一起使用:

select empid,
  max(case when empindex = 1 then empstate end) empState1,
  max(case when empindex = 1 then empStDate end) empStDate1,
  max(case when empindex = 1 then empEndDate end) empEndDate1,
  max(case when empindex = 2 then empstate end) empState2,
  max(case when empindex = 2 then empStDate end) empStDate2,
  max(case when empindex = 2 then empEndDate end) empEndDate2
from sourcetbl
group by empid;

见SQL Fiddle with Demo。

如果您想使用 PIVOT 函数来获取结果,那么我建议您首先取消旋转列 empStateempStDateempEndDate,以便您首先拥有多行。您可以使用 UNPIVOT 函数或 CROSS APPLY 来转换代码将是的数据:

select empid, col+cast(empindex as varchar(10)) col,  value
from sourcetbl
cross apply
(
  select 'empstate', empstate union all
  select 'empstdate', convert(varchar(10), empstdate, 120) union all
  select 'empenddate', convert(varchar(10), empenddate, 120)
) c (col, value);

见Demo。取消数据透视后,您可以应用 PIVOT 函数,这样最终代码将是:

select empid,
  empState1, empStDate1, empEndDate1,
  empState2, empStDate2, empEndDate2
from 
(
  select empid, col+cast(empindex as varchar(10)) col,  value
  from sourcetbl
  cross apply
  (
    select 'empstate', empstate union all
    select 'empstdate', convert(varchar(10), empstdate, 120) union all
    select 'empenddate', convert(varchar(10), empenddate, 120)
  ) c (col, value)
) d
pivot
(
  max(value)
  for col in (empState1, empStDate1, empEndDate1,
              empState2, empStDate2, empEndDate2)
) piv;

见SQL Fiddle with Demo。

如果empindex 数量有限,上述版本会很好用,但如果没有,则可以使用动态 SQL:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col+cast(empindex as varchar(10))) 
                    from SourceTbl
                    cross apply
                    (
                      select 'empstate', 1 union all
                      select 'empstdate', 2 union all
                      select 'empenddate', 3
                    ) c (col, so)
                    group by col, so, empindex
                    order by empindex, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT empid,' + @cols + ' 
            from 
            (
                select empid, col+cast(empindex as varchar(10)) col,  value
                from sourcetbl
                cross apply
                (
                  select ''empstate'', empstate union all
                  select ''empstdate'', convert(varchar(10), empstdate, 120) union all
                  select ''empenddate'', convert(varchar(10), empenddate, 120)
                ) c (col, value)
            ) x
            pivot 
            (
                max(value)
                for col in (' + @cols + ')
            ) p '

execute sp_executesql @query;

见 SQL Fiddle with Demo

您可以使用这些查询 INSERT INTO 您的DestTbl,或者您现在可以通过查询来获得所需的结果,而不是以这种格式存储数据。

这些查询以以下格式放置数据:

| EMPID | EMPSTATE1 | EMPSTDATE1 | EMPENDDATE1 | EMPSTATE2 | EMPSTDATE2 | EMPENDDATE2 |
---------------------------------------------------------------------------------------
|    10 |        AL | 2012-01-01 |  2012-12-01 |        FL | 2012-02-01 |  2013-02-01 |
|    15 |        FL | 2012-03-20 |  2099-01-01 |    (null) |     (null) |      (null) |

【讨论】:

我之前已经看到 FOR XML 使用了很多......但是为了最好地使用:STUFF QUOTENAME 和 NVARCHAR(MAX) 在 PIVOT 中的最佳用途:) MSACCESS 称之为TRANSFORM(不知道为什么该命令从未进入 SQL 服务器)【参考方案2】:

哇,这比我想象的要复杂,但我确实让它工作得很好!谢谢。这是我的最终版本。 TextKey 包含要转换为列的数据,TextValue 是每个单元格内的值。

DECLARE @cols AS NVARCHAR(MAX),
        @query AS NVARCHAR(MAX)


select @cols = STUFF((SELECT distinct ', ' + QUOTENAME(TextKey) 
                    from #SourceTbl
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT FromEntityID, DisplayName, ' + @cols + ' 
              FROM 
              (
                  select FromEntityID, DisplayName, TextKey, TextValue
                  from #SourceTbl
              ) x
              pivot 
              (
                  min(TextValue)
                  for TextKey in (' + @cols + ')
              ) p 
              ORDER BY FromEntityID
              '

execute(@query)

【讨论】:

以上是关于基于 SQL Server 中的一列透视多列的主要内容,如果未能解决你的问题,请参考以下文章

基于Javascript中的一列从多列中删除重复项

SQL Server 全文索引介绍(转载)

SQL Server 使用全文索引进行页面搜索

Sql Server 主键 外键约束

透视多列sql server

SQL Server2012创建约束图解