我的数据库里有三个相关的表。
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner int NOT NULL,
Subject varchar(50) NULL
)
用户属于多个组。这是通过多对多关系实现的,但在本例中不相关。通过dbo.ticket.owner字段,票证可以归组或用户所有。
描述票证与可选用户或组之间关系的“最正确”方式是什么?
我想我应该在ticket表中添加一个标志,说明什么类型拥有它。
8条答案
按热度按时间dxxyhpgq1#
你有几个选择,都在“正确性”和易用性上有所不同。一如既往,正确的设计取决于你的需要。
下面是一个使用发布的模式的粗略示例:
lnvxswe22#
@Nathan Skerl的列表中的第一个选项是我曾经参与的一个项目中实现的,其中在三个表之间建立了类似的关系(其中一个引用了另外两个表,一次引用一个)。
因此,引用表有两个外键列,而且它有一个约束来保证一行引用一个表(不是两个表,也不是两个表都引用)。
以下是应用到表时的外观:
如您所见,
Ticket
表有两列OwnerGroup
和OwnerUser
,这两列都是可空外键(其他两个表中的相应列也相应地成为主键)。CK_Ticket_GroupUser
检查约束确保两个外键列中只有一个包含引用(另一个为NULL,这就是为什么两个都必须是可空的)。(The
Ticket.ID
上的主键对于这个特定的实现不是必需的,但是在这样的表中有一个主键肯定不会有什么坏处。)ryhaxcpt3#
另一种方法是创建一个关联表,其中包含每个潜在资源类型的列。在您的示例中,两个现有所有者类型中的每一个都有自己的表(这意味着您有一些要参考的内容)。如果始终是这种情况,则您可以创建如下内容:
使用此解决方案,您可以在向数据库添加新实体的同时继续添加新列,并删除和重新创建@Nathan Skerl所示的外键约束模式。此解决方案与@Nathan Skerl非常相似,但看起来不同(取决于首选项)。
如果您不打算为每个新的所有者类型创建一个新的表,那么为每个潜在的所有者包含一个owner_type而不是一个外键列可能会更好:
通过上述方法,您可以添加任意多的所有者类型。Owner_ID将没有外键约束,但将用作对其他表的引用。缺点是,您必须查看该表以了解所有者类型,因为根据架构,所有者类型并不立即显而易见。只有在您事先不知道所有者类型并且它们不会“Don“不要链接到其他表。如果您事先知道所有者类型,我会使用类似于@Nathan Skerl的解决方案。
抱歉,如果我有一些SQL错误,我只是扔在一起。
s6fujrry4#
还有一种选择是在
Ticket
中有一列指定拥有实体类型(User
或Group
),第二列具有引用的User
或Group
id,并且不使用外键,而是依赖触发器来实施引用完整性。与Nathan的excellent model(上图)相比,我在这里看到了两个优点:
2w3rbyxf5#
你也可以使用一个枚举来标识
Owner
是用户还是组,如下所示:也许它并不比任何建议的解决方案更好,它可能没有提供任何优势。事实上,我认为这可能需要修改
Enum_OwnerType
,甚至ticket
,以改变OwnerType
,我猜...我希望它是有用的无论如何。piah890a6#
我有很多这样的情况下,我只是使用多态能力如下:
示例
我有一个包含
id
、amount
、user_id
列的营业额表,我需要知道每条记录的引用,所以我只添加了table_id
和table_type
两个字段,最终营业额表为id
、amount
、user_id
、table_id
、table_type
。1
,25000
,2
,22
,order
]1
,25000
,2
,23
,credit
]注解
如果使用M:M表,它需要这么多的时间,两个检索记录和我的方式
nnt7mjpx7#
nathan_jr的第四个选项(建模一个实体,作为用户和组的基础,并拥有该实体所拥有的票据)不强制PartyId的引用完整性。你必须在应用层这样做,这会引起各种各样的麻烦。当django的genericforeignkey实现相同的解决方案时,不能称之为反模式。但毫无疑问,你可以使用框架的orm(使用类似于django的多表继承)设计出更健壮、性能更高的东西
8oomwypt8#
我认为这将是最通用的方式来表示你想要什么,而不是使用一个标志。