为啥带有“额外”列的数据透视不能合并结果

Posted

技术标签:

【中文标题】为啥带有“额外”列的数据透视不能合并结果【英文标题】:Why pivot with "extra" columns doesn't combine results为什么带有“额外”列的数据透视不能合并结果 【发布时间】:2015-03-07 00:46:33 【问题描述】:

我知道你们中的许多人都观察到了这种行为,但我想知道是否有人可以解释原因。当我创建一个小表来创建使用数据透视函数的示例时,我得到了我期望的结果:

CREATE TABLE dbo.AverageFishLength
    (
      Fishtype VARCHAR(50) ,
      AvgLength DECIMAL(8, 2) ,
      FishAge_Years INT
    )
INSERT  INTO dbo.AverageFishLength
        ( Fishtype, AvgLength, FishAge_Years )
VALUES  ( 'Muskie', 32.75, 3 ),
        ( 'Muskie', 37.5, 4 ),
        ( 'Muskie', 39.75, 5 ),
        ( 'Walleye', 16.5, 3 ),
        ( 'Walleye', 18.25, 4 ),
        ( 'Walleye', 20.0, 5 ),
        ( 'Northern Pike', 20.75, 3 ),
        ( 'Northern Pike', 23.25, 4 ),
        ( 'Northern Pike', 26.0, 5 );

这是透视查询:

SELECT  Fishtype ,
        [3] AS [3 Years Old] ,
        [4] AS [4 Years Old] ,
        [5] AS [5 Years Old]
FROM    dbo.AverageFishLength   PIVOT( SUM(AvgLength) 
                                FOR FishAge_Years IN ( [3], [4], [5] ) ) AS PivotTbl

结果如下:

但是,如果我创建带有标识列的表,则结果会分成不同的行:

DROP TABLE dbo.AverageFishLength
CREATE TABLE dbo.AverageFishLength
    (
      ID INT IDENTITY(1,1) ,
      Fishtype VARCHAR(50) ,
      AvgLength DECIMAL(8, 2) ,
      FishAge_Years INT
    )
INSERT  INTO dbo.AverageFishLength
        ( Fishtype, AvgLength, FishAge_Years )
VALUES  ( 'Muskie', 32.75, 3 ),
        ( 'Muskie', 37.5, 4 ),
        ( 'Muskie', 39.75, 5 ),
        ( 'Walleye', 16.5, 3 ),
        ( 'Walleye', 18.25, 4 ),
        ( 'Walleye', 20.0, 5 ),
        ( 'Northern Pike', 20.75, 3 ),
        ( 'Northern Pike', 23.25, 4 ),
        ( 'Northern Pike', 26.0, 5 );

完全相同的查询:

SELECT  Fishtype ,
        [3] AS [3 Years Old] ,
        [4] AS [4 Years Old] ,
        [5] AS [5 Years Old]
FROM    dbo.AverageFishLength   PIVOT( SUM(AvgLength) 
                                FOR FishAge_Years IN ( [3], [4], [5] ) ) AS PivotTbl

不同的结果:

在我看来,查询中正在使用 ID 列,即使它根本没有出现在查询中。这几乎就像它隐式包含在查询中,但未显示在结果集中。

谁能解释为什么会这样?

【问题讨论】:

我应该补充一点,我知道如何绕过它,使用子查询或 row_number... 我真的只是对它发生的原因感兴趣。 这个问题让我饿了 【参考方案1】:

发生这种情况是因为 ID 列对于每一行都是唯一的,并且因为您正在直接查询表(没有子查询),所以该列包含在聚合函数所需的 GROUP BY 中。

MSDN docs about FROM 的文档声明如下:

table_source PIVOT <pivot_clause>

指定 table_source 基于 pivot_column 进行旋转。 table_source 是一个表或表表达式。输出是一个表,其中包含 table_source 的所有列,除了 pivot_column 和 value_column。 table_source 的列,除了 pivot_column 和 value_column 之外,被称为枢轴运算符的分组列

PIVOT 对输入表执行关于分组列的分组操作,并返回每组一行。此外,对于出现在 input_table 的 pivot_column 中的 column_list 中指定的每个值,输出都包含一列。

您的版本基本上是在说SELECT * FROM yourtable 和 PIVOT 该数据。即使ID 列不在您的最终SELECT 列表中,它也是查询中的一个分组元素。如果您将 PIVOT 与“pre-PIVOT”示例进行比较以显示您将看到您的版本。此示例使用 CASE 表达式和聚合函数:

SELECT Fishtype,
  sum(case when FishAge_Years = 3 then AvgLength else 0 end) as [3],
  sum(case when FishAge_Years = 4 then AvgLength else 0 end) as [4],
  sum(case when FishAge_Years = 5 then AvgLength else 0 end) as [5]
FROM dbo.AverageFishLength
GROUP BY Fishtype, ID;

结果会出现偏差,因为即使您在最终列表中没有 ID,它仍然被用于分组,并且由于它们是唯一的,因此您会得到多行。

使用 PIVOT 时解决此问题的最简单方法是使用子查询:

SELECT Fishtype ,
        [3] AS [3 Years Old] ,
        [4] AS [4 Years Old] ,
        [5] AS [5 Years Old]
FROM
(
  SELECT Fishtype, 
    AvgLength, 
    FishAge_Years
  FROM    dbo.AverageFishLength
) d
PIVOT
( 
  SUM(AvgLength) 
  FOR FishAge_Years IN ( [3], [4], [5] ) 
) AS PivotTbl;

在此版本中,您只返回表中实际需要和想要的列 - 这不包括 ID,因此它不会用于对您的数据进行分组。

【讨论】:

好的,我明白了。如果它在语法中更透明,或者如果您可以通过从 SELECT 中排除某些列而不是必须执行子查询来从表中排除某些列,那就太好了。感谢您的详尽解释。 @Dave.Gugg 它实际上在文档中,请参阅我的编辑。它隐藏在 FROM 的文档中。

以上是关于为啥带有“额外”列的数据透视不能合并结果的主要内容,如果未能解决你的问题,请参考以下文章

多索引数据框到带有新列的数据透视表

为啥在创建数据透视表时,Excel2013无法勾选“将此数据添加到数据模型?

如何添加多对多的额外数据透视列?

在子查询中为数据透视列的值实现 WHERE 子句?

排除元素的 Excel 数据透视表小计

具有两行到列的数据透视表