为什么要在数据库中存储一个枚举?


69

我已经看到了许多类似这样的问题,要求提供有关如何在数据库中存储枚举的建议。但是我不知道你为什么要这么做。假设我们有一个Person带有gender字段和一个Gender枚举的实体。然后,我的人员表具有一列性别。

除了强制正确性的明显原因外,我不明白为什么我会创建一个额外的表gender来映射应用程序中已有的内容。我真的不喜欢重复。



1
您还将在何处存储可能定期更改的数据?尽管您可能已经考虑了所有选项,但是如果有人出现并想要添加新选项该怎么办。您准备好调整该硬编码列表了吗?某人可能希望将性别设置为除男性或女性之外的某种形式,例如双性恋。
JB King

4
@JBKing ...只需看看Facebook的性别列表即可。


3
如果您的客户是“迷惑的Tumblrites”,那么您该死的创建一个数据库模式,该数据库模式至少可以在您打算继续营业时创建满足他们需求的东西。
史蒂芬·本纳普

Answers:


74

让我们再举一个不那么受概念和期望困扰的例子。我在这里有一个枚举,这是漏洞的优先级集合。

您在数据库中存储什么值?

所以,我可以存储'C''H''M',并'L'在数据库中。或'HIGH'等。这具有字符串类型的数据的问题。有一组已知的有效值,如果您没有将该组存储在数据库中,则可能很难使用。

为什么将数据存储在代码中?

您已经List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};在代码中实现了某种效果。这意味着您已经获得了该数据到正确格式的各种映射(您将所有大写字母插入数据库,但是将其显示为Critical)。您的代码现在也很难本地化。您已经将想法的数据库表示形式绑定到了存储在代码中的字符串。

在需要访问此列表的任何地方,您要么需要代码重复,要么需要带有一堆常量的类。两者都不是好选择。也不应该忘记还有其他应用程序可以使用此数据(可以用其他语言编写-Java Web应用程序使用了Crystal Reports报表系统和向其中提供数据的Perl批处理作业)。报告引擎将需要知道有效的数据列表(如果没有在'LOW'优先级中标记任何内容,并且您需要知道这是报告的有效优先级,会发生什么情况),并且批处理作业将具有关于有效数据的信息。值是。

假设,您可能会说“我们是一家单语言商店-所有内容都是用Java编写的”,并且只有一个包含此信息的.jar-但是现在,这意味着您的应用程序彼此紧密耦合,而.jar包含数据。每次发生更改时,您都需要与Web应用程序一起发布报告部分和批处理更新部分-并希望所有版本都能顺利进行发布。

当你的老板想要另一个优先事项时会怎样?

你老板今天来了。有一个新的优先级- CEO。现在,您必须去更改所有代码,然后重新编译和重新部署。

使用“表中的枚举”方法,您可以将枚举列表更新为具有新的优先级。获取列表的所有代码都将其从数据库中拉出。

数据很少单独存在

使用优先级,数据键可以进入其他表,这些表可能包含有关工作流的信息,或者谁可以设置此优先级或其他。

回到问题中提到的性别:性别与使用的代词有一个链接:he/his/himshe/hers/her...,您想要避免将其硬编码到代码本身中。而随后你的老板来的,你需要添加你有'OTHER'性别(保持简单),你需要这种性别涉及到they/their/them...和你的老板看到的Facebook已经和......嗯,是的。

通过将自己限制为字符串类型的数据位而不是枚举表,现在需要在其他一系列表中复制该字符串,以保持数据与其其他位之间的这种关系。

那其他数据存储又如何呢?

无论将其存储在何处,都存在相同的原理。

  • 您可能有一个文件,priorities.prop其中包含优先级列表。您可以从属性文件中读取此列表。
  • 您可能有一个文档存储数据库(如CouchDB),该数据库具有的条目enums(然后在JavaScript中编写验证函数):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • 您可能会有一个带有某种模式的XML文件:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

核心思想是相同的。数据存储本身就是需要存储和强制执行有效值列表的地方。通过将其放在此处,可以更轻松地推断代码和数据。您不必担心每次都要检查自己的内容(是大写还是小写?为什么chritical在此列中有类型?等等...),因为您知道从数据存储中得到的是确切地说,是您期望数据存储向您发送的数据-您可以查询数据存储以获取有效值列表。

