创建新的数据库表而不使用枚举数据类型是否浪费资源?


38

假设我提供4种服务类型(它们不太可能经常更改):

  • 测试中
  • 设计
  • 程式设计
  • 其他

假设我有60-80个实际服务,每个服务都属于上述类别之一。例如,“服务”可以是“使用技术A的测试程序”,并且类型为“测试”。

我想将它们编码到数据库中。我想出了一些选择:

选项0:

使用VARCHAR直接直接编码的业务类型为字符串

选项1:

使用数据库enum。但是,枚举是邪恶的

选项2:

使用两个表:

service_line_item (id, service_type_id INT, description VARCHAR);
service_type (id, service_type VARCHAR);

我什至可以享受参照完整性:

ALTER service_line_item 
    ADD FOREIGN KEY (service_type_id) REFERENCES service_type (id);

听起来不错,是吗?

但是我仍然必须对事物进行编码并处理整数,即在填充表时。或者在填充或处理表时必须创建精心设计的程序或数据库结构。即,在直接处理数据库或在编程端创建新的面向对象的实体并确保我正确操作它们时,可以使用JOIN。

选项3:

不使用enum,不使用两个表,而只使用一个整数列

service_line_item (
    id,
    service_type INT,        -- use 0, 1, 2, 3 (for service types)
    description VARCHAR
);

这就像一个“伪枚举”,在事物的代码方面需要更多的开销,例如知道{2 == 'Programming'}并适当地处理它。

题:

目前,我已经在概念的指导下使用选项2实现了它

  1. 不要使用枚举(选项1)
  2. 避免将数据库用作电子表格(选项0)

但是我不禁觉得这对我而言在编程和认知开销方面是浪费的-我必须知道两个表,并且处理两个表,而不是一个。

我正在寻找一种“不浪费的方式” Option 3。IT更轻巧,并且需要基本相同的代码结构来进行操作(稍作修改,但复杂度和结构基本相同,但只有一个表)

我认为理想情况下,它并不总是浪费,并且每种选择都有很好的案例,但是关于何时应该使用选项2和何时使用选项3是否有很好的指南?

当只有两种类型(二进制)时

为了进一步解决这个问题,在同一地点,我有一个二进制选项“标准”或“例外”服务,该选项可以应用于服务订单项。我已经使用Option 3对其进行了编码。

我选择不创建新表只是为了保存值{“ Standard”,“ Exception”}。所以我的列只保存{0,1}并且我的列名叫做exception,并且我的代码正在执行翻译{0, 1} => {STANDARD, EXCEPTION}(我将其编码为编程语言中的常量)

到目前为止,也不喜欢这种方式.....(不喜欢选项2或选项3)。我确实发现选项2优于3,但是开销更大,而且无论我使用2和3中的哪个选项,我仍然无法逃避将数据编码为整数的事情。

ORM

要添加一些上下文,请在阅读答案后-我刚刚(最近)开始使用ORM(在我的案例中是Doctrine 2)。通过注释定义数据库模式后,我想填充数据库。由于我的整个数据集相对较小,因此我想尝试使用编程构造来查看其工作原理。

我先填充service_types,然后填充s,service_line_item因为实际电子表格中已有列表。因此,“标准/例外”和“测试”之类的东西都是电子表格上的字符串,在将它们存储在数据库中之前必须将它们编码为正确的类型。

我找到了这样的答案:在doctrine2中,您用什么代替ENUM?,建议不要使用DB的enum构造,而应使用INT字段并使用编程语言的“ const”构造对类型进行编码。

但是正如上面的SO问题所指出的,一旦定义了整数,我就可以避免直接使用整数并使用语言构造-常量...。

但是仍然..无论您如何转动它,如果我string以一种类型开头,即使使用ORM时,也必须首先将其转换为正确的类型。

因此,如果说$str = 'Testing';,我仍然需要在某个地方执行以下操作:

switch($str):
{ 
    case 'Testing':  $type = MyEntity::TESTING; break;
    case 'Other':    $type = MyEntity::OTHER; break;
}

好的事情是您没有处理整数/魔术数(而是处理编码的常量),但是坏的事情是,如果没有此转换步骤,您将无法自动魔术地将数据放入数据库或从数据库中取出。知识。

这就是我的部分意思,是说诸如“仍然必须对事物进行编码并处理整数”之类的事情。(现在,在Ocramius发表评论后,我将不必直接处理整数,而可以根据需要处理命名常量和从常量到常量的某种转换)。


9
无论您做什么,都不要做#3。维持它的精神病患者将不得不不断弄清楚那些神奇数字的含义。如果这样做的话,您最好希望他们不知道您的住所。blog.codinghorror.com/coding-for-violent-psychopaths
RubberDuck

7
我喜欢选项2。如果您不喜欢查询表的泛滥,请使用一个表并添加“查询类型”列。但是,是的,创建查找表是执行此操作的“标准”方法,因为它允许您执行一些有趣的事情,例如轻松地在UI中填充下拉列表。
罗伯特·哈维

