我正在使用带有Sequelize的 PostgreSQL作为我的 ORM。
我有一种类型,User
. 第二种类型是Group
,它可以通过一个GroupMemberships
表关联任意数量的用户。User
s 也可以拥有任意数量的Group
s。
我的第三种类型 ,Playlist
可以属于 aUser
或 a group
。为这种类型设计模式的最佳方法是什么,以便它可以拥有一种类型的所有者或任何一种?
我的第一遍创建了两个关联,但一次只填充了一个。这可能可行,但看起来很hacky并且使查询变得困难。
附加信息
以下是我对 MDCCL 通过评论发布的澄清请求的回应:
(1) 如果一个播放列表属于给定组,可以说这个播放列表与一对多用户相关,只要他们是该组的成员,对吗?
我相信这在技术上是正确的,但是这种一对多的关联并不明确存在。
(2) 那么,一个特定的播放列表是否可以同时由一对多的组拥有?
不, a 不可能Playlist
由 one-to-many 拥有Groups
。
(3) 特定播放列表是否可以由一对多组拥有,同时由不是该组成员的一对多用户拥有?
不,因为如(2)中的一对多 from Playlist
toGroup
不应该存在。此外,如果 aPlaylist
由 a 拥有,Group
则它不属于 a User
,反之亦然。一次只有一个所有者。
(4) 用于唯一标识组、用户和播放列表的属性是什么?
它们每个都有一个代理主键 ( id
) 和一个自然键(虽然不是主键)。这些slug
是Group
和Playlist
,和username
。User
(5) 特定播放列表是否会发生所有者更改?
尽管我不打算将其作为一个功能(至少最初是这样),但我认为这可能会发生。
(6) Group.Slug和Playlist.Slug属性是什么意思?它们的值是否足够稳定,可以定义为主键,还是经常更改?这两个属性的值以及User.Username必须是唯一的,对吗?
这些slug
s 是其各自实体的唯一、小写、连字符版本title
。例如,group
带有title
“测试组”的 a 将具有slug
“测试组”。重复项附加了增量整数。这将随时改变他们的title
变化。我相信这意味着他们不会做出很好的主键?是的,slugs
并且usernames
在各自的表格中是独一无二的。
如果我正确理解了您的规范,那么您的场景涉及(除其他重要方面外)超类型 - 子类型结构。
我将在下面举例说明如何(1)在抽象的概念级别对其进行建模,然后(2)在逻辑级别的DDL 设计中表示它。
商业规则
以下概念公式是您业务环境中最重要的规则之一:
由于 (a) User和Playlist之间以及 (b) Group和Playlist之间的关联或关系非常相似,这一事实表明User和Group是Party 1的互斥实体子类型,而后者又是它们的实体超类型 -supertype-子类型集群是经典的数据结构,出现在非常多样化的概念模式中——。以这种方式,可以断言两个新规则:
之前的四个规则必须重新表述为只有三个:
说明性IDEF1X图
图 1所示的 IDEF1X 2图表整合了所有上述业务规则以及其他相关的规则:
如图所示,Group和User被描绘为子类型,它们通过各自的线和与超类型Party的专有符号连接。
Party.PartyTypeCode属性代表子类型鉴别器,即它指示哪种子类型实例必须补充给定的超类型出现。
此外,Party通过OwnerId属性与Playlist连接,该属性被描述为指向Party.PartyId的 FOREIGN KEY 。通过这种方式,Party将 (a) Playlist与 (b) Group和 (c) User相互关联。
因此,由于特定的Party实例是Group或User,特定的播放列表最多可以与一个子类型出现链接。
说明性逻辑级布局
之前阐述的 IDEF1X 图为我提供了创建以下逻辑 SQL-DDL 排列的平台(并且我提供了注释作为注释,突出了几个特别相关的点——例如,约束声明——):
当然,您可以进行一项或多项调整,以便在实际数据库中以所需的精度表示业务上下文的所有特征。
注意:我已经在这个 db<>fiddle和这个 SQL Fiddle上测试了上面的逻辑布局,它们都在 PostgreSQL 9.6 上“运行”,所以你可以看到它们“在运行”。
蛞蝓
如您所见,我没有在 DDL 声明中包含
Group.Slug
也不Playlist.Slug
作为列。之所以如此,是因为,与您的以下解释一致可以得出结论,它们的值是可导出的(即,它们必须根据相应的
Group.Title
和Playlist.Title
值来计算或计算,有时与 - 我假设,某种系统生成的 - INTEGER 结合使用),所以我不会声明所述列在任何基表中,因为它们会引入更新异常。相反,我会生产
Slugs
也许,在一个视图中,它 (a) 包括虚拟列中这些值的派生,并且 (b) 可以直接用于进一步的 SELECT 操作 - 附加 INTEGER 部分可以获得,例如,通过组合 (1) 的值带有 (2) 中间连
Playlist.OwnerId
字符和 (3) 的值Playlist.Title
;或者,通过应用程序代码,模仿之前描述的方法(可能在程序上),一旦相关数据集被检索并格式化以供最终用户解释。
以这种方式,这两种方法中的任何一种都可以避免“更新同步”机制,如果
Slugs
它们保留在基表的列中,则该机制应该到位。完整性和一致性考虑
重要的是要提到 (i)每一
Party
行必须始终由 (ii)恰好一个代表子类型的表中的相应对应物补充,(iii) 必须“符合”Party.PartyTypeCode
列中包含的值——表示鉴别器——。以声明的方式强制执行这种情况是非常有利的,但是没有一个主要的 SQL 数据库管理系统(包括 Postgres)提供了必要的工具来进行这样的操作;因此,到目前为止,在ACID TRANSACTIONS中编写过程代码是保证在您的数据库中始终满足前面描述的情况的最佳选择。其他可能性是诉诸触发器,但可以这么说,它们很容易使事情变得不整洁。
可比案例
如果您想建立一些类比,您可能有兴趣查看我对题为的(较新)问题的回答
因为讨论了可比较的场景。
尾注
1 Party是法律上下文中使用的术语,指的是组成单个实体的个人或一,因此该名称适用于代表用户和集团在相关业务环境方面的概念。
2 信息建模集成定义( IDEF1X ) 是一种高度推荐的数据建模技术,于 1993 年 12 月由美国国家标准与技术研究院(NIST)确立为标准。它完全基于 (a) 由关系模型的唯一创始人,即EF Codd 博士撰写的一些早期理论著作;(b)由PP Chen 博士开发的实体关系视图;以及 (c) Robert G. Brown 创建的逻辑数据库设计技术。