外卖

有效值集是data而不是code。您确实需要争取DRY代码-但是重复的问题是您正在复制代码中的数据,而不是尊重其作为数据的位置并将其存储在数据库中。

它使针对数据存储编写多个应用程序变得更加容易,并且避免出现实例,在这些实例中您将需要部署与数据本身紧密耦合的所有内容-因为您尚未将代码与数据耦合。

这使测试应用程序更加容易,因为CEO添加优先级时不必重新测试整个应用程序-因为没有任何代码关心优先级的实际值。

能够相互独立地推理代码和数据,使得在进行维护时更容易查找和修复错误。


6
如果您可以在代码中添加枚举值而不必更改任何逻辑(并避免将其局部化显示),那么我首先怀疑是否需要额外的枚举值。尽管我已经大到可以评估使用简单的SQL查询轻松查询数据库备份以分析问题的能力,但是如今,使用ORM可以很好地完成工作,而不必完全查看基础数据库。我在这里不了解有关本地化(代词)的要点-这些东西当然不应该在数据库中,而是我会说的某种资源文件。
Voo

1
@Voo代词是与此枚举值相关的其他数据的示例。如果没有数据在表中,则字符串类型的值将需要在其中没有适当的FK约束。如果资源文件中有代词(如此类),则说明数据库和文件之间已经耦合(更新数据库并重新部署文件)。考虑一下可以通过管理界面即时修改的Redmine枚举,而无需进行重新部署。

1
...还要记住,数据库是一个多语言数据存储。如果您要求使用一种语言将验证作为ORM的一部分进行,则必须用您使用的任何其他语言来复制该验证(我最近与Java前端一起工作,该前端使用Python将数据推送到数据库中-Java ORM和Python系统必须在事物上达成共识-并且该共识(有效类型)最容易通过使数据库使用“枚举”表来强制实现。)

2
@Voo枚举的Redmine用法与bugzilla相同:“最重要的表包含系统的所有错误。它由各种错误属性组成,包括所有枚举值,例如严重性和优先级。” -它不是自由格式的文本字段,它是此已知且可枚举的集合之一的值。它不是一个编译时枚举,但仍然是枚举。另请参阅螳螂

1
因此,请确认-您的意思是人们永远不要使用枚举?不清楚。
niico

18

您认为在阅读查询时哪个更可能产生错误?

select * 
from Person 
where Gender = 1

要么

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

人们在SQL中创建枚举表是因为他们发现后者更易读-从而减少了编写和维护SQL的错误。

您可以直接在中将性别设置为字符串Person,但随后必须尝试执行大小写。由于字符串和整数之间的差异,您还可能增加表的存储命中率和查询时间,具体取决于您的DB在优化方面的能力。


5
但是接下来我们要加入表格。如果我的实体有两个枚举,那么我将联接三个表只是为了一个简单的查询。
user3748908

11
@ user3748908-是吗?联接是DB擅长的方面,而替代方案则更糟-至少在选择此路由的人看来。
Telastyn

8
@ user3748908:数据库不仅非常擅长执行联接,而且还非常擅长执行一致性。当您可以将一个表中的一列指向另一个识别行并说“此列的值必须是该表中的标识符之一”时,实施一致性确实非常好。
Blrfl 2015年

2
都是如此,但是在许多情况下,出于性能原因需要牺牲联接。不要误会我的意思,我全都致力于这种类型的设计和联接,但是如果您发现有时由于性能而不需要联接,那么我认为世界不会终结。
JonH

3
如果出于性能原因而不得不放弃加入参考表@JonH,则需要购买更大的服务器,或者停止尝试通过大量子查询推送谓词(我假设您知道自己在做什么)。引用表是在启动数据库后的几秒钟之内应该放在缓存中的东西。
2015年

10

我不敢相信人们还没有提到这一点。

外键

通过将枚举保留在数据库中,并在包含枚举值的表上添加外键,可以确保没有代码为该列输入错误的值。这有助于提高数据完整性,这是IMO您应该具有枚举表的最明显原因。