不要在此处的帖子中使用“编辑”;我们不是论坛。每个Stack Exchange帖子已经包含任何人都可以查看的详细编辑历史记录
罗伯特·哈维

如果我不能使用EDIT,该怎么用?
丹尼斯

就像我已经做的那样,只需编辑帖子并使它看起来自然。查看编辑历史记录以查看更改。
罗伯特·哈维

Answers:


35

使用参考表的选项2是标准方法。它已经被数百万程序员使用,并且可以工作。这是一种模式,因此任何其他查看您的内容的人都会立即知道发生了什么。有一些可以在数据库上使用的库和工具,可以使您免于繁琐的工作,并且可以正确处理。使用它的好处无数。

浪费吗?是的,但只有一点点。任何一个像样的数据库都将始终保持这种频繁连接的小表的高速缓存,因此通常不会感觉到浪费。

您描述的所有其他选项都是临时性的,包括MySQL的enum,因为它不是SQL标准的一部分。(除此之外,令人讨厌的enum是MySQL的实现,而不是想法本身。我不介意有一天将其视为标准的一部分。)

使用纯整数的最终选择#3 特别容易破解。您会遇到最糟糕的情况:没有参照完整性,没有命名值,数据库中没有关于值代表什么的明确知识,只是到处乱扔了任意整数。因此,您最好退出代码中的常量,而开始使用硬编码值。circumference = radius * 6.28318530718;。那个怎么样?

我认为您应该重新检查为什么您发现参考表比较繁琐。据我所知,没有人发现它们繁重。可能是因为您没有使用正确的工具来完成这项工作吗?

关于必须“对事物进行编码并处理整数”或必须“创建精心设计的编程结构”或“在编程方面创建新的面向对象的实体”的句子告诉我,也许您正在尝试进行对象相关的操作动态映射(ORM)分散在整个应用程序代码中,或者在最佳情况下,您可能尝试使用自己的对象关系映射机制,而不是使用现有的ORM工具(例如Hibernate)来完成这项工作。所有这些事情对于Hibernate都是轻而易举的。学习它需要花费一些时间,但是一旦学习了它,您就可以真正专注于开发应用程序,而不必理会如何在数据库中表示内容的复杂机制。

最后,如果您想在直接使用数据库时使生活更轻松,那么至少可以做两件事,我现在可以想到:

  1. 创建将主表与它们引用的任何引用表连接起来的视图,以便每一行不仅包含引用ID,还包含相应的名称。

  2. 代替对引用表使用整数id,请使用带有4个字母缩写的CHAR(4)列。因此,您类别的ID将变为“ TEST”,“ DSGN”,“ PROG”,“ OTHR”。(当然,它们的描述将保留为适当的英语单词。)这会稍慢一些,但请相信我,没有人会注意到。

最后,当只有两种类型时,大多数人只使用布尔列。因此,“标准/例外”列将实现为布尔值,并将其称为“ IsException”。


3
顺便说一句Postgres也有枚举类型。它们很简单,没什么特别的,它允许您使用可读的字符串作为值,但是在后台使用了更有效的整数。

结果是重复数据但没有冗余(例如,不会导致更新/插入/删除异常)怎么办?例如,一个人的性别(不太可能引入新的数据类型,永远不需要更改性别的名称,等等)
Adam Thompson

这是因为最终您会发现自己需要一个“接受环境”,并且您需要更改的枚举不变。
Pieter B

3

选项2在编程端带有常量或枚举。
尽管它复制知识,违反了“真理的唯一来源”原则,但是您可以使用快速失败技术来处理它。当系统加载时,它将检查数据库中是否存在枚举或const值。如果不是,则系统应引发错误并拒绝加载。通常,现在修复此错误的费用要比以后发生更严重的错误的费用低。


0

没有什么可以阻止您使用[短]字符串作为键,因此您仍然可以在表中拥有名称的可读性,而不必求助于无意义的替代数字编码。您应该仍然有单独的表格来描述服务类型,只是在您的应用程序国际化的情况下!

您的用户可以用自己的语言看到您的四个类别,但是您的数据库表仍然包含可以读取的值-而且它们都不要求任何数据库结构或代码更改!

table service_type 
( id VARCHAR 
, name VARCHAR 
  primary key ( id ) 
);
table service_line_item 
( id 
, service_type VARCHAR 
, description VARCHAR
  foreign key ( service_type ) references service_type ( id )
);

select * from service_type ; 

+-------------+----------------+
| id          | name           |
+-------------+----------------+
| Testing     | Testen         |
| Design      | Design         | 
| Programming | Programmierung |
| Other       | Andere         |
+-------------+----------------+

或者,对于您的法国客户而言...

update services_types set name = 'Essai'         where id = 'Testing'; 
update services_types set name = 'Conception'    where id = 'Design'; 
update services_types set name = 'Programmation' where id = 'Programming'; 
update services_types set name = 'Autre'         where id = 'Other'; 
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.