处理删除或启用/删除行及其引用表的最佳做法是什么?
例如,假设我有一个非常简单的'论坛'应用程序。
我有一个表users
,其中包含我的webapp帐户和threads
,其中包含用户创建的线程,还有一个表comments
,其中包含用户对线程发表评论的评论。
现在,请注意,在注册时我想在激活其帐户之前验证用户的电子邮件。这是最好的方法吗?什么是最佳做法?也许通过只返回带有字段is_active=true
的行的视图?使用2个分隔的表格,例如pre_users
(包含仍需要验证的用户)和users
(已验证的表)?
同样,您将如何处理想要暂停其帐户的用户?它的主题和评论?您是否会添加另一个标志is_suspended
,并更新视图以考虑该标志?如果它不是一个视图,而是两个单独的表,我怎么能处理引用?
另外,删除一个帖子。假设在线程删除时我不希望实际删除该线程,因为我不希望发布注释的用户突然看不到他们的注释。怎么处理这个?
针对这类问题的最佳做法是什么?
答案 0 :(得分:10)
对于活动状态不影响唯一性的表(例如,用户通过用户名或电子邮件地址标识,不可能有相同内容的活动和非活动版本)我使用可空的日期时间字段来代表一种状态。例如,对于您的users表,我有一个verified_at列,该列最初设置为null,并且当用户验证其帐户时的当前日期/时间。用户帐户暂停也可以这样做。如果用户重新激活他们的帐户,我们只需将suspended_at字段设置为null。
如果您需要的状态值多于简单的是/否,我会使用单独的字段来更改状态值和日期/时间。
如果您想跟踪更新的历史记录(例如激活/暂停),最好在单独的表中执行此操作。在这种情况下,您可以从users表中引用当前激活记录,这将更有效地使用表的索引,而不是查询状态列。
在某些情况下,状态会影响唯一性。例如,在论坛中,用户可能能够存储任意数量的签名或化身,但是一次只能有一个是活动的。在这些情况下,最好将当前数据与历史数据分开。例如,用于签名的表,以及在users表中用于引用活动签名的外键。 您应该能够更新任何行的状态,而无需关心其他行中的值。
避免级联状态,它们会破坏以前的子项状态,并且在许多情况下使状态字段的使用几乎毫无意义。而是在查询时与父表连接以过滤顶级状态。
最后,数据中的状态是时间数据建模方向的一个步骤。我建议阅读这个主题。
答案 1 :(得分:2)
您可能需要更详细地了解业务需求,然后我们才能回答所有这些变体。
一般情况下,正如您在评论中与@reaanb进行辩论一样,我不会担心联接的性能 - 现代硬件和现代数据库可以处理大量记录。
我还会专注于对关系模型中的问题进行建模,而不用担心"但是包含此检查并不是更复杂的问题。注重可懂度(设计与要求有多接近?)。我真的不喜欢使用视图捕获这些东西,因为当基础业务需求发生变化时,它们会使更改变得更难。
根据我的经验,您需要回答的最大问题是"业务要求是否关注随时间的变化"?
如果答案是" no",那么您可以使用任何有意义的状态标志。例如,在用户表上,您可能有一个状态列,其中包含"已注册/已验证/已取消激活/已删除"。代码相对简单 - 但您无法轻松回答问题,例如"此失效用户在哪个日期已经过验证?"或者"用户的状态是什么谁发布了这个评论"。
如果要求确实关心时间,我喜欢的模型是添加" valid_from"和" valid_until"到需要了解时间的行。 "当前" row有一个null
valid_until列。这使您可以在任何时间点了解数据的状态 - 但它确实使您的查询更加复杂,尤其是如果您跨多个表连接。
但是,这意味着 - 例如 - 您可以允许尚未验证的用户发布评论,但在他们验证帐户之前隐藏这些评论。
这也意味着您可以创建报告,显示过去每个日期的每个状态中有多少用户,重新激活的非活动用户数等等。
添加" valid_from"和" valid_until"到你的"帖子"表,你可以包括版本控制 - 也许你想表明评论是在帖子的旧版本上发布的?
最后,在一些复杂的应用程序中,我使用finite state machine来管理状态之间的有效转换。这可能对你的系统来说太过分了。
答案 2 :(得分:1)
数据建模中的状态模式
在这种情况下,我们可以使用一种模式,我将其命名为数据建模中的状态模式。
假设我们有一个具有多个状态的实体。例如user
实体,其中包含pre-registered
,normal user
,deleted user
,suspended user
等。
另一个例子,用户Post
(如Stackoverflow帖子)有许多状态,如normal
,deleted by user
,deleted by moderator
,{{ 1}},duplicated
等等。
假设我们要为closed
实体建模状态。在这种情况下,我们可以使用实体来保存所有状态类型(例如user
,pre-registered
,normal user
,deleted user
)。我们可以将其命名为suspended user
并将所有用户状态类型放入其中。
因此,另一个实体需要保留任何用户状态。让我把它命名为User_Status_Types
。它的F.Ks为User_Statuses
和User
。
使用此模式,我们可以保存用户的所有状态。
要改善模式,我们可以在User_Status_Types
中显示User_Statuses
的F.K,显示用户的最终状态。 (请注意,这不是Cycling Dependency Trap)
问题1 :如果实体有两种不同类型的状态?
在这种情况下应该使用这种模式的时间。
问题2 :如果子实体(从该实体拥有F.K的任何实体)的状态取决于实体的状态?
例如,如果我们想 NOT 显示已删除用户的评论。在这种情况下,我们有两个选择:
选项1 :我们可以在用户状态更改上编写触发器。如果用户状态更改为已删除,则我们的触发器也会将所有用户注释状态更改为已删除。
选项2 :在此选项中,评论状态不会更改。 但是,我们可以在评论的select命令中使用额外条件,并显示其父状态正常的注释。
问题3 :如果状态变化的顺序很重要,我们想在我们的数据模型中对它们进行建模。
在这种情况下,我们可以添加一个名为User
的新实体,它具有来自User_Status_Types_Sequence
的2个F.K作为源和目标。意味着源状态可以更改为目标状态。因此,我们可以从数据库中选择序列有效性,然后在数据库中执行它。