为啥我的 PIVOT 查询使用不包含 NULL 的数据集产生 NULL 结果?

Posted

技术标签:

【中文标题】为啥我的 PIVOT 查询使用不包含 NULL 的数据集产生 NULL 结果?【英文标题】:Why does my PIVOT query produce NULL results with a dataset that contains no NULLs?为什么我的 PIVOT 查询使用不包含 NULL 的数据集产生 NULL 结果? 【发布时间】:2022-01-06 00:40:02 【问题描述】:

我有一个设备数据库,其中包含有关设备的数据,包括名称、位置以及许多数字和字符串属性。我正在尝试执行查询,仅将具有数值的属性拉入数据透视表。

当我平面查询数据时,数据看起来很好。但是当我在同一个数据集上运行PIVOT 查询时,所有结果都是NULL

设置:

/* database setup and data insert */

USE [master] ;
GO

IF EXISTS ( SELECT * FROM sys.databases WHERE [name] = N'DeviceDatabase' )
BEGIN
    ALTER DATABASE [DeviceDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE ;
    DROP DATABASE [DeviceDatabase] ;
END
CREATE DATABASE [DeviceDatabase] ;
GO

USE [DeviceDatabase] ;
GO

CREATE TABLE dbo.Devices
(
    [id]                        INT             NOT NULL    IDENTITY (1,1)
        CONSTRAINT      [PK_Devices]            PRIMARY KEY CLUSTERED
    , [device_name]             VARCHAR(50)     NOT NULL
    , [device_display_name]     VARCHAR(50)     NOT NULL
    , [device_location]         VARCHAR(50)     NULL
) ;

CREATE TABLE dbo.DeviceGroups
(
    [id]                        INT             NOT NULL    IDENTITY (1,1)
        CONSTRAINT      [PK_DeviceGroups]       PRIMARY KEY CLUSTERED
    , [device_group_name]       VARCHAR(50)     NOT NULL
) ;

CREATE TABLE dbo.DeviceDeviceGroups
(
    [id]                        INT             NOT NULL    IDENTITY (1,1)
        CONSTRAINT      [PK_DeviceDeviceGroups] PRIMARY KEY CLUSTERED
    , [device_id]               INT             NOT NULL
        CONSTRAINT      [FK_DeviceDeviceGroups_Devices]
        FOREIGN KEY REFERENCES dbo.Devices ( [id] )
    , [group_id]                INT             NOT NULL
        CONSTRAINT      [FK_DeviceDeviceGroups_DeviceGroups]
        FOREIGN KEY REFERENCES dbo.DeviceGroups ( [id] )
) ;

CREATE TABLE dbo.DeviceAttributes
(
    [id]                        INT             NOT NULL    IDENTITY (1,1)
        CONSTRAINT      [PK_DeviceAttributes]   PRIMARY KEY CLUSTERED
    , [device_id]               INT             NOT NULL
        CONSTRAINT      [FK_DeviceAttributes_Devices]
        FOREIGN KEY REFERENCES dbo.Devices ( [id] )
    , [attribute_name]          VARCHAR(50)     NOT NULL
    , [attribute_value]         VARCHAR(50)     NULL
) ;
GO

INSERT
    INTO        dbo.Devices
                ( [device_name], [device_display_name], [device_location] )
    VALUES      ( 'dev001', 'Device 1', 'Location A' )
                , ( 'dev002', 'Device 2', 'Location A' )
                , ( 'dev003', 'Device 3', 'Location B' )
                , ( 'dev004', 'Device 4', 'Location B' ) ;

INSERT
    INTO        dbo.DeviceGroups
                ( [device_group_name] )
    VALUES      ( 'Group 1A' )  
                , ( 'Group 1B' ) ;

INSERT
    INTO        dbo.DeviceDeviceGroups
                ( [device_id], [group_id] )
    VALUES      ( 1, 1 )
                , ( 2, 1 )
                , ( 3, 1 )
                , ( 4, 1 ) ;

INSERT
    INTO        dbo.DeviceAttributes
                ( [device_id], [attribute_name], [attribute_value] )
    VALUES      ( 1, 'attrib #1', '0.10' )
                , ( 1, 'attrib #2', '0.02' )
                , ( 1, 'attrib #3', '0.07' )
                , ( 1, 'attrib #4', '0.02' )
                , ( 2, 'attrib #1', '0.16' )
                , ( 2, 'attrib #2', '0.05' )
                , ( 2, 'attrib #3', '0.12' )
                , ( 2, 'attrib #4', '0.04' )
                , ( 3, 'attrib #1', '0.15' )
                , ( 3, 'attrib #2', '0.05' )
                , ( 3, 'attrib #3', '0.07' )
                , ( 3, 'attrib #4', '0.06' )
                , ( 4, 'attrib #1', '0.10' )
                , ( 4, 'attrib #2', '0.03' )
                , ( 4, 'attrib #3', '0.07' )
                , ( 4, 'attrib #4', '0.03' ) ;
GO

令人讨厌的是attribute_value 列是字符串而不是数字,但并非所有属性本质上都是数字的。 (这是供应商的架构。)

当我对表执行平面查询(使用 CTE)时,我会得到一组完整的属性值。

查询:

/* flat query */

USE [DeviceDatabase] ;
GO

DECLARE     @PrinterGroup   AS VARCHAR(50) ;
SET         @PrinterGroup   = 'Group 1A' ;

WITH cte_GroupedDevices AS
(
    SELECT          d.[id] AS [device_id]
                    , d.[device_name]
                    , d.[device_display_name]
                    , d.[device_location]
                    , dg.[device_group_name]
    FROM            dbo.Devices AS d
        INNER JOIN  dbo.DeviceDeviceGroups AS ddg
            ON      d.[id] = ddg.[device_id]
        INNER JOIN  dbo.DeviceGroups AS dg
            ON      ddg.[group_id] = dg.[id]
    WHERE           dg.[device_group_name] = @PrinterGroup
)
, cte_AttributedDevices AS
(
    SELECT          gd.[device_name]
                    , gd.[device_display_name]
                    , gd.[device_group_name]
                    , gd.[device_location]
                    , da.[attribute_name]                   
                    , CAST ( da.[attribute_value] AS DECIMAL (5,2) ) AS [attribute_value]
    FROM            cte_GroupedDevices AS gd
        INNER JOIN  dbo.DeviceAttributes AS da
            ON      gd.[device_id] = da.[device_id]
    WHERE           da.[attribute_name] IN
                    ( 
                        'attrib #1'
                        , 'attrib #2'
                        , 'attrib #3'
                        , 'attrib #4'
                    )
)
SELECT          [device_display_name]
                , [device_group_name]
                , [device_location]
                , [attribute_name]                  
                , [attribute_value]
FROM            cte_AttributedDevices
ORDER BY        [device_name] ASC, [attribute_name] ASC ;

结果:

device_display_name  |  device_group_name | device_location  |  attribute_name   |  attribute_value
---------------------------------------------------------------------------------------
Device 1                Group 1A            Location A          attrib #1           0.10
Device 1                Group 1A            Location A          attrib #2           0.02
Device 1                Group 1A            Location A          attrib #3           0.07
Device 1                Group 1A            Location A          attrib #4           0.02
Device 2                Group 1A            Location A          attrib #1           0.16
Device 2                Group 1A            Location A          attrib #2           0.05
Device 2                Group 1A            Location A          attrib #3           0.12
Device 2                Group 1A            Location A          attrib #4           0.04
Device 3                Group 1A            Location B          attrib #1           0.15
Device 3                Group 1A            Location B          attrib #2           0.05
Device 3                Group 1A            Location B          attrib #3           0.07
Device 3                Group 1A            Location B          attrib #4           0.06
Device 4                Group 1A            Location B          attrib #1           0.10
Device 4                Group 1A            Location B          attrib #2           0.03
Device 4                Group 1A            Location B          attrib #3           0.07
Device 4                Group 1A            Location B          attrib #4           0.03

但是,当我执行 PIVOT 查询(旋转 attribute_name 列)时,使用相同的 CTE 基础,针对相同的数据集,所有属性值都为 NULL。

查询:

/* pivot query */

USE [DeviceDatabase] ;
GO

DECLARE     @PrinterGroup   AS VARCHAR(50) ;
SET         @PrinterGroup   = 'Group 1A' ;

WITH cte_GroupedDevices AS
(
    SELECT          d.[id] AS [device_id]
                    , d.[device_name]
                    , d.[device_display_name]
                    , d.[device_location]
                    , dg.[device_group_name]
    FROM            dbo.Devices AS d
        INNER JOIN  dbo.DeviceDeviceGroups AS ddg
            ON      d.[id] = ddg.[device_id]
        INNER JOIN  dbo.DeviceGroups AS dg
            ON      ddg.[group_id] = dg.[id]
    WHERE           dg.[device_group_name] = @PrinterGroup
)
, cte_AttributedDevices AS
(
    SELECT          gd.[device_name]
                    , gd.[device_display_name]
                    , gd.[device_group_name]
                    , gd.[device_location]
                    , da.[attribute_name]                   
                    , CAST ( da.[attribute_value] AS DECIMAL (5,2) ) AS [attribute_value]
    FROM            cte_GroupedDevices AS gd
        INNER JOIN  dbo.DeviceAttributes AS da
            ON      gd.[device_id] = da.[device_id]
    WHERE           da.[attribute_name] IN
                    ( 
                        'attrib #1'
                        , 'attrib #2'
                        , 'attrib #3'
                        , 'attrib #4'
                    )
)
SELECT          [device_display_name]
                , [device_group_name]
                , [device_location]
                , [attrib_1]
                , [attrib_2]
                , [attrib_3]
                , [attrib_4]
FROM            cte_AttributedDevices
PIVOT
(
    MIN ( [attribute_value] )
    FOR [attribute_name] IN
    (
        [attrib_1]
        , [attrib_2]
        , [attrib_3]
        , [attrib_4]
    )
) AS pvt
ORDER BY        [device_name] ASC ;

结果:

device_display_name  |  device_group_name | device_location  |  attrib_1  | attrib_2 |  attrib_3  | attrib_4
------------------------------------------------------------------------------------------------------------
Device 1                Group 1A            Location A          NULL        NULL        NULL        NULL
Device 2                Group 1A            Location A          NULL        NULL        NULL        NULL
Device 3                Group 1A            Location B          NULL        NULL        NULL        NULL
Device 4                Group 1A            Location B          NULL        NULL        NULL        NULL

我在查询的PIVOT 段中尝试了许多不同的函数——MINMAXSUMAVG——所有这些都会产生相同的结果。

我尝试将数据转储到一个临时表中——一个在attribute_value 列上具有数字数据类型的临时表——但它产生了相同的结果,无论是平面查询还是旋转查询。

我已经在attribute_value 列上尝试了NOT NULL 约束,包括基表和临时表。同样的结果。

我做错了什么?

【问题讨论】:

据我所知,在您的 PIVOT 中,当您似乎应该使用 [attrib #1] 时,您引用了 [attrib_1](例如) 这是 pivot 的问题之一 - 因为它生成的列来自列数据 values,因此服务器无法预先告诉您元数据“你拼错了”。 【参考方案1】:

这是一个工作示例。注意:@YourResults 可以替代您的实际初始查询。

我没有看到将attrib #1 转换为attrib_1 的任何逻辑

示例

Declare @YourResults Table ([device_display_name] varchar(50),[device_group_name] varchar(50),[device_location] varchar(50),[attribute_name] varchar(50),[attribute_value] varchar(50))
Insert Into @YourResults Values 
 ('Device 1','Group 1A','Location A','attrib #1',0.10)
,('Device 1','Group 1A','Location A','attrib #2',0.02)
,('Device 1','Group 1A','Location A','attrib #3',0.07)
,('Device 1','Group 1A','Location A','attrib #4',0.02)
,('Device 2','Group 1A','Location A','attrib #1',0.16)
,('Device 2','Group 1A','Location A','attrib #2',0.05)
,('Device 2','Group 1A','Location A','attrib #3',0.12)
,('Device 2','Group 1A','Location A','attrib #4',0.04)
,('Device 3','Group 1A','Location B','attrib #1',0.15)
,('Device 3','Group 1A','Location B','attrib #2',0.05)
,('Device 3','Group 1A','Location B','attrib #3',0.07)
,('Device 3','Group 1A','Location B','attrib #4',0.06)
,('Device 4','Group 1A','Location B','attrib #1',0.10)
,('Device 4','Group 1A','Location B','attrib #2',0.03)
,('Device 4','Group 1A','Location B','attrib #3',0.07)
,('Device 4','Group 1A','Location B','attrib #4',0.03)
 
Select * 
 From @YourResults
 Pivot (min([attribute_value]) for [attribute_name] IN (
                                                         [attrib #1]
                                                       , [attrib #2]
                                                       , [attrib #3]
                                                       , [attrib #4]
                                                       ) ) Pvt

结果

编辑 - 如果需要 [attrib_1] 您可以指定列并分配别名。

Select [device_display_name]
      ,[device_group_name]
      ,[device_location]
      ,[attrib_1] = [attrib #1]
      ,[attrib_2] = [attrib #2]
      ,[attrib_3] = [attrib #3]
      ,[attrib_4] = [attrib #4]
 From @YourResults
 Pivot (min([attribute_value]) for [attribute_name] IN (
                                                         [attrib #1]
                                                       , [attrib #2]
                                                       , [attrib #3]
                                                       , [attrib #4]
                                                       ) ) Pvt

【讨论】:

以上是关于为啥我的 PIVOT 查询使用不包含 NULL 的数据集产生 NULL 结果?的主要内容,如果未能解决你的问题,请参考以下文章

使用 PIVOT 的 SQL 查询

通过 SQL Pivot 将“NULL”值替换为“0”

Sql PIVOT,如何使用 PIVOT 将结果中的 NULL 转换为值 0

为啥我的 Graphql 查询返回 null? [复制]

为啥我的 graphql 嵌套查询返回 null?

具有用户定义字段的 MS Access PIVOT