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, $100K)

Местен идентификатор на автомобил = 2 (Син, Дизел, 6, $80k)

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

Опитвам се да намеря необходимите съединения, необходими за обединяване на двете заявки по-горе заедно, за да дам пълен набор от атрибути за локални автомобили, някои наследени:

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 свързват ли се обратно с колоната ID на вашата таблица MasterCars. Ако случаят е такъв, тогава заявката може лесно да се актуализира, за да ви даде това, което искате. Ако това не е така, тогава не вярвам, че това, което искате, е възможно. Актуализирах примера си с предположението, че колоната MasterCarID се съпоставя обратно към вашата колона ID в таблицата MasterCars. - person Edmond Quinton; 13.05.2016
comment
Здравей Едмонд. Да, съжалявам, че актуализирах скрипта си и не актуализирах стойностите на външния ключ в колоната LocalCars.MasterCarId. Кодирането в 2 сутринта не помага :) Заявката ви работи перфектно. Благодаря, че отделихте време да ми помогнете. - person David James Ball; 13.05.2016

Демонстрация на SQL Fiddle

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
Има нещо в тази db схема, което не разбирам. Защо кола 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