设计模式 - 许多父表之一

EBa*_*arr 13 database-design

我经常在数据库中遇到一种情况,其中给定的表可以 FK 到许多不同的父表中的一个。我已经看到了这个问题的两种解决方案,但都不是个人满意的。我很好奇你在那里看到了什么其他模式?有没有更好的方法来做到这一点?

一个人为的例子
假设我的系统有Alerts. 可以接收各种对象的警报——客户、新闻和产品。一个给定的警报可以只针对一个项目。无论出于何种原因,客户、文章和产品都在快速移动(或本地化),因此在创建警报时无法将必要的文本/数据拉入警报。鉴于此设置,我看到了两种解决方案。

注意:下面的 DDL 是针对 SQL Server 的,但我的问题应该适用于任何 DBMS。

解决方案 1 -- 多个可空 FKey

在此解决方案中,链接到多个表之一的表具有多个 FK 列(为简洁起见,下面的 DDL 不显示 FK 创建)。 好处- 在这个解决方案中,我有外键很好。FK 的空优化使得添加准确数据变得方便且相对容易。THE BAD Querying 不是很好,因为它需要N LEFT JOINS 或N UNION 语句来获取关联数据。在 SQL Server 中,特别是 LEFT JOINS 阻止创建索引视图。

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)
Run Code Online (Sandbox Code Playgroud)

解决方案 2——每个父表中的一个 FK
在这个解决方案中,每个“父”表都有一个到 Alert 表的 FK。它可以轻松检索与父级关联的警报。不利的一面是,从警报到引用者之间没有真正的链条。此外,数据模型允许孤立警报——其中警报与产品、新闻或客户无关。同样,多个 LEFT JOIN 来找出关联。

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)
Run Code Online (Sandbox Code Playgroud)

这只是关系数据库中的生活吗?您是否发现了更令人满意的替代解决方案?

小智 4

我理解第二种解决方案不适用,因为它不提供一对多(警报)关系。

由于严格的 3NF 合规性,您只能采用两种解决方案。

我会设计一个耦合度较低的模式:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys
Run Code Online (Sandbox Code Playgroud)

或者,如果完整性关系是强制性的,我可能会设计:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)
Run Code Online (Sandbox Code Playgroud)

反正...

三个有效的解决方案加上另一个要考虑的许多(对象)对一(警报)关系......

这些呈现出来的寓意是什么?

它们略有不同,但在标准上的权重相同:

  • 插入和更新的性能
  • 查询复杂度
  • 储存空间

所以,选择那个对你来说更舒服的。