【问题标题】:SQL Server dynamic pivot returning null value when none exist in the data当数据中不存在时,SQL Server 动态数据透视返回空值
【发布时间】:2010-12-16 00:23:41
【问题描述】:

我有 3 个表格(tblPreferencetblCustomertblCustomerPreference),如下所示:

tblPreference:
ID       | Name            | DefaultValue
(int PK) | (nvarchar(100)) | (nvarchar(100))
-------------------------------
1        | Preference1     | 1
2        | Preference2     | Yes
3        | Preference3     | 1

tblCustomer:
CustomerID | ...
(int PK)
--------------------
1          | ...
2          | ...
3          | ...

tblCustomerPreference:
ID       | CustomerID | PreferenceID | Value
(int PK) | (int)      | (int)        | (nvarchar(100))
-------------------------------------------------------
1        | 1          | 1            | 0
2        | 1          | 2            | Yes
3        | 2          | 1            | 0
4        | 2          | 2            | No

我正在创建此数据的数据透视,因此使用以下存储过程将其全部放在一行中,以便它始终拉回所有首选项,如果找到特定于客户的值,它将返回,否则返回默认值价值:

CREATE PROCEDURE [dbo].[usp_GetCustomerPreferences] @CustomerID int AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @PivotColumns nvarchar(max)
    DECLARE @PivotColumnsSelectable nvarchar(max)
    SELECT @PivotColumns = COALESCE(@PivotColumns + ',','') + QUOTENAME(Preference.Name),
           @PivotColumnsSelectable = COALESCE(@PivotColumnsSelectable + ',' + Char(10),'') + Preference.Source + '.' + QUOTENAME(Preference.Name) + ' AS ' + QUOTENAME(Preference.Name)
    FROM (SELECT [Name],
                 'PreferencePivot' AS [Source]
          FROM [dbo].[tblPreference]) Preference

    DECLARE @sqlText nvarchar(max)
    SELECT @sqlText = 'SELECT ' + @PivotColumnsSelectable + '
    FROM (SELECT tblPreference.Name AS PreferenceName,
                CASE
                    WHEN tblCustomerPreference.Value IS NOT NULL THEN tblCustomerPreference.Value
                    ELSE tblPreference.DefaultValue
                END AS Value,
                @innerCustomerID AS CustomerID
            FROM tblCustomerPreference
                RIGHT JOIN tblPreference ON tblCustomerPreference.PreferenceID = tblPreference.ID
            WHERE (tblCustomerPreference.CustomerID = @innerCustomerID OR tblCustomerPreference.ID IS NULL)) data
            PIVOT (MAX(Value)
                   FOR PreferenceName IN (' + @PivotColumns + ')) PreferencePivot'

    EXECUTE sp_executesql @sqlText, N'@innerCustomerID int', @CustomerID
END

我遇到的问题是,当我查询 CustomerID 1 或 2 时,一切都按预期返回,所有值都按预期填充。但是,如果我查询 CustomerID 3,它将为为其他客户填充的任何 PreferenceID 返回一个NULL。如果我在没有 PIVOT 表达式的情况下运行查询,它将返回所有按预期填充的首选项。只有当我 PIVOT 数据时,NULL 才会潜入。我希望我错过了一些简单的事情,但我没有看到错误。

【问题讨论】:

    标签: sql-server pivot dynamic-sql


    【解决方案1】:

    您甚至在 CustomerID 的 1 和 2 中看到 preference3 默认值的唯一原因是因为没有针对 preference3 的 tblCustomerPreference 记录,而不是因为没有针对 CustomerID=1 的组合的 tblCustomerPreference 记录/ Preference3 和 CustomerID=2 / Preference3。

    在您的 RIGHT JOIN 条件中,您指定仅在首选项值上连接 tblCustomerPreference 和 tblPreference - 这只会具体化来自 tblPreference 的记录,该记录具有 NO 匹配的记录tblCustomerPreference 中的任何客户 ID。如果您在 customerID = @innerCustomerID 上为该子句添加额外的连接条件,您现在将做您正在寻找的事情:即给我 ALL 首选项记录和 ANY匹配 tblCustomerPreference for CustomerID=@innerCustomerID。

    尝试通过简单地为 CustomerID 1 和 Preference3 添加 tblCustomerPreference 记录,您会注意到您不仅会开始看到 Customer2 的 Prefernce3 值为 NULL,而且您甚至不会再获得 Customer 的结果3.

    看起来这是您在 WHERE 子句中尝试执行的操作,但由于在查询处理期间 JOIN 是在 WHERE 子句之前处理的(遵循语句处理的正确逻辑顺序),因此您将获得一个中间结果集严格基于偏好组合,而不是客户和偏好组合。

    因此,进行一些小的更改,您应该会很好。基本上只需在 RIGHT JOIN 子句中添加一个附加条件,指定特定客户,即@innerCustomerID 并删除整个 WHERE 子句,一切就绪。请注意,这也会产生实际返回任何传递的任何 @CustomerID 的所有默认值的副作用,该值甚至不作为客户存在 - 如果您想更改它以不为不存在的客户返回任何内容,只需添加一个检查在查询之前或包含 where exists() 过滤器:

    alter PROCEDURE [dbo].[usp_GetCustomerPreferences] @CustomerID int AS
    BEGIN
        -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
        SET NOCOUNT ON;
    
        DECLARE @PivotColumns nvarchar(max)
        DECLARE @PivotColumnsSelectable nvarchar(max)
        SELECT @PivotColumns = COALESCE(@PivotColumns + ',','') + QUOTENAME(Preference.Name),
               @PivotColumnsSelectable = COALESCE(@PivotColumnsSelectable + ',' + Char(10),'') + Preference.Source + '.' + QUOTENAME(Preference.Name) + ' AS ' + QUOTENAME(Preference.Name)
        FROM (SELECT [Name],
                     'PreferencePivot' AS [Source]
              FROM [dbo].[tblPreference]) Preference
    
        DECLARE @sqlText nvarchar(max)
        SELECT @sqlText = 'SELECT ' + @PivotColumnsSelectable + '
        FROM (SELECT tblPreference.Name AS PreferenceName,
                    CASE
                        WHEN tblCustomerPreference.Value IS NOT NULL THEN tblCustomerPreference.Value
                        ELSE tblPreference.DefaultValue
                    END AS Value,
                    @innerCustomerID AS CustomerID
                FROM tblCustomerPreference
                    RIGHT JOIN tblPreference 
                    ON tblCustomerPreference.PreferenceID = tblPreference.ID
                    AND tblCustomerPreference.CustomerID = @innerCustomerID
                ) data
                PIVOT (MAX(Value)
                       FOR PreferenceName IN (' + @PivotColumns + ')) PreferencePivot'
    
     print @sqlText
    
        EXECUTE sp_executesql @sqlText, N'@innerCustomerID int', @CustomerID
    END
    

    【讨论】:

    • 太棒了!像我预期的那样工作。考虑到它是在旋转之前创建临时表,我认为 WHERE 语句将在聚合中考虑。以后我必须牢记这一点。
    猜你喜欢
    • 2019-03-03
    • 2014-10-16
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 2013-02-09
    • 2018-11-22
    相关资源
    最近更新 更多