在数据库表中具有“记录状态”列是一种不好的做法吗?


12

我首先要澄清一下,“状态”列并不是要反映表中记录(行)所代表的真实项目的状态。相反,它旨在显示记录本身的状态。

它可以像活动/不活动一样简单,也可以像批准/删除/锁定/未决/拒绝等复杂。状态可以存储在布尔/短整数列或单字符列中,并带有true/ 1=活动或A=已批准。

基本思想是在应用程序中具有回收站/类似垃圾桶的恢复支持(并在数据库中进行模拟)。如果有一个前端GUI或其他界面可以让用户“删除”记录,则它实际上不会删除表中的记录,而只是将记录状态更改为“不活动”或“已删除”。当接口获取记录时,它总是获取仅与状态为“活动”或“已批准”的条件匹配的记录。

如果用户犯了一个错误并且需要恢复“删除的”记录(从用户的角度来看),DBA可以轻松地将记录重新打回“活动”或“已批准”状态,这比搜索备份并希望找到原始记录要好。那里。或者界面本身可以让用户在单独的视图中查看已删除的记录,并根据需要还原它们,甚至永久删除它们(删除实际记录)。

我的问题:

  • 这是好习惯还是坏习惯?
  • 它会影响数据的规范化吗?
  • 潜在的陷阱是什么?
  • 有没有其他方法可以达到相同的目标?(看注释)
  • 您如何让数据库仅对特定状态强制对数据执行唯一约束(但对其他状态允许任意数量的重复项)?
  • 为什么数据库不提供原生的类似“回收站”的功能或表跟踪/恢复功能,所以我们可以让接口不用担心而删除实际记录?

注意:我读过有关维护单独的历史记录表的信息,但是在存储和必须生成触发器并使触发器与跟踪表的架构保持最新方面而言,这似乎更糟。


唯一约束(您已经命名)的问题正是为什么历史记录表通常更受欢迎的原因-您可以将唯一键约束保留在原始表上,而不必在历史记录表上添加它们。为它们使用特定的(取决于数据库的)存储选项,因此它们在存储方面通常更好,而不是更差。当您有很多这样的表时,触发器和历史记录表不应手写,而应生成,这将解决如何使它们保持“最新”的问题。
布朗

Answers:


5

我知道这是“软删除”;只是将记录标记为“已删除”,即使实际上并非如此。

这是好习惯还是坏习惯?

这取决于。
如果您的用户[急需]这件事,那可能是一件好事。但是,在绝大多数情况下,我会说这增加了很多开销,却收效甚微。

它会影响数据的规范化吗?

否,但这影响您对该数据的索引编制。
确保在索引中包括“已删除”列,以便在查询中尽早排除这些行。

潜在的陷阱是什么?

您的数据变得有点复杂。数据附近任何地方的所有内容都需要“了解”这些额外的“不是真的”记录。或者,您必须在排除这些行的那些表上创建视图,并在例如“选择的报告工具”中使用这些视图。

您的数据库大小可能会增加。如果您并没有真正删除这些行,那么它们仍然存在,会占用空间。这可能是(也可能不是)问题,尤其是因为您已将它们包括在索引中,因此它们消耗的空间成倍增加。

有没有其他方法可以达到相同的目标?(看注释)

不是,不是

您如何让数据库仅对特定状态强制对数据执行唯一约束(但对其他状态允许任意数量的重复项)?

不容易。声明性引用完整性(外键子句)是实现此目的的最干净的方法,对于诸如报表工具之类的事情而言,它很容易采用这些规则来确定表之间的关系。这样的规则适用于所有记录,无论“状态”如何(都无法解决)。

另一种选择是使用触发器,即过程代码片段,这些片段在表之间建立引用完整性,并执行您需要的所有聪明的,有条件的工作。这对您的特定情况很好,但是Declarative RI的大多数优点都没有了-表格之间没有[外部]可检测的关系;这就是触发器中的所有“隐藏”。

为什么数据库不提供原生的类似“回收站”的功能或表跟踪/恢复功能,所以我们可以让接口不用担心而删除实际记录?

他们为什么呢?

毕竟,这些是数据库,而不是文件系统或电子表格。

他们所做的,他们可以做的非常非常好。

他们不做的事情可能没有太多需求。


好的答案,但是还有其他选择,例如,将行移到可以从中恢复它们的备份表中。备份表可以具有最少的索引。这样可以最大程度地减少您使用现有方法所注意到的问题(较大的索引,表用户的潜在困惑等),但显然增加了您要维护另一个表的事实(这意味着条目已转到外键引用中)。还有很多其他选项-但实际上想到的是所有自定义实现,而不是每个SQL数据库在这种情况下都提供的常规选项。
Frank Hopkins

9

