【问题标题】:How to build data modeling with a child table being used by two or more parent tables如何使用两个或多个父表使用的子表构建数据建模
【发布时间】:2021-09-26 08:15:58
【问题描述】:

在一个简单的示例中,我有一个买家表和一个供应商表。如何使用两个父表对电话表建模。

更新 这是一个合适的方法吗?

注意:使用联结表时,会不会很困难,例如在数据库中查询一个电话号码,知道它是属于供应商还是买家?

更新

经过考虑,我认为这种情况下最好的解决方案是创建一个基表并将其称为实体,因此其他有电话的实体必须在此表中有记录。尽管示例中的 Buyer 和 Supplier 表具有相同的字段(只是为了方便),但在实际场景中它们是两个具有非常不同字段的表。

【问题讨论】:

  • 在您的简单示例中,假设每个供应商或采购商一个电话号码,将外键添加到供应商和采购员表到 PhoneNumber 表。 (表名应该是单数。)如果供应商有多个电话号码,您将需要一个 SupplierPhoneNumber 联结表。拥有多个电话号码的买家也是如此。
  • @GilbertLeBlanc 我同意设计解决方案,但表格是集合(好的,包),应该是复数。我们不谈论“1 到 10 之间的整数集合”,也不会谈论“我们使用的供应商集合”。或者,如果您更喜欢权威机构、DBMS 供应商和 ANSI 提供的参数,请使用复数形式:sys.tablessys.columns 等在 mssql 中,INFORMATION_SCHEMA.ROUTINES 等用于 ANSI,ALL_TABLES 用于 oracle...
  • @GilbertLeBlanc 我将了解表的实际含义,或者 ANSI 标准委员会或 DBMS 供应商的权威意见。 ISO 说 entities 应该是单数的,但表不是实体,它们是命题的集合。更好的是,IMO 是集体名词。最近有人有一个“ActorsMovies”映射表,但一个好名字是“Casts”。 C. J. Date 在散文中使用复数形式,但为了简洁起见,他的书中使用了一个字母表。来自非权威来源的“方便的意见”似乎并不重要。但我确实同意“保持一致”。
  • 抛开这个命名问题,什么是最合适的方法。如果使用联结表,例如在数据库中查找电话号码时,是否很难知道它属于供应商还是买方?

标签: sql-server data-modeling


【解决方案1】:

这是一个大问题。

在讨论期间,我将保留您的后续评论,这基本上是一个方便的问题:给定一个电话号码,能够轻松地知道它属于谁/属于什么会很好.

假设我们按照您的原始图表保持依赖关系的方向,其中电话号码“指向”其买方或供应商。那是什么样子的?

create table PhoneNumbers
(
   phoneNumber varchar(16) primary key, 
   buyerId int foreign key references Buyers,
   supplierId int foreign key references suppliers
);

这里的明显限制是,如果您添加更多种类的“有电话号码的事物”,您必须在电话号码表中添加越来越多的列。目前还不清楚一个电话号码是否应该同时适用于买家和供应商。只能有一个关联的 Id 不为空吗?如果添加更多相关内容,这将成为一个令人讨厌的检查约束。

但是,话虽如此,解决方案相当简单。如果你绝对肯定地知道你只有两种相关的类型,你可以“摆脱”这个。这很糟糕,但我们认识到这很糟糕,还是照做了。

但这方便吗?查询是什么样的?

select    relatedThingType = case
             when b.buyerId is not null then 'buyer'
             when s.supplierId is not null then 'supplier'
             else '????'
          end     
from      PhoneNumbers p
left join Buyers       b on b.buyerId = p.buyerId
left join Suppliers    s on s.supplierId = p.supplierId
where     p.phoneNumber = @phoneNumber

不太方便。我们在这里与模式作斗争,防止两者都为空的情况,根据我们的案例陈述的顺序任意决定如果两个外键都被填充,我们将选择买方而不是供应商,等等。

好的,如果我们按照建议和您更新的图表将其颠倒过来会怎样?

create table PhoneNumbers
(
   phoneNumber varchar(16) primary key
);

create table Buyers
(
   buyerId int primary key,
   buyerName varchar(32),
   phoneNumber varchar(16) foreign key references phoneNumbers
);

create table Suppliers
(
   supplierId int primary key,
   supplierName varchar(32),
   phoneNumber varchar(16) foreign key references phoneNumbers
);

有一点很明显:给定一个电话号码,我们实际上不必加入 PhoneNumbers 表来找出谁拥有它 - 只要我们不使用代理 ID 而是使用电话号码本身作为键。

事实上,具有单列的表(因此必然是键)在查询中从来没有用过。它们的唯一功能是强制引用完整性(并使级联操作可用)。

好的,查询时间

select   relatedThingType = 'buyer'
from     Buyers 
where    phoneNumber = @phoneNumber
union all
select   'supplier'
from     Suppliers
where    phoneNumber = @phoneNumber;

基本上,join 变成了union。这样更方便吗?我认为,如果有一些“查询结构的整体大小”的度量标准,那将是大致相同的。但是我们不再那么频繁地与模式作斗争了。我们还“自然地”允许买家和供应商都拥有电话号码,如果发生这种情况,两者都会被退回。

