我已经看到了许多类似这样的问题,要求提供有关如何在数据库中存储枚举的建议。但是我不知道你为什么要这么做。假设我们有一个Person
带有gender
字段和一个Gender
枚举的实体。然后,我的人员表具有一列性别。
除了强制正确性的明显原因外,我不明白为什么我会创建一个额外的表gender
来映射应用程序中已有的内容。我真的不喜欢重复。
我已经看到了许多类似这样的问题,要求提供有关如何在数据库中存储枚举的建议。但是我不知道你为什么要这么做。假设我们有一个Person
带有gender
字段和一个Gender
枚举的实体。然后,我的人员表具有一列性别。
除了强制正确性的明显原因外,我不明白为什么我会创建一个额外的表gender
来映射应用程序中已有的内容。我真的不喜欢重复。
Answers:
让我们再举一个不那么受概念和期望困扰的例子。我在这里有一个枚举,这是漏洞的优先级集合。
所以,我可以存储'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/him
和she/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
添加优先级时不必重新测试整个应用程序-因为没有任何代码关心优先级的实际值。
能够相互独立地推理代码和数据,使得在进行维护时更容易查找和修复错误。
您认为在阅读查询时哪个更可能产生错误?
select *
from Person
where Gender = 1
要么
select *
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female"
人们在SQL中创建枚举表是因为他们发现后者更易读-从而减少了编写和维护SQL的错误。
您可以直接在中将性别设置为字符串Person
,但随后必须尝试执行大小写。由于字符串和整数之间的差异,您还可能增加表的存储命中率和查询时间,具体取决于您的DB在优化方面的能力。
我不敢相信人们还没有提到这一点。
通过将枚举保留在数据库中,并在包含枚举值的表上添加外键,可以确保没有代码为该列输入错误的值。这有助于提高数据完整性,这是IMO您应该具有枚举表的最明显原因。
我在与你同意的营地中。如果在代码中保留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
当您将枚举定义保留在数据库之外时,所有这些查询都较小,并且更易于维护。
我将创建一个Genders表,因为它可以用于数据分析。我可以查询数据库中的所有男性或女性,以生成报告。查看数据的方式越多,发现趋势信息就越容易。显然,这是一个非常简单的枚举,但是对于复杂的枚举(例如世界上的国家或州),可以更轻松地生成专门的报告。
首先,您需要确定数据库是否只会被一个应用程序使用,或者是否有多个应用程序使用它。在某些情况下,数据库只不过是应用程序的文件格式(在这方面通常可以使用SQLite数据库)。在这种情况下,将枚举定义复制为表通常会很好,并且可能更有意义。
但是,一旦您要考虑让多个应用程序访问数据库的可能性,那么用于枚举的表就很有意义了(其他答案更详细地解释了为什么)。您或另一位开发人员想查看原始数据库数据时要考虑的另一件事。如果是这样,则可以将其视为另一种应用程序使用(仅在实验室指标为原始SQL的情况下使用)。
如果您在代码中定义了枚举(用于更简洁的代码和编译时检查),并且在数据库中定义了表,则建议添加单元测试以验证两者是否同步。
当您使用代码枚举来驱动代码中的业务逻辑时,出于多种原因,您仍应创建一个表来表示数据库中的数据。以下是确保您的数据库值与代码值保持同步的一些技巧:
不要将表上的ID字段设置为Identity列。包括ID和描述作为字段。
在表中做一些不同的事情,以帮助开发人员知道这些值是半静态的/绑定到代码枚举。在所有其他查找表中(通常是用户可以在其中添加值的表),我通常具有LastChangedDateTime和LastChangedBy,但是在枚举相关表中不包含它们有助于我记住它们只能由开发人员更改。记录下来。
创建验证代码,以检查枚举中的每个值是否在相应的表中,以及只有那些值在相应的表中。如果您具有在构建后运行的自动化应用程序“运行状况测试”,请在此处进行。如果没有,则只要在IDE中运行应用程序,就使代码在应用程序启动时自动运行。
创建生产交付的SQL脚本执行相同的操作,但是从DB内部进行。如果创建正确,它们也将有助于环境迁移。
还取决于谁访问数据。如果您只有一个应用程序可能会很好。如果添加数据仓库或报告系统。他们将需要知道该代码的含义,该代码的可人工修改版本是什么。
通常,类型表不会在代码中作为枚举重复。您可以将类型表加载到缓存的列表中。
Class GenderList
Public Shared Property UnfilteredList
Public Shared Property Male = GetItem("M")
Public Shared Property Female = GetItem("F")
End Class
通常,键入来去去。您需要添加新类型的日期。了解何时删除特定类型。仅在需要时显示它。如果客户希望“变性者”为性别怎么办,而其他客户不希望怎么办?所有这些信息最好存储在数据库中。