设计模式-许多父表之一


13

我经常遇到数据库中的一种情况,其中给定的表可以FK到许多不同的父表之一。我已经看到了解决该问题的两种解决方案,但都不令人满意。我很好奇,您在那里看到过其他哪些模式?有更好的方法吗?

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

注意:以下DDL用于SQL Server,但我的问题应适用于任何DBMS。

解决方案1-多个可空FKey

在此解决方案中,链接到多个表的表具有多个FK列(为简便起见,下面的DDL不显示FK创建)。 好的 -在这种解决方案中,很高兴我有外键。FK的零光学特性使添加精确数据变得非常方便且相对容易。BAD查询不是很好,因为它需要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) 
)

解决方案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)
)

这只是关系数据库中的生活吗?您是否找到其他更令人满意的替代解决方案?


1
您是否可以创建一个带有以下列的父表Alertable:ID,CreateDate,Name,Type。您可以将三个子表添加到FK,而您的ALert将仅将其添加到一个可警报的表中。
2012年

在某些情况下,您提出了一个好要点-有效的解决方案#3。但是,无论何时数据快速移动或本地化,它都不起作用。举例来说,产品,客户和新闻都有各自对应的“语言”表,以支持多种语言的名称。如果我必须以用户的母语提供“名称”,则无法将其存储在中Alertable。那有道理吗?
EBarr 2012年

1
@EBarr:“如果我必须以用户的母语提供“名称”,则无法将其存储在Alertable中。这有意义吗?” 不,不是。在当前架构下,如果必须使用用户本地语言传递“名称”,则可以将其存储在“产品”,“客户”或“新闻”表中吗?
ypercubeᵀᴹ

@ypercube我补充说,每个表都有一个关联的语言表。我试图制作一个方案,其中“名称”文本可能会因请求而异,因此无法存储在Alertable中。
EBarr 2012年

除非它已经有一个可接受的名称,否则我建议为您要查看所有警报及其关联的父级的查询使用“章鱼联接”一词。:)
内森·朗

Answers:


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

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

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)

无论如何...

对于许多(对象)一对一(警报)关系,应考虑三个有效的解决方案...

这些呈现出来的是什么道德?

它们有细微的差别,并且在标准上的权重相同:

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

因此,选择适合您的人。


1
感谢您的输入;我同意您的大部分意见。我可能巧妙地描述了所需的关系(不包括您眼中的解决方案2),因为这是一个虚构的示例。 fn1-可以理解,我只是在简化表格以集中精力解决这个问题。fn2-复合键,我走了!关于较小的耦合模式,我理解其简单性,但亲自尝试尽可能使用DRI设计。
EBarr 2012年

对此进行回顾,我开始怀疑我的解决方案的正确性……无论如何,它已经被投票两次,对此我表示感谢。尽管我认为我的设计是有效的,但不适合所给的问题,因为无法解决“ n”个联合/联接……
Marcus Vinicius Pompeu13年

DRI的首字母缩写吸引了我。对于你们所有人来说,它代表声明性引用完整性,它是引用数据完整性背后的技术,通常通过以下方式实现:(鼓式滚动)DDL FOREIGN KEY语句。有关en.wikipedia.org/wiki/Declarative_Referential_Integritymsdn.microsoft.com/zh-cn/library/…的
Marcus Vinicius Pompeu

1

我已经使用了触发器维护的联接表。如果无法或不希望重构数据库,则该解决方案可以很好地用作最后的解决方法。这个想法是,您有一个可以处理RI问题的表,而所有DRI都反对它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.