因此,这些模型在可用基数方面存在功能差异,这也意味着查询语义存在差异。到目前为止,选择并不是真正基于“什么更方便”,而是基于“您的模型需要强制执行的基数是什么”?

但是...这个版本稍微更方便。添加新的 relatedThingType 不需要我们更改任何架构。我们只是将另一个 union all 附加到查询中。

好的,大揭秘时间。我一直在故意使用这个尴尬的术语relatedThingType。我们显然对这是什么没有一个清晰的概念。我们如何解决这个问题?

问题是,您实际上是在说“买家和供应商都有一个共同点:他们都是可以有电话号码的东西。但在其他方面它们是不同的”。

如果您进行任何非 sql 编程,那闻起来有点像继承关系。当您查看这两个表的模式时,会得到更多暗示。他们都有名字。所以他们有一个共同的属性。在 OOAD 中,这将是基类上的一个字段。

因此,解决此问题的一种方法是明确承认该基类。买家和供应商都是当事人,当事人可以有一个电话号码。

create table Parties 
(
   partyType varchar(16) check (partyType in ('buyer', 'supplier')),
   partyId int primary key,
   phoneNumber varchar(16),   
   partyName varchar(32),
   constraint pk_p unique (partyType, partyId)
);

create table Buyers
(
   partyId int primary key,
   partyType varchar(16) default 'buyer' check (partyType = 'buyer'),
   -- other buyer specific columns
   constraint fk_b_p foreign key (partyType, partyId) references Parties(partyType, partyId)
);

create table Suppliers
(
   partyId int primary key,
   partyType varchar(16) default 'supplier' check (partyType = 'supplier'),
   -- other supplier specific columns
   foreign key (partyType, partyId) references Parties(partyType, partyId)
);

看起来不太方便。但是等等,这就是 schema。我们刚才讨论的查询呢?

select  partyType, partyName, phoneNumber
from    Parties
where   phoneNumber = @phoneNumber

这很方便。

当然,您可以使用视图获得相同的结果,其定义与我之前编写的union all 查询基本相同。您是否真的要“实例化基类”只是一种选择。我们稍后会讨论如何做出选择...

但是模式中那些奇怪的默认和检查约束是怎么回事?他们正在执行的规则是,一方只能是任一供应商,买方,但不能同时是两者。我们永远不会让买家和供应商意外共享partyId。这是一种“排他的子类型”关系。如果您需要强制执行此操作,则需要实例化基类。视图不会削减它。

我们可以走得更远,因为前一段暗示了另一种可能的情况:包含子类型关系的情况。一方可以是既是买家,又是供应商。为此,您将进入“基于角色的建模”模式,其中Party 具有一个或多个Roles,由PartyRoles 联结表确定。我通常将其称为“CounterParties”表,因为每一行都代表某个参与方在我们的观点中扮演某种角色 - 即,它们是由角色定义的某种特定类型的交易对手。

好的,它的架构是什么?嗯...我不会真的做这个。我会把它留作练习。

嗯,这一切都很好,很学术,但你还没有给我答案!

确实如此。但这里的想法不是给出“唯一的真实答案”。希望这一切都表明的正确答案取决于你的模型需要建模的内容。这取决于您的域。买家也可以是供应商吗?当事人可以有多个电话号码吗?一个电话号码可以关联多个买家吗?这些问题的答案对你的模式必须做什么和不能做什么提出了严格的要求。您构建的任何模式都必须遵守这些约束。我提供的各种选项都遵循不同的域约束集,它们并不全都等价。

所以,你的问题的答案是......你会恨我......

“视情况而定”。


请原谅 DDL 中的任何语法错误。它只是为了说明概念

【讨论】:

  • 我非常感谢您的 cmets,他们确实反映了我的很多想法。我刚刚用稍微不同的图表更新了这个问题,但概念相似。
  • @Marcoscdoni 如果您认为“实例化基类”是您的最佳解决方案,那就去吧!但是请......不要称它为“实体”。 “实体”一词是字面上任何东西的通用编程词。对开发者来说,我的咖啡杯是一个“实体”,但我的咖啡杯放在你的桌子上是没有意义的!派对是您将在所有参考书中看到的术语,但您可以使用组织或公司,或任何在您的领域中最有意义的东西。但请不要“实体”。也不是“事物”,也不是“对象”,也不是“行”,也不是“数据”,也不是......你明白了:)
  • allmhuran 只是举例,不是真实的图表。事实上,我来自巴西,这张桌子会用一个合适的词来命名。关于表的设计是否有任何不使用基表的具体原因?虽然 SQL Server 是一个关系型数据库,呈现出来的结构看起来很像 OOP,但它的表之间仍然有清晰且定义明确的关系,对吧?正如我之前所说,买方和供应商示例表有几个不同的字段,此外,如果我使用电话存储在各方中的结构,电话号码必须是唯一的。
  • @Marcoscdoni 不,如果它符合您领域的要求,设计绝对合理。我用过很多次了。
  • allmhuran 谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-19
  • 2021-10-09
  • 2018-10-20
  • 1970-01-01
  • 2016-03-05
  • 1970-01-01
相关资源
最近更新 更多