SQL-запрос для восстановления унаследованной модели EAV

В моей базе данных есть 5 таблиц, представляющих унаследованную модель EAV:

CREATE TABLE AttributeNames
    ("ID" int, "Name" varchar(8))
;

INSERT INTO AttributeNames
    ("ID", "Name")
VALUES
    (1, 'Color'),
    (2, 'FuelType'),
    (3, 'Doors'),
    (4, 'Price')
;

CREATE TABLE MasterCars
    ("ID" int, "Name" varchar(10))
;

INSERT INTO MasterCars
    ("ID", "Name")
VALUES
    (5, 'BMW'),
    (6, 'Audi'),
    (7, 'Ford')
;

CREATE TABLE MasterCarAttributes
    ("ID" int, "AttributeNameId" int, "Value" varchar(10), "MasterCarId" int)
;

INSERT INTO MasterCarAttributes
    ("ID", "AttributeNameId", "Value", "MasterCarId")
VALUES
    (100, 1, 'Red', 5),
    (101, 2, 'Gas', 5),
    (102, 3, '4', 5),
    (102, 4, '$100K', 5),
    (103, 1, 'Blue', 6),
    (104, 2, 'Diesel', 6),
    (105, 3, '3', 6),
    (106, 4, '$80k', 6),
    (107, 1, 'Green', 7),
    (108, 2, 'Diesel', 7),
    (109, 3, '5', 7),
    (110, 4, '$60k', 7)
;

CREATE TABLE LocalCars
    ("ID" int, "MasterCarId" int)
;

INSERT INTO LocalCars
    ("ID", "MasterCarId")
VALUES
    (8, '5'),
    (9, '6'),
    (10, NULL)
;

CREATE TABLE LocalCarAttributes
    ("ID" int, "AttributeNameId" int, "Value" varchar(6), "LocalCarId" int)
;

INSERT INTO LocalCarAttributes
    ("ID", "AttributeNameId", "Value", "LocalCarId")
VALUES
    (43, 1, 'Yellow', 8),
    (44, 3, '6', 9),
    (45, 1, 'Red', 10),
    (46, 2, 'Gas', 10),
    (47, 3, '2', 10),
    (48, 4, '$60k', 10)
;

Я могу получить все атрибуты основного автомобиля следующим образом:

SELECT MC.ID, MCA.AttributeNameId, MCA.Value
FROM MasterCars MC
left join MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
order by MC.ID;

Точно так же я могу получить все атрибуты локального автомобиля следующим образом:

SELECT LC.ID, LCA.AttributeNameId, LCA.Value
FROM LocalCars LC
left join LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
order by LC.ID;

Если LocalCars.MasterCarId не равен NULL, то этот локальный автомобиль может наследовать атрибуты этого главного автомобиля. Локальный атрибут автомобиля с тем же AttributeNameId переопределяет любой главный атрибут с тем же AttributeNameId.

Итак, учитывая приведенные выше данные, у меня есть 3 местных автомобиля с 4 атрибутами (цвет, тип топлива, двери, цена). Значения унаследованных атрибутов выделены жирным шрифтом:

Местный идентификатор автомобиля = 1 (желтый, бензин, 4, 100 000 долларов).

Местный идентификатор автомобиля = 2 (Синий, Дизель, 6, 80 000 долларов).

Местный идентификатор автомобиля = 3 (красный, бензин, 2, 60 тысяч долларов)

Я пытаюсь найти необходимые соединения, необходимые для объединения двух вышеуказанных запросов вместе, чтобы получить полный набор атрибутов локальных автомобилей, некоторые из которых унаследованы:

LocalCarId    AttributeNameId     Value
------------------------------------------
1             1                   Yellow
1             2                   Gas
1             3                   4
1             4                   $100K
2             1                   Blue
2             2                   Diesel
2             3                   6
2             4                   $80K
3             1                   Red
3             2                   Gas
3             3                   2
3             4                   $60K

или, возможно, даже:

