【问题标题】:One-to-many relationship with "default" child instance与“默认”子实例的一对多关系
【发布时间】:2016-02-15 03:32:20
【问题描述】:

(我有点脑子放屁,我不记得这个问题类的名称了,因为我之前在 SO 上看到过解决方案,所以请将此标记为另一个的副本问题,如果您从以前在这里找到更好的答案)。

假设我们有一个 Cars 和 CarOwners 数据库。每个 CarOwner 都有很多汽车(一对多),但每个 CarOwner 也有自己喜欢的汽车。

这是一个初始架构:

CREATE TABLE Owners (
    OwnerId bigint IDENTITY(1,1) PRIMARY KEY,
    Name    nvarchar(100)
)

CREATE TABLE Cars (
    CarId   bigint IDENTITY(1,1) PRIMARY KEY,
    Vin     varchar(17),
    OwnerId bigint,

    FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId)
)

为了让车主拥有一辆最喜欢的汽车,Owners 表需要扩展一个新列 FaveCarId bigint,其中 Cars 表具有外键约束,但会在 Owners 和 Cars 之间增加不必要的耦合(例如,如果我们将HousesComputers 添加到数据库中,当我们获得有关所有者的信息时,我们并不关心他们的FaveCar 是什么。这也增加了一个鸡与蛋的问题,其中@ 987654328@ 列必须为NULLable 才能创建Owner 行,添加新的Car 行,然后将新的CarID 添加回FaveCar 列。

...所以另一种解决方案是扩展Cars 列以添加布尔值IsFavourite 列,但这有一个问题是没有什么能阻止某人提供两个Cars(属于同一个@ 987654337@) 一组IsFavourite 列值。唯一约束(在OwnerId+IsFavourite 之间也无济于事,因为如果所有者拥有 3 辆或更多汽车,其中 2 辆将拥有IsFavourite=0

【问题讨论】:

  • 我更新了答案以包含数据库继承架构。

标签: database-design foreign-keys


【解决方案1】:

关联表解决方案

CREATE TABLE OwnersFavoriteCar (
    OwnerId bigint PRIMARY KEY,
    CarId bigint,
    FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId),
    FOREIGN KEY (CarId) REFERENCES Cars(CarId)
);

您可以创建一个关联表,通常用于M:N 关系,但将主键设置为OwnerId。这可以防止车主拥有不止一辆喜欢的汽车。

如果您认为可以添加HouseComputer,并且所有者除了Car 之外还会有其中一个的收藏夹,您可以将其重命名为OwnersFavoriteStuff 并添加两列:

HouseId bigint -- with FK constraint
ComputerId bigint -- with FK constraint

数据库继承解决方案

或者,您可以选择数据库继承:

CREATE TABLE Things (
    ThingId bigint PRIMARY KEY
    ThingType nvarchar(10) CHECK (ThingType in ('car', 'house')) -- type discriminator
);

CREATE TABLE Cars (
    ThingId bigInt PRIMARY KEY,
    Vin     nvarchar(17),
    OwnerId bigint,
    FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId),
    FOREIGN KEY (ThingId) REFERENCES Things(ThingId)
);


CREATE TABLE Houses (
    ThingId bigInt PRIMARY KEY,
    Color   nvarchar(17),
    Rooms bigint,
    OwnerId bigint,
    FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId),
    FOREIGN KEY (ThingId) REFERENCES Things(ThingId)
);

CREATE TABLE OwnersFavoriteThing (
    OwnerId bigint ,
    ThingId bigint,
    Type nvarchar(10) CHECK (Type in ('Car', 'House')), -- Used to discriminate amoung different Things
    PRIMARY KEY (OwnerId, ThingId), -- Makes sure that owner cannot have more than one favorite thing of each type
    FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId),
    FOREIGN KEY (ThingId) REFERENCES Things(ThingId)
);

有了这个数据库继承模式,Things 是顶层,CarsHouses 都扩展了Things

您通常会在CarsHouses 上使用BEFORE INSERT 触发器,该触发器首先将一行插入Things,获取新的ThingId,然后将其插入CarsHouses

现在,无论您在数据库中创建了多少新表,您的 FavoriteThing 表都保持第三范式。 type 列用于区分不同类型的things

【讨论】:

    【解决方案2】:

    试试下面的数据模型:

    IsFavorite 是 OwnedCar 的一个属性,由Person 和 Car 标识,而不仅仅是由 Person 或 Car 标识。 所以需要复合键
    您可以将 IsFavorite 实现为 OwnedCar 的列,或者创建一个新实体 FavoriteCar 以增加完整性。

    要约束 OwnedCar 和 FavoriteCar,您需要创建唯一索引(备用键)。

    • 对于 OwnedCar,为 CarId 添加唯一索引,以便每辆 Car 只能由一个 Person 拥有。
    • 对于 FavoriteCar,为 OwnerId 添加一个唯一索引,以便每个 Person 只有一辆最喜欢的汽车。

    没有复合键的问题是,您可以插入一辆喜欢的车,但车主不拥有该车。

    CREATE TABLE OwnersFavoriteCar (
        OwnerId bigint PRIMARY KEY,
        CarId bigint,
        FOREIGN KEY (OwnerId) REFERENCES Owners(OwnerId),
        FOREIGN KEY (CarId) REFERENCES Cars(CarId)
    );
    

    【讨论】:

      猜你喜欢
      • 2013-06-26
      • 1970-01-01
      • 1970-01-01
      • 2011-01-26
      • 1970-01-01
      • 2016-07-23
      • 1970-01-01
      • 2020-07-10
      • 1970-01-01
      相关资源
      最近更新 更多