Динамический свод SQL Server, возвращающий нулевое значение, если в данных ничего не существует - PullRequest
1 голос
/ 12 ноября 2009

У меня есть 3 таблицы (tblPreference, tblCustomer, tblCustomerPreference), которые выглядят примерно так:

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, он вернет NULL для любых PreferenceID, которые заполнены для других клиентов. Если я запускаю запрос без выражения PIVOT, он возвращает все настройки, заполненные, как и ожидалось. Только когда я PIVOT, данные NULL закрадываются. Я надеюсь, что пропустил что-то простое, но я не вижу ошибки.

1 Ответ

1 голос
/ 13 ноября 2009

Единственная причина, по которой вы даже видите значения по умолчанию для preference3 в 1 и 2 CustomerID, заключается в том, что для preference3 существует NO tblCustomerPreference, а не потому, что для комбинации CustomerID = 1 / нет записи tblCustomerPreference. Preference3 и CustomerID = 2 / Preference3.

В вашем условии RIGHT JOIN вы указываете соединение только между tblCustomerPreference и tblPreference только для значения предпочтения - это когда-либо только материализует запись из tblPreference, у которой NO соответствует записи для ANY идентификатор клиента в tblCustomerPreference. Если вы добавите дополнительное условие соединения для customerID = @innerCustomerID для этого предложения, теперь вы будете делать то, что ищете: т.е. дать мне ALL записей предпочтений и ANY соответствия tblCustomerPreference для CustomerID=@innerCustomerID.

Попробуйте, просто добавив запись tblCustomerPreference для CustomerID 1 и Preference3, и вы заметите, что вы начнете видеть не только NULL для значения Prefernce3 для Customer2, но вы больше не будете получать результат для Customer 3.

Похоже, это то, что вы пытались сделать в своем предложении WHERE, но поскольку JOIN обрабатываются до предложения WHERE во время обработки запроса (после правильного логического порядка обработки операторов), вы получаете промежуточный набор результатов, который строго на основе комбинации предпочтений в отличие от комбинации предпочтений клиента и предпочтений.

Итак, пара небольших изменений, и вы должны быть хорошими. По сути, просто добавьте дополнительное условие в ваше предложение RIGHT JOIN, указывающее конкретного клиента, т.е. @innerCustomerID, и удалите все предложение WHERE, и все готово. Обратите внимание, что это также будет иметь побочный эффект фактического возврата всех значений по умолчанию для любого переданного @CustomerID, который даже не существует в качестве клиента - если вы хотите изменить это, чтобы ничего не возвращать для несуществующих клиентов, просто добавьте проверку перед запросом или включите фильтр where there ():

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...