这里有几个(不完整的)数据库表,它们存储有关酒店房间的信息。它们存储的信息是相同的,但是它们的设计是不同的:
-
单独列存储楼层信息:
| id | floor |----|------- | 1 | 1 | 2 | 1 | 3 | 2 | 4 | 2
-
以id存储楼层信息
| id |----- | 101 | 102 | 201 | 202
像表2那样在id中存储语义数据是否总是一个糟糕的主意,或者是否有更有表现力的id足够有价值来证明它?
如果您想使用自然键,那么就使用自然键。不要将自然键命名为id
。
如果使用合成键,则将其视为必须唯一但没有其他含义的任意值。
这不是关于数据的语义,而是关于原子性和你是否违反了1NF。你应该问自己的问题是:
从数据管理的角度来看,房间号是否应该被视为一个原子数据 ?换句话说,您是否总是从数据库中读取(并写入)整个房间号(无论您是否在客户端代码中将其视为一个整体)?
- 如果是,您可以安全地将其设置为单个属性(然后将其设置为键或键的一部分,如果这是需要的)。是否也有代理键是另一回事(如下所述)。
- 如果没有(例如:因为你需要查询楼层,或者使用楼层作为FK等),那么你必须将其拆分为单独的属性(例如楼层+每层房间),否则你将违反1NF。
注意:我不知道这是否是故意的,但是你的场景(1)没有包含足够的数据来重建房间号,所以它与场景(2)相比建模了一个不同的域。
顺便说一句,在键中存储语义数据本身并不是一个坏做法。如果某些属性或属性组合必须是唯一的,那么您必须为它们创建一个键,无论它们是否具有内在意义。您不能用"代理"键替换该键,您可以添加代理键(正如您可以想象的那样,它有其优点和缺点)。
我同意,这是个很糟糕的主意。id的思想是,它是行的单列唯一标识符,可以替代多列键。如果您将列称为"id",那么查看您的模式的任何人都希望看到主键。你也在失去语义价值,因为你知道那个东西是地板的表示。
如果您没有id列,并且只将列称为"floor",那么就可以了。你所关心的是,如果你需要找到一个特定的记录,你能做到吗?如果您的特定应用程序不需要查找floor以外的任何其他信息,那么继续使用"floor"作为列名,不要添加ID列。但是,如果您有一个复合键,如floor和hotel id,那么您可能希望再次添加id列作为"快捷"单列键,您可以将其用作其他表中的外键。
在表的主键中包含语义肯定是一个坏主意。
表中主键的作用是:
- 是唯一的,
- 作为记录的标识。
如果你把这样的语义放在主键中,那么你假设:
- 所有房间都有楼层,
- 不会有超过一家酒店,
- 房间号不能有字母。
这些假设在未来很容易瓦解。然后,您需要更改现有记录的主键-这将使它们不再是记录的标识。
这样的逻辑也可以应用于其他情况。例如,您可以使用条形码作为图书的唯一标识符,但将来您可能会有没有条形码的图书。
话虽这么说,我甚至会进一步改变你原来的表,有id
, room_number
和floor
。有些酒店因为迷信,没有1301
、1302
等房间号。但这并不意味着他们没有第13层。
最后回答你的问题-这可能并不总是一个糟糕的想法,但我想不出一个例子,这不是一个坏主意。
是的,这总是一个糟糕的主意:
- 如果后来"值"改变了,你也必须改变ID,这是一个更糟糕的主意
- 如果您发现必须在两个具有数字相邻ID的现有行之间插入值数据,则没有空间容纳新ID
- ID的目的是唯一地标识行。如果ID包含信息,你就重载了它的角色,这在编程中从来都不是一个好主意
- 如果你将信息隐藏在ID中,你必须将再次提取出来,这会在使用的任何地方使用代码,这意味着工作量和维护
- 你可能使用的任何框架将无法利用你的"优化";他们希望值在列中,而不是在ID 的片段中。你到底在节省什么?磁盘便宜,软件/硬件现在是如此之快,节省是微不足道的,永远不会超过负面的
我过去也这么做过,但一直很后悔这么做,因为你无法预测未来,业务和数据也以你无法预测的方式变化。把自己锁在任何特定的世界观里都是危险的。事实上,我见过的每一个这样做的案例,都导致了很多问题。
KISS
用最高有效数字表示楼层的数字来识别房间绝对没有错。我想我住过的每一家酒店都是这样。我敢肯定,您的表格中必须有这样一个房间号属性。
对自己和别人的怀疑都是因为你把房间号叫做"id"。房间号是一个标识符——大概它是唯一真正重要的房间标识符——但如果这是你的属性所指的,那么就叫它"roomnumber",而不是"id"。