这是一种实践。好的还是坏的在很大程度上取决于您的应用程序,以及您真正需要/想要“取消删除”的频率。我对在系统中的每个表中放置此类列的计划非常怀疑-您似乎不太可能真正在系统中的每个表上实施取消删除操作。它需要实现-在大多数情况下,您并没有从单个表中删除单个行,而是必须遍历子表以删除行并更新相关表。

对于大多数其他问题,它高度依赖于实现。例如,Oracle提供了不同的方法来跟踪对表的所有更改-闪回数据存档(FDA也称为Total Recall)是用于维护行的每个版本的完整历史记录以及用于实施的数据库内归档的最新方法软删除模式。其他数据库可以提供其他方式来实现该模式。取决于数据库以及如何实现软删除,这会对性能,是否可以强制实施约束以及如何强制执行约束等产生各种影响。如果我们在谈论Oracle,则可以对基于函数的索引进行很多操作,例如,在SQL Server中,通常可以出于类似目的使用筛选索引。


Oracle闪回正是我想要的理想解决方案。太糟糕了,它是Oracle专有的。
ADTC'3

4

在MRP / ERP系统中,通常使用“标记为删除”字段。

例如,某人可能想将不再出售的零件或库存记录标记为无效,但仍有与之关联的未完成订单。在记录上进行真正的删除可能会影响尚未发货的订单,尚未过账的分类帐条目,直到月底之前都不会建立的历史记录表等。许多系统将不允许删除记录,除非它通过一系列对其他表的验证。如果您是通过关系来级联删除,那么真正的删除可能更具破坏性。

相反,通过将其标记为删除,可以在记录上放置一个明确的意图标记,并且以后计划的任务可以验证记录,以验证所有相关表都不再引用该记录,从而可以删除该记录。

可以在客户表和其他“长期”表上对此功能进行类似的处理。尽管标志的名称可能变成“已发货”或“已取消”之类的东西,但在诸如订单之类的易失性表上,这甚至是有意义的。它具有相同的功能:不要立即删除它,而应将其用作清除程序的标志,以便将来尝试验证记录的删除。


3

作为一种替代解决方案,事件源的使用可以实现相似的目标,而不会使表结构复杂化,尽管它确实使修改数据的代码更加复杂,因为您必须将修改写入事件中,并可以保留到事件历史记录中。然后,您可以像在任何给定时间一样重新创建数据库,这可能是非常有用的功能。

(我不相信这就是您所说的“历史表”的意思,我认为您的意思是简单地将修改或删除的记录复制到另一个表中,然后再进行更改)


有趣的概念。我将研究如何实现这一点。
ADTC'3

1

我在以下用例中经常看到并使用此模式:

  • 您只希望显示今天生效的值的元数据。例如,从启用= 1的下拉列表中的汽车制造商列表中进行选择,ID,VALUE,ENABLED的表值为1,“ Ford”,1和2,“ Edsel”,0、3,“ Toyota” ,1仅给出福特和丰田的选择
  • 对于一个案例管理系统,其中范例是一个案例一次只能处于一种状态。在这种情况下,切换列称为CURRENT,其值0或1由检查约束强制执行。当情况从一种状态转移到另一种状态时,应用程序会将旧状态的CURRENT标志更新为0,将新状态更新为1

问题是,如果有多个应用程序或Web服务正在向表写入数据,则必须强制数据完整性。您如何确保一种情况下只有一种当前状态?正如贾斯汀·凯夫(Justin Cave)指出的那样,可以在Oracle中通过创建基于函数的虚拟索引来完成此工作,但这对于本来看起来很简单的概念来说是额外的开销。


1

如果您打算使用数据进行报告(任何足够大的应用程序都需要报告),则这是一个好习惯。

为了加速您的应用程序,您实际上不应该让报告工具在数据库上运行。因此,您需要复制/同步到另一个数据库。

recordStatus仅使用两种状态ACTIVECANCELLEDlastUpdatedOn时间戳结合使用。我使用recordStatus而不是status通常具有商业意义的东西。

当我将报表数据库与应用程序同步时,我会进行过滤,lastUpdatedOn以了解要在报表端替换的数据库。

在报告方面,由于通常不会进行报告,因此我将没有recordStatusor lastUpdatedOn字段。这样,当我看到CANCELLED状态时,将以仅具有活动记录的方式从报告方删除该记录。

它可以扩展到其他类型的存储,例如需要几乎完全同步的存档或备份。但是,报告是更常见的目的。

请注意,您的例子ApprovedNewPending是不要把作为公共领域作为具有商业意义,只应该去的地方是有道理的商业智慧是个好主意。

至于锁定,请使用versionNo它为您的记录提供乐观的锁定。

替代的另一种选择recordStatusrecordActive将它存储为a boolean,它占用更少的空间和更少的索引,但是我担心您可能无法预见的未来需求。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.