Sql server 当数据中不存在空值时,SQL Server动态透视返回空值

Sql server 当数据中不存在空值时,SQL Server动态透视返回空值,sql-server,pivot,dynamic-sql,Sql Server,Pivot,Dynamic Sql,我有3个表(tblPreference,tblCustomer,tblcustomerference),它们看起来类似于以下内容: tblPreference: ID | Name | DefaultValue (int PK) | (nvarchar(100)) | (nvarchar(100)) ------------------------------- 1 | Preference1 | 1 2 | Preferen

我有3个表(
tblPreference
tblCustomer
tblcustomerference
),它们看起来类似于以下内容:

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
表达式的情况下运行查询,它将返回按预期填充的所有首选项。只有当我透视数据时,
NULL
才会悄悄进入。我希望我错过了一些简单的东西,但我没有看到错误。

在CustomerID的1和2中看到preference3默认值的唯一原因是因为preference3没有tblCustomerPreference记录,不是因为CustomerID=1/首选项3和CustomerID=2/首选项3的组合没有tblCustomerPreference记录

在右连接条件中,您仅在首选项值上指定tblCustomerPreference和tblPreference之间的连接-这将仅具体化tblPreference中的记录,该记录对于tblCustomerPreference中的任何customerID都没有匹配记录。如果您为该子句在customerID=@innerCustomerID上添加了一个附加的连接条件,那么您现在就可以执行您正在寻找的操作:即,为customerID=@innerCustomerID提供所有首选项记录和任何匹配的tblCustomerPreference

通过简单地为CustomerID 1和Preference3添加一个tblCustomerPreference记录来尝试,您会注意到,您不仅会看到Customer2的Preference3值为NULL,甚至不会再得到Customer3的结果

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

所以,一些小的变化,你应该是好的。基本上,只需在右JOIN子句中添加一个附加条件,指定一个特定的客户,即@innerCustomerID,然后删除整个WHERE子句,就可以了。请注意,这也会产生一个副作用,即实际返回传递的任何@CustomerID的所有默认值,而这些@CustomerID甚至不作为客户存在-如果要将其更改为不为不存在的客户返回任何值,只需在查询之前添加一个检查或包含一个where exists()筛选器:


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

在右连接条件中,您仅在首选项值上指定tblCustomerPreference和tblPreference之间的连接-这将仅具体化tblPreference中的记录,该记录对于tblCustomerPreference中的任何customerID都没有匹配记录。如果您为该子句在customerID=@innerCustomerID上添加了一个附加的连接条件,那么您现在就可以执行您正在寻找的操作:即,为customerID=@innerCustomerID提供所有首选项记录和任何匹配的tblCustomerPreference

通过简单地为CustomerID 1和Preference3添加一个tblCustomerPreference记录来尝试,您会注意到,您不仅会看到Customer2的Preference3值为NULL,甚至不会再得到Customer3的结果

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

所以,一些小的变化,你应该是好的。基本上,只需在右JOIN子句中添加一个附加条件,指定一个特定的客户,即@innerCustomerID,然后删除整个WHERE子句,就可以了。请注意,这也会产生一个副作用,即实际返回传递的任何@CustomerID的所有默认值,而这些@CustomerID甚至不作为客户存在-如果要将其更改为不为不存在的客户返回任何值,只需在查询之前添加一个检查或包含一个where exists()筛选器:


令人惊叹的!像我预期的那样工作。考虑到它在数据透视之前创建临时表,我认为WHERE语句将被考虑在聚合中。我以后一定要记住这一点。太棒了!像我预期的那样工作。考虑到它在数据透视之前创建临时表,我认为WHERE语句将被考虑在聚合中。我以后必须记住这一点。
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