SQL 查询重建继承的 EAV 模型

SQL query to reconstruct inherited EAV model

我的数据库中有 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, '0K', 5),
    (103, 1, 'Blue', 6),
    (104, 2, 'Diesel', 6),
    (105, 3, '3', 6),
    (106, 4, 'k', 6),
    (107, 1, 'Green', 7),
    (108, 2, 'Diesel', 7),
    (109, 3, '5', 7),
    (110, 4, 'k', 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, 'k', 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 个属性(颜色、燃料类型、车门、价格)。继承的属性值以粗体显示:

本地汽车 ID = 1(黄色,汽油4100K 美元

本地汽车 ID = 2(蓝色柴油、6、$80k

本地汽车 ID = 3(红色,汽油,2,6 万美元)

我正在尝试找到将上述两个查询连接在一起以提供完整的本地汽车属性集所需的必要连接,其中一些是继承的:

LocalCarId    AttributeNameId     Value
------------------------------------------
1             1                   Yellow
1             2                   Gas
1             3                   4
1             4                   0K
2             1                   Blue
2             2                   Diesel
2             3                   6
2             4                   K
3             1                   Red
3             2                   Gas
3             3                   2
3             4                   K

甚至可能:

LocalCarId    AttributeNameId     LocalValue         MasterValue
    -------------------------------------------------------------
    1             1                   Yellow        Red
    1             2                   NULL          Gas
    1             3                   NULL          4
    1             4                   NULL          0K
    2             1                   NULL          Blue
    2             2                   NULL          Diesel
    2             3                   6             3
    2             4                   NULL          K
    3             1                   Red           NULL
    3             2                   Gas           NULL
    3             3                   2             NULL
    3             4                   K          NULL

SQL Fiddle Demo

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 |

可以通过对所有本地汽车属性和主汽车属性执行联合来解决该问题。每条记录都标有 [IsMasterAttribute] 标志。下一步是使用 ROW_NUMBER() window 函数对每个重复属性进行排名。最后一步是仅 select 等级为 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]