是的,对多对多(为简便起见,为M:N)的关联或关系的标识是数据库从业人员在布局概念图时经常面临的情况。所述基数比的关联在性质非常不同的业务环境中出现,并且当通过例如SQL-DDL安排在逻辑级别正确表示时,它们不会引入有害的冗余。
这样,数据库建模练习的目标应该是高精度地反映感兴趣的业务上下文的相关特征;因此,如果您正确地识别出存在多个M:N关联,则无论该关联有多少个连接,您都必须在(a)概念模式以及(b)相应的逻辑级别声明中表达它们其他—基数比必须解决。
商业规则
您已经提供了一个经过充分考虑的问题,并且还澄清了您正在使用的数据库纯粹是假设的,这很重要,因为我认为正在考虑的“真实世界”业务场景将更加广泛因此,这意味着更复杂的信息需求。
我决定(1)对您提供的业务规则进行一些修改和扩展,以便(2)产生更具描述性的概念模式(尽管仍然是假设的)。这是我汇总的一些公式:
- 一个党1可以是一个人或一个组织
- 一个党是由恰好一个分类PartyType
- 一个PartyType进行分类零一或一对多的缔约方
- 一个组织开发零一对多产品
- 一个产品或者是一个系统或一个游戏
- 一个产品是由恰好一个分类ProductType
- 一个系统是由恰好一个编目SystemType中
- 一个游戏可以通过一个一对多播放系统
- 一个系统是用来玩一个一对多的游戏
- 一个游戏是由零一或一对多分类流派
- 一个类型进行分类零一或一对多的游戏
- 一个产品源自一个一对多的乔布斯
- 一个工作是通过零一或一对多履行人民,谁在玩角色的合作者
- 一个人是零一个或多个工作的协作者
1 缔约方是指在法律范围内指组成单个实体的个人或一组个人时使用的术语,因此该名称适用于代表人员和组织。
IDEF1X图表
随后,我创建了图1中所示的IDEF1X 2图表(确保单击链接以更高分辨率查看),将上面显示的业务规则(以及一些其他相关的视图)整合到单个图形设备中:
2 信息建模集成定义(IDEF1X)是一种高度推荐的数据建模技术,由美国国家标准技术研究院(NIST)于1993年12月建立为标准。它基于(a)由关系模型的唯一发起人,即EF Codd博士撰写的早期理论材料;(b)由陈PP博士开发的数据的实体关系视图;(c)由Robert G. Brown创建的逻辑数据库设计技术。
如您所见,通过对应的关联实体类型,我仅描绘了三个M:N关联,即:
除其他方面外,还有两个不同的超型-亚型结构,其中:
如果您不熟悉超类型与子类型的关联,则可能会找到帮助,例如,我对题为:
说明性逻辑SQL-DDL布局
接下来,我们必须确保在逻辑层面上:
- 每种实体类型都由一个单独的基表表示
- 适用实体类型的每个单个属性都由一个特定的列表示
- 确切的数据类型是固定的每列,以确保所有值它包含属于特定和良好定义的组,无论是INT,DATETIME,CHAR,等等(当然,使用时,例如,火鸟或PostgreSQL的,您可能希望使用功能更强大的DOMAIN)
- 配置(声明性)多个约束,以确保保留在所有表中的行形式的断言符合在概念级别确定的业务规则
因此,我根据先前显示的IDEF1X图声明了以下DDL安排:
CREATE TABLE PartyType ( -- Stands for an independent entity type.
PartyTypeCode CHAR(1) NOT NULL, -- To retain 'P' or 'O'.
Name CHAR(30) NOT NULL, -- To keep 'Person' or 'Organization'.
--
CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode)
);
CREATE TABLE Party ( -- Represents an entity supertype.
PartyId INT NOT NULL,
PartyTypeCode CHAR(1) NOT NULL, -- To hold the value that indicates the type of the row denoting the complementary subtype occurrence: either 'P' for 'Person' or 'O' for 'Organization'.
CreatedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT Party_PK PRIMARY KEY (PartyId),
CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
REFERENCES PartyType (PartyTypeCode)
);
CREATE TABLE Person ( -- Denotes an entity subtype.
PersonId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
GenderCode CHAR(3) NOT NULL,
BirthDate DATE NOT NULL,
--
CONSTRAINT Person_PK PRIMARY KEY (PersonId),
CONSTRAINT Person_AK UNIQUE (FirstName, LastName, GenderCode, BirthDate), -- Composite ALTERNATE KEY.
CONSTRAINT PersonToParty_FK FOREIGN KEY (PersonId)
REFERENCES Party (PartyId)
);
CREATE TABLE Organization ( -- Stands for an entity subtype.
OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Name CHAR(30) NOT NULL,
FoundingDate DATE NOT NULL,
--
CONSTRAINT Organization_PK PRIMARY KEY (OrganizationId),
CONSTRAINT Organization_AK UNIQUE (Name), -- Single-column ALTERNATE KEY.
CONSTRAINT OrganizationToParty_FK FOREIGN KEY (OrganizationId)
REFERENCES Party (PartyId)
);
CREATE TABLE ProductType ( -- Represents an independent entity type.
ProductTypeCode CHAR(1) NOT NULL, -- To enclose the values 'S' and 'G' in the corresponding rows.
Name CHAR(30) NOT NULL, -- To comprise the values 'System' and 'Person' in the respective rows.
--
CONSTRAINT ProductType_PK PRIMARY KEY (ProductTypeCode)
);
CREATE TABLE Product ( -- Denotes an entity supertype.
OrganizationId INT NOT NULL,
ProductNumber INT NOT NULL,
ProductTypeCode CHAR(1) NOT NULL, -- To keep the value that indicates the type of the row denoting the complementary subtype occurrence: either 'S' for 'System' or 'G' for 'Game'.
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Product_PK PRIMARY KEY (OrganizationId, ProductNumber), -- Composite PRIMARY KEY.
CONSTRAINT ProductToOrganization_FK FOREIGN KEY (OrganizationId)
REFERENCES Organization (OrganizationId),
CONSTRAINT ProductToProductType_FK FOREIGN KEY (ProductTypeCode)
REFERENCES ProductType (ProductTypeCode)
);
CREATE TABLE SystemType ( -- Stands for an independent entity type.
SystemTypeCode CHAR(1) NOT NULL,
Name CHAR(30) NOT NULL,
--
CONSTRAINT SystemType_PK PRIMARY KEY (SystemTypeCode)
);
CREATE TABLE MySystem ( -- Represents a dependent entity type.
OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
SystemNumber INT NOT NULL,
SystemTypeCode CHAR(1) NOT NULL,
ParticularColumn CHAR(30) NOT NULL,
--
CONSTRAINT System_PK PRIMARY KEY (OrganizationId, SystemNumber),
CONSTRAINT SystemToProduct_FK FOREIGN KEY (OrganizationId, SystemNumber)
REFERENCES Product (OrganizationId, ProductNumber),
CONSTRAINT SystemToSystemType_FK FOREIGN KEY (SystemTypeCode)
REFERENCES SystemType (SystemTypeCode)
);
CREATE TABLE Game ( -- Denotes an entity subtype.
OrganizationId INT NOT NULL, -- To be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
GameNumber INT NOT NULL,
SpecificColumn CHAR(30) NOT NULL,
--
CONSTRAINT Game_PK PRIMARY KEY (OrganizationId, GameNumber),
CONSTRAINT GameToProduct_FK FOREIGN KEY (OrganizationId, GameNumber)
REFERENCES Product (OrganizationId, ProductNumber)
);
CREATE TABLE Genre ( -- Stands for an independent entity type.
GenreNumber INT NOT NULL,
Name CHAR(30) NOT NULL,
Description CHAR(90) NOT NULL,
--
CONSTRAINT Genre_PK PRIMARY KEY (GenreNumber),
CONSTRAINT Genre_AK1 UNIQUE (Name),
CONSTRAINT Genre_AK2 UNIQUE (Description)
);
CREATE TABLE SystemGame ( -- Represents an associative entity type or M:N association.
SystemOrganizationId INT NOT NULL,
SystemNumber INT NOT NULL,
GameOrganizationId INT NOT NULL,
GameNumber INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT SystemGame_PK PRIMARY KEY (SystemOrganizationId, SystemNumber, GameOrganizationId, GameNumber), -- Composite PRIMARY KEY.
CONSTRAINT SystemGameToSystem_FK FOREIGN KEY (SystemOrganizationId, SystemNumber) -- Multi-column FOREIGN KEY.
REFERENCES MySystem (OrganizationId, SystemNumber),
CONSTRAINT SystemGameToGame_FK FOREIGN KEY (SystemOrganizationId, GameNumber) -- Multi-column FOREIGN KEY.
REFERENCES Game (OrganizationId, GameNumber)
);
CREATE TABLE GameGenre ( -- Denotes an associative entity type or M:N association.
GameOrganizationId INT NOT NULL,
GameNumber INT NOT NULL,
GenreNumber INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT GameGenre_PK PRIMARY KEY (GameOrganizationId, GameNumber, GenreNumber), -- Composite PRIMARY KEY.
CONSTRAINT GameGenreToGame_FK FOREIGN KEY (GameOrganizationId, GameNumber)
REFERENCES Game (OrganizationId, GameNumber), -- Multi-column FOREIGN KEY.
CONSTRAINT GameGenreToGenre_FK FOREIGN KEY (GenreNumber)
REFERENCES Genre (GenreNumber)
);
CREATE TABLE Job ( -- Stands for an associative entity type or M:N association.
OrganizationId INT NOT NULL,
ProductNumber INT NOT NULL,
JobNumber INT NOT NULL,
Title CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Job_PK PRIMARY KEY (OrganizationId, ProductNumber, JobNumber), -- Composite PRIMARY KEY.
CONSTRAINT Job_AK UNIQUE (Title), -- Single-column ALTERNATE KEY.
CONSTRAINT JobToProduct_FK FOREIGN KEY (OrganizationId, ProductNumber) -- Multi-column FOREIGN KEY.
REFERENCES Product (OrganizationId, ProductNumber)
);
CREATE TABLE Collaborator ( -- Represents an associative entity type or M:N association.
CollaboratorId INT NOT NULL,
OrganizationId INT NOT NULL,
ProductNumber INT NOT NULL,
JobNumber INT NOT NULL,
AssignedDateTime DATETIME NOT NULL,
--
CONSTRAINT Collaborator_PK PRIMARY KEY (CollaboratorId, OrganizationId, ProductNumber, JobNumber), -- Composite PRIMARY KEY.
CONSTRAINT CollaboratorToPerson_FK FOREIGN KEY (CollaboratorId)
REFERENCES Person (PersonId),
CONSTRAINT CollaboratorToJob_FK FOREIGN KEY (OrganizationId, ProductNumber, JobNumber) -- Multi-column FOREIGN KEY.
REFERENCES Job (OrganizationId, ProductNumber, JobNumber)
);
需要强调的是,在多个表中都有复合 PRIMARY KEY约束的声明,它们表示概念实体类型之间发生的连接层次结构,这种结构对于例如表示SELECT的数据检索非常有益。包含JOIN子句的操作以获取派生表。
是的,(i)每个M:N关联和(ii)每个关联的实体类型都由(iii)逻辑DDL结构中的对应表表示,因此请特别注意PRIMARY和FOREIGN KEY约束(以及代表这些概念性元素的表格的注释(我留为注释),因为它们有助于确保相关行之间的连接满足适用的基数比。
EF Codd博士从关系范式的起源介绍了组合键的用法,正如他在1970年的开创性论文《大型共享数据库的关系模型》中所列举的示例所证明的(确切地说,该模型还介绍了最优雅的方法来处理概念上的M:N关联)。
我放置了 db <> fiddle和SQL Fiddle,它们都在Microsoft SQL Server 2014上运行,以便可以“实际”测试结构。
正常化
规范化是一个逻辑级别的过程,基本上可以暗示:
通过第一个范式消除非原子列,以便通过使用数据的子语言(例如SQL)更轻松地应对数据操作和约束。
借助连续的范式消除特定表的列之间的不良依赖关系,以避免更新异常。
自然,必须考虑所涉及的表和列所具有的含义。
我喜欢将规范化视为一种基于科学的测试,即设计师在划定稳定的逻辑级别排列以确定其项是否符合每种正常形式后,便将其应用于相关元素。然后,如果需要,设计人员将采取适当的纠正措施。
冗余
在关系模型中,尽管重复包含在列中的值不仅可以接受而且可以预期,但禁止重复行。就此而言,据我所知,在以前公开的逻辑布局中包含的所有表中,都避免了重复行和其他类型的有害冗余,也许您想澄清一下这方面的问题。
无论如何,您当然可以(a)通过正常形式对自己所说的结构进行评估,以定义它是否满足要求,并且(b)必要时进行修改。
相关资源
- 在本系列文章中,我将对一些简单的M:N关联进行一些讨论,这些关联可以使两种不同实体类型的实例相互关联。
- 在另一篇文章中,我提出了一种方法来处理“材料清单”或“零件爆炸”构造的发生,其中我描述了如何连接同一类型实体的不同实例。
三元协会
您还通过注释提出了另一个重要方面(发布在现已删除的答案中):
每当我尝试建立一座桥梁时,这座桥梁中的元素也具有“多对多”的感觉,这给我的印象是不允许或至少不鼓励这样做。
这种情况似乎表明您关注的问题之一与概念性三元关联有关。基本上说,当存在(1)涉及(2)两种其他关系的关系,换句话说是“关系之间的关系”时,也会发生这种关联,这也是一种典型情况,因为关系本身就是一个实体—。
如果妥善管理这些安排,也不会带来有害的冗余。而且,是的,如果在某种特定的用例中您确定这种关系在“现实世界”实体类型之间存在,则您必须(i)建模并(ii)在逻辑级别准确地声明它们。