问题只有5行,明确指出“除了执行正确性的明显原因外”。所以没有人提到它,因为OP指出这很明显,并且他正在寻找其他理由-PS:我同意你的观点,这是一个足够好的理由。
user1007074

6

我在与你同意的营地中。如果在代码中保留Gender枚举,在数据库中保留tblGender,则维护时可能会遇到麻烦。您需要记录这两个实体应该具有相同的值,因此对一个实体所做的任何更改也必须对另一个实体进行任何更改。

然后,您需要将枚举值传递给存储过程,如下所示:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

但是,如果将这些值保存在数据库表中,请考虑如何做:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

当然,关系数据库在构建时考虑了联接,但是哪个查询更易于阅读?


这是另一个示例查询:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

与此相比:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

这是另一个示例查询:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

请注意,在此示例中,您必须将结果中的性别单元格从int转换为enum。但是,这些转换很容易。与此相比:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

当您将枚举定义保留在数据库之外时,所有这些查询都较小,并且更易于维护。


1
如果不是性别怎么办。我认为我们对性别领域过于关注。如果操作员说“所以我有一个带有优先级字段的实体Bug”,该怎么办-您的答案会改变吗?

4
@MichaelT可能的“优先级”值列表是代码的一部分,至少在一定程度上与数据的一部分相同。您是否看到了各种优先级的图形图标?您不希望它们从数据库中删除吗?诸如此类的主题可以被主题化和样式化,并且仍然代表存储在数据库中的相同值范围。无论如何,您不能只在数据库中对其进行更改。您需要同步演示文稿代码。
Eugene Ryabtsev

1

我将创建一个Genders表,因为它可以用于数据分析。我可以查询数据库中的所有男性或女性,以生成报告。查看数据的方式越多,发现趋势信息就越容易。显然,这是一个非常简单的枚举,但是对于复杂的枚举(例如世界上的国家或州),可以更轻松地生成专门的报告。


1

首先,您需要确定数据库是否只会被一个应用程序使用,或者是否有多个应用程序使用它。在某些情况下,数据库只不过是应用程序的文件格式(在这方面通常可以使用SQLite数据库)。在这种情况下,将枚举定义复制为表通常会很好,并且可能更有意义。

但是,一旦您要考虑让多个应用程序访问数据库的可能性,那么用于枚举的表就很有意义了(其他答案更详细地解释了为什么)。您或另一位开发人员想查看原始数据库数据时要考虑的另一件事。如果是这样,则可以将其视为另一种应用程序使用(仅在实验室指标为原始SQL的情况下使用)。

如果您在代码中定义了枚举(用于更简洁的代码和编译时检查),并且在数据库中定义了表,则建议添加单元测试以验证两者是否同步。


1

当您使用代码枚举来驱动代码中的业务逻辑时,出于多种原因,您仍应创建一个表来表示数据库中的数据。以下是确保您的数据库值与代码值保持同步的一些技巧:

  1. 不要将表上的ID字段设置为Identity列。包括ID和描述作为字段。

  2. 在表中做一些不同的事情,以帮助开发人员知道这些值是半静态的/绑定到代码枚举。在所有其他查找表中(通常是用户可以在其中添加值的表),我通常具有LastChangedDateTime和LastChangedBy,但是在枚举相关表中不包含它们有助于我记住它们只能由开发人员更改。记录下来。

  3. 创建验证代码,以检查枚举中的每个值是否在相应的表中,以及只有那些值在相应的表中。如果您具有在构建后运行的自动化应用程序“运行状况测试”,请在此处进行。如果没有,则只要在IDE中运行应用程序,就使代码在应用程序启动时自动运行。

  4. 创建生产交付的SQL脚本执行相同的操作,但是从DB内部进行。如果创建正确,它们也将有助于环境迁移。


0

还取决于谁访问数据。如果您只有一个应用程序可能会很好。如果添加数据仓库或报告系统。他们将需要知道该代码的含义,该代码的可人工修改版本是什么。

通常,类型表不会在代码中作为枚举重复。您可以将类型表加载到缓存的列表中。

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

通常,键入来去去。您需要添加新类型的日期。了解何时删除特定类型。仅在需要时显示它。如果客户希望“变性者”为性别怎么办,而其他客户不希望怎么办?所有这些信息最好存储在数据库中。

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.