如果我正确地理解了您的规范,那么您的方案将涉及(除其他重要方面之外)超类型-子类型结构。
我将在下面举例说明如何(1)在概念的抽象级别对其进行建模,然后(2)在逻辑级别的 DDL设计中表示它。
商业规则
以下概念表述是您的业务环境中最重要的规则:
- 甲播放列表由或恰好一个拥有组或恰好一个用户在特定时间点
- 一个播放列表可以由一个一对多所拥有的组或用户在不同的时间点
- 一个用户拥有零一或一对多的播放列表
- 一组拥有零个或多个播放列表
- 一组由一到多的达议员(谁必须为用户)
- 一个用户可能是一个会员的零一或一对多的群组。
- 一组由一到多的达议员(谁必须为用户)
由于之间的关联或关系(一)用户和播放列表和(b)之间的集团和播放列表都不太一样,这个事实表明,用户和组是相互排斥的实体亚型党1,这是在把自己的实体超-supertype-子类型集群是经典的数据结构,它们以非常多种形式的概念模式出现。通过这种方式,可以断言两个新规则:
- 一个党正好由一个分类PartyType
- 一个党可以是一个组或用户
前面的四个规则必须重新定义为三个:
- 一个播放列表正好由一个拥有党在特定时间点
- 一个播放列表可以由一个一对多所拥有缔约方在不同的时间点
- 一党拥有零一或一对多的播放列表
信息库IDEF1X图表
图1中显示的IDEF1X 2图将所有上述业务规则与其他相关的业务规则合并在一起:
如图所示,Group和User被描绘为子类型,这些子类型由相应的行和排他性符号与Party(超类型)连接。
该Party.PartyTypeCode物业代表亚型鉴别,即,它表示一种亚型实例必须补充一个给定的超发生。
而且,Party通过OwnerId属性与播放列表连接,该属性被描述为指向Party.PartyId的FOREIGN KEY 。这样,Party将(a)播放列表与(b)Group和(c)User相关联。
因此,由于特定的Party实例是Group或User,所以特定的播放列表最多可以与一个子类型出现链接。
说明性逻辑层布局
之前阐述的IDEF1X图已将我用作创建以下逻辑SQL-DDL布置的平台(并且我已提供注释作为注释,突出显示了几个特别相关的点,例如约束声明):
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define the
-- most convenient INDEX strategies based on the exact
-- data manipulation tendencies of your business domain.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE PartyType ( -- Represents an independent entity type.
PartyTypeCode CHAR(1) NOT NULL,
Name CHAR(30) NOT NULL,
--
CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode),
CONSTRAINT PartyType_AK UNIQUE (Name)
);
CREATE TABLE Party ( -- Stands for the supertype.
PartyId INT NOT NULL,
PartyTypeCode CHAR(1) NOT NULL, -- Symbolizes the discriminator.
CreatedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT Party_PK PRIMARY KEY (PartyId),
CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
REFERENCES PartyType (PartyTypeCode)
);
CREATE TABLE UserProfile ( -- Denotes one of the subtypes.
UserId INT NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
UserName CHAR(30) NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
GenderCode CHAR(3) NOT NULL,
BirthDate DATE NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Multi-column ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (UserName), -- Single-column ALTERNATE KEY.
CONSTRAINT UserProfileToParty_FK FOREIGN KEY (UserId)
REFERENCES Party (PartyId)
);
CREATE TABLE MyGroup ( -- Represents the other subtype.
GroupId INT NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Title CHAR(30) NOT NULL,
--
CONSTRAINT Group_PK PRIMARY KEY (GroupId),
CONSTRAINT Group_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT GroupToParty_FK FOREIGN KEY (GroupId)
REFERENCES Party (PartyId)
);
CREATE TABLE Playlist ( -- Stands for an independent entity type.
PlaylistId INT NOT NULL,
OwnerId INT NOT NULL,
Title CHAR(30) NOT NULL,
CreatedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT Playlist_PK PRIMARY KEY (PlaylistId),
CONSTRAINT Playlist_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT PartyToParty_FK FOREIGN KEY (OwnerId) -- Establishes the relationship with (a) the supertype and (b) through the subtype with (c) the subtypes.
REFERENCES Party (PartyId)
);
CREATE TABLE GroupMember ( -- Denotes an associative entity type.
MemberId INT NOT NULL,
GroupId INT NOT NULL,
IsOwner BOOLEAN NOT NULL,
JoinedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT GroupMember_PK PRIMARY KEY (MemberId, GroupId), -- Composite PRIMARY KEY.
CONSTRAINT GroupMemberToUserProfile_FK FOREIGN KEY (MemberId)
REFERENCES UserProfile (UserId),
CONSTRAINT GroupMemberToMyGroup_FK FOREIGN KEY (GroupId)
REFERENCES MyGroup (GroupId)
);
当然,您可以进行一项或多项调整,以便在实际数据库中以所需的精度表示业务上下文的所有特征。
注意:我已经在db <> fiddle和此SQL Fiddle上测试了上述逻辑布局,它们都在PostgreSQL 9.6上“运行”,以便您可以看到它们“正在运行”。
Sl
如您所见,我没有在DDL声明中包括Group.Slug
也不Playlist.Slug
作为列。这是因为,按照您的以下解释
这些slug
是各自实体的唯一,小写,带连字符的版本title
。例如,group
带有title
“测试组”的a将具有slug
“测试组”。重复项将附加增量整数。每当他们title
改变时,这都会改变。我相信这意味着他们不会做出出色的主键吗?是的,slugs
并且usernames
在各自的表中是唯一的。
可以得出结论,它们的值是可导的(即,它们必须根据对应的Group.Title
和Playlist.Title
值进行计算或计算,有时与(我认为是某种系统生成的INTEGER)结合使用),因此我不会声明所述列在任何基本表中,因为它们会引入更新不规则性。
相反,我会产生 Slugs
以这种方式,任何这两种方法将避免了“更新同步”机制,应该到位当且仅当所述Slugs
被保持在基表的列。
完整性和一致性考虑
关键是要提及的是(ⅰ)每 Party
行必须补充在任何时候都通过(ii)所述相应的对应物在恰好一个站立的亚型,其(III)必须“符合”的表中所包含的值Party.PartyTypeCode
的列-表示区分符-。
以声明的方式实施这种情况将是非常有利的,但是没有一个主要的SQL数据库管理系统(包括Postgres)提供了必要的工具来像这样进行。因此,到目前为止,在ACID TRANSACTIONS内编写过程代码是确保数据库中始终满足上述情况的最佳选择。另一种可能性是求助于TRIGGERS,但是可以这么说,他们倾向于使事情变得不整洁。
可比案例
如果您想建立一些类比,则可能有兴趣查看我对题为(较新)问题的回答
因为讨论了可比较的方案。
尾注
1 当事人是指在法律上下文中指组成单个实体的个人或一组个人时使用的术语,因此该名称适用于代表有关业务环境的用户和组的概念。
2 信息建模集成定义( IDEF1X)是一种高度推荐的数据建模技术,该技术已于1993年12月由美国国家标准技术研究院(NIST)建立为标准。它扎实地基于(a)由关系模型的唯一发起人,即 EF Codd博士撰写的一些早期理论著作;(b)由陈PP博士提出的关于实体关系的观点;以及(c)Robert G. Brown创建的逻辑数据库设计技术。