LocalCarId    AttributeNameId     LocalValue         MasterValue
    -------------------------------------------------------------
    1             1                   Yellow        Red
    1             2                   NULL          Gas
    1             3                   NULL          4
    1             4                   NULL          $100K
    2             1                   NULL          Blue
    2             2                   NULL          Diesel
    2             3                   6             3
    2             4                   NULL          $80K
    3             1                   Red           NULL
    3             2                   Gas           NULL
    3             3                   2             NULL
    3             4                   $60K          NULL

person David James Ball    schedule 12.05.2016    source источник


Ответы (2)


Проблему можно решить, выполнив объединение всех ваших локальных атрибутов автомобилей и основных атрибутов автомобилей. Каждая запись помечается флагом [IsMasterAttribute]. Следующим шагом будет использование оконной функции ROW_NUMBER () для ранжирования каждого из повторяющихся атрибутов. Последний шаг - выбрать только атрибуты с рангом 1.

    ;WITH CTE_CombinedAttributes
    AS
    (
        SELECT               1 AS IsMasterAttribute
                            ,LC.ID
                            ,MC.ID AS MasterCarId
                            ,MCA.AttributeNameId
                            ,MCA.Value
        FROM                 MasterCars MC
        LEFT OUTER JOIN     MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
        INNER JOIN          LocalCars LC ON LC.MasterCarId = MC.ID
        UNION ALL
        SELECT               0 AS IsMasterAttribute
                            ,LC.ID
                            ,LC.MasterCarId
                            ,LCA.AttributeNameId
                            ,LCA.Value
        FROM                LocalCars LC
        LEFT OUTER JOIN     LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
    )
    , 
    CTE_RankedAttributes
    AS
    (
        SELECT   [IsMasterAttribute]
                ,[ID]
                ,[AttributeNameId]
                ,[Value]
                ,ROW_NUMBER() OVER (PARTITION BY [ID], [AttributeNameId] ORDER BY [IsMasterAttribute]) AS [AttributeRank]
        FROM    CTE_CombinedAttributes
    )
    SELECT       [IsMasterAttribute]
                ,[ID]
                ,[AttributeNameId]
                ,[Value]
    FROM        CTE_RankedAttributes
    WHERE       [AttributeRank] = 1
    ORDER BY    [ID]

Второй вывод также возможен путем простого поворота к окончательному результату:

    ;WITH CTE_CombinedAttributes
    AS
    (
        SELECT               1 AS IsMasterAttribute
                            ,LC.ID
                            ,MC.ID AS MasterCarId
                            ,MCA.AttributeNameId
                            ,MCA.Value
        FROM                 MasterCars MC
        LEFT OUTER JOIN     MasterCarAttributes MCA on MC.ID = MCA.MasterCarId
        INNER JOIN          LocalCars LC ON LC.MasterCarId = MC.ID
        UNION ALL
        SELECT               0 AS IsMasterAttribute
                            ,LC.ID
                            ,LC.MasterCarId
                            ,LCA.AttributeNameId
                            ,LCA.Value
        FROM                LocalCars LC
        LEFT OUTER JOIN     LocalCarAttributes LCA on LC.ID = LCA.LocalCarId
    )
    , 
    CTE_RankedAttributes
    AS
    (
        SELECT   [IsMasterAttribute]
                ,[ID]
                ,[AttributeNameId]
                ,[Value]
                ,ROW_NUMBER() OVER (PARTITION BY [ID], [AttributeNameId] ORDER BY [IsMasterAttribute]) AS [AttributeRank]
        FROM    CTE_CombinedAttributes
    )
    SELECT       [ID]
                ,[AttributeNameId]
                ,MAX(
                    CASE [IsMasterAttribute]
                        WHEN 0 THEN [Value]
                    END
                 ) AS LocalValue
                ,MAX(
                    CASE [IsMasterAttribute]
                        WHEN 1 THEN [Value]
                    END
                 ) AS MasterValue
    FROM        CTE_RankedAttributes
    GROUP BY    [ID], [AttributeNameId]
    ORDER BY    [ID]
person Edmond Quinton    schedule 13.05.2016
comment
Привет, большое спасибо за уделенное время. Я чувствую, что очень близок к решению. Моя проблема в том, что я должен был уточнить, что MasterCar.Id и LocalCar.Id могут быть полностью независимыми значениями, и мы не можем синхронизировать их в нашей системе. Объединение MC.ID и LC.ID вызывает проблему. Я обновил исходный сценарий, добавив случайные идентификаторы. - person David James Ball; 13.05.2016
comment
Привет, Дэвид, связаны ли значения столбца «MasterCarId» в таблице LocalCars со столбцом идентификатора таблицы MasterCars. В этом случае запрос можно легко обновить, чтобы получить то, что вы хотите. Если это не так, то я не верю, что то, чего вы хотите, возможно. Я обновил свой пример, предположив, что столбец MasterCarID действительно сопоставляется с вашим столбцом идентификатора в таблице MasterCars. - person Edmond Quinton; 13.05.2016
comment
Привет, Эдмонд. Да, извините, я обновил свой скрипт и не обновил значения внешнего ключа в столбце LocalCars.MasterCarId. Кодирование в 2 часа ночи не помогает :) Ваш запрос работает отлично. Спасибо, что нашли время помочь мне. - person David James Ball; 13.05.2016

демонстрация скрипта SQL

SELECT LC."ID" as LocalCarID,   
       COALESCE(LCA."AttributeNameId", MCA."AttributeNameId") as "AttributeNameId",
       COALESCE(LCA."Value", MCA."Value") as "Value"
FROM LocalCars LC
LEFT JOIN MasterCars MC
       ON LC."MasterCarId" = MC."ID"
LEFT JOIN MasterCarAttributes MCA
       ON MC."ID" = MCA."MasterCarId"
LEFT JOIN LocalCarAttributes LCA
        ON (    MCA."AttributeNameId" = LCA."AttributeNameId"
             OR MCA."AttributeNameId" IS NULL)
             -- This is the important part
             -- Try to join with a MasterAtribute otherwise use the Car Atribute.
       AND LC."ID" = LCA."ID"

ВЫХОД

| LocalCarID | AttributeNameId |  Value |
|------------|-----------------|--------|
|          1 |               1 |   Blue |
|          1 |               2 |    Gas |
|          2 |               1 |  Green |
|          2 |               2 | Diesel |
person Juan Carlos Oropeza    schedule 12.05.2016
comment
Что-то в этой схеме БД я не понимаю. Почему у машины 2 нет мастер-машины? Потому что в этом случае у вас не будет основных атрибутов, и вы не можете гарантировать, что у автомобиля есть все атрибуты ... как это случилось с автомобилем 1. - person Juan Carlos Oropeza; 13.05.2016
comment
Спасибо, но ваше решение не подходит для моей реальной модели. Столбцы идентификаторов в таблицах атрибутов могут быть полностью случайными, поэтому предложение LC.ID = LCA.ID не будет выполняться. Моя модель немного сложна для объяснения, но основная и локальная машина будут иметь одинаковое количество атрибутов, но некоторые из них могут быть переопределены на локальном уровне. - person David James Ball; 13.05.2016
comment
@DavidJamesBall Значит, вы задали неполный вопрос, и я виноват, что ответ недостаточно общий? Я решаю поставленный вами вопрос. Если у вас есть другой вопрос, приложите больше усилий, чтобы описать вашу настоящую проблему. - person Juan Carlos Oropeza; 13.05.2016
comment
Он не был неполным, это была всего лишь небольшая выборка данных. Приносим извинения, если я недостаточно подробно изложил свою проблему, чтобы объяснить, что я ищу общий запрос, который может работать с гораздо большим набором данных. Я отредактировал свой вопрос. - person David James Ball; 13.05.2016