了解T-SQL中的PIVOT函数


82

我对SQL非常陌生。

我有一个这样的表:

ID | TeamID | UserID | ElementID | PhaseID | Effort
-----------------------------------------------------
1  |   1    |  1      |   3       |  5     |   6.74
2  |   1    |  1      |   3       |  6     |   8.25
3  |   1    |  1      |   4       |  1     |   2.23
4  |   1    |  1      |   4       |  5     |   6.8
5  |   1    |  1      |   4       |  6     |   1.5

我被告知要获取这样的数据

ElementID | PhaseID1 | PhaseID5 | PhaseID6
--------------------------------------------
    3     |   NULL   |   6.74   |   8.25
    4     |   2.23   |   6.8    |   1.5

我了解我需要使用PIVOT功能。但是不清楚。如果有人可以在上述情况下进行解释,那将是非常有帮助的(或任何替代方法,如果有的话)

Answers:


108

APIVOT用于将数据从一列旋转到多列。

对于您的示例,这是STATIC Pivot,这意味着您可以对要旋转的列进行硬编码:

create table temp
(
  id int,
  teamid int,
  userid int,
  elementid int,
  phaseid int,
  effort decimal(10, 5)
)

insert into temp values (1,1,1,3,5,6.74)
insert into temp values (2,1,1,3,6,8.25)
insert into temp values (3,1,1,4,1,2.23)
insert into temp values (4,1,1,4,5,6.8)
insert into temp values (5,1,1,4,6,1.5)

select elementid
  , [1] as phaseid1
  , [5] as phaseid5
  , [6] as phaseid6
from
(
  select elementid, phaseid, effort
  from temp
) x
pivot
(
  max(effort)
  for phaseid in([1], [5], [6])
)p

这是带有工作版本的SQL演示

这也可以通过动态PIVOT完成,您可以在其中动态创建列列表并执行PIVOT。

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

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(c.phaseid) 
            FROM temp c
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT elementid, ' + @cols + ' from 
            (
                select elementid, phaseid, effort
                from temp
           ) x
            pivot 
            (
                 max(effort)
                for phaseid in (' + @cols + ')
            ) p '


execute(@query)

两者的结果:

ELEMENTID   PHASEID1    PHASEID5    PHASEID6
3           Null        6.74        8.25
4           2.23        6.8         1.5

1
谢谢。我只需要PhaseID在QUOTENAME之前进行硬编码即可。对?
Web-E

1
在QUOTENAME中,您必须确定需要从哪一列获取值。那是你的要求吗?
塔林

为了使STUFF解决方案可以使用奇怪的列名(空格,括号等),我也必须这样做SELECT distinct '],[',并且在声明的末尾1, 2, '') + ']'
Nat

@ Web-E,不幸的是。解决方法是,您可以在应用程序中编写查询字符串,也可以在存储过程中使用动态SQL。
MarcoM

7

这些是非常基本的示例。

SQL SERVER – PIVOT和UNPIVOT表示例

产品表上方链接的示例:

SELECT PRODUCT, FRED, KATE
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
 PIVOT (SUM(QTY) FOR CUST IN (FRED, KATE)) AS pvt
ORDER BY PRODUCT

呈现:

 PRODUCT FRED  KATE
 --------------------
 BEER     24    12
 MILK      3     1
 SODA   NULL     6
 VEG    NULL     5

在SQL Server中的数据透视表博客文章中可以找到类似的示例。一个简单的例子


还要注意,如果从源表中拉出额外的数字列,则数据透视表会将结果分成多行。示例SELECT CUST, VEG, SODA FROM (SELECT rand() as x, CUST, PRODUCT, QTY FROM Product) up PIVOT ( SUM(x) FOR PRODUCT IN (VEG, SODA) ) AS pvt ORDER BY CUST GO 为了使此工作正常,您必须qty从源中删除列
Raheel Hasan

4

我在这里添加了没有人提及的内容。

pivot当源具有3列时,此功能非常有用:一列用于aggregate,一列通过传播为,一列for作为row分布的枢轴。在产品示例中为QTY, CUST, PRODUCT

但是,如果源中有更多列,则会将结果分成多行,而不是根据每个附加列的唯一值将每个枢轴分成一行(例如 Group By将在简单查询中一样)。

请看此示例,我在源表中添加了一个timestamp列:

在此处输入图片说明

现在看到它的影响:

SELECT CUST, MILK

FROM Product
-- FROM (SELECT CUST, Product, QTY FROM PRODUCT) p
PIVOT (
    SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt

ORDER BY CUST

在此处输入图片说明


为了解决此问题,您可以像上面每个人一样将子查询作为源进行拉取-只有3列(这并不总是适用于您的方案,假设您需要where为时间戳记一个条件)。

第二种解决方案是使用agroup by并再次对透视列值进行求和。

SELECT 
CUST, 
sum(MILK) t_MILK

FROM Product
PIVOT (
    SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt

GROUP BY CUST
ORDER BY CUST

GO

在此处输入图片说明


4

枢轴用于将数据集中的列之一从行转换为列(通常称为“扩展列”)。在您给出的示例中,这意味着将PhaseID行转换为一组列,其中每个不同的值都有一个列PhaseID在这种情况下可以包含-1、5和6。

在您给出的示例中,这些透视值通过列进行分组ElementID

通常,您还需要提供某种形式的聚合,从而为您提供由扩展值PhaseID)和分组值ElementID)的交集引用的。尽管在给出的示例中将使用的聚合尚不清楚,但涉及到该Effort列。

一旦完成此枢纽操作,就使用分组散布列查找汇总值。或者在您的情况下,ElementID然后进行PhaseIDX查找Effort

使用分组,扩展,聚合术语,您通常会看到枢轴的示例语法为:

WITH PivotData AS
(
    SELECT <grouping column>
        , <spreading column>
        , <aggregation column>
    FROM <source table>
)
SELECT <grouping column>, <distinct spreading values>
FROM PivotData
    PIVOT (<aggregation function>(<aggregation column>)
        FOR <spreading column> IN <distinct spreading values>));

给出了图形化的解释,说明如果进一步帮助的话,分组,散布和聚合列如何从源转换为数据透视表。


3

设置兼容性错误

在使用数据透视功能之前先使用此功能

ALTER DATABASE [dbname] SET COMPATIBILITY_LEVEL = 100  

3
    SELECT <non-pivoted column>,
    [first pivoted column] AS <column name>,
    [second pivoted column] AS <column name>,
    ...
    [last pivoted column] AS <column name>
FROM
    (<SELECT query that produces the data>)
    AS <alias for the source query>
PIVOT
(
    <aggregation function>(<column being aggregated>)
FOR
[<column that contains the values that will become column headers>]
    IN ( [first pivoted column], [second pivoted column],
    ... [last pivoted column])
) AS <alias for the pivot table>
<optional ORDER BY clause>;

USE AdventureWorks2008R2 ;
GO
SELECT DaysToManufacture, AVG(StandardCost) AS AverageCost 
FROM Production.Product
GROUP BY DaysToManufacture;

    DaysToManufacture          AverageCost
0                          5.0885
1                          223.88
2                          359.1082
4                          949.4105

    -- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days, 
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost 
    FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;




Here is the result set.
Cost_Sorted_By_Production_Days    0         1         2           3       4       
AverageCost                       5.0885    223.88    359.1082    NULL    949.4105

1
为什么<SELECT query that produces the data>不只是桌子?
Raheel Hasan
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.