从业务需求到数据模型:实体、关系与拆表

1435597771 ·

在本篇文章中,我们只解决一个问题:一段业务需求,到底应该变成那些表?

数据库设计中的很多问题,在建表初期就已经埋下了隐患。业务后期如果要对前期草率的设计做改动,成本极高——查询会越来越扭曲,数据不一致的问题也会频发。

正确的路径其实很清晰:先从业务中识别实体、属性和关系,再决定表结构、外键位置和是否需要中间表。

1.为什么不能拿到需求就直接建表

我们需要先达成一个共识,需求往往是模糊的,比如这句话:”一个订单可以包含多个商品,每个商品有数量和单价“, 如果我们不分析这个需求直接建表,那么很容易犯下三类错误:

  • 把可变数量的东西塞进固定字段

    • 错误做法:在订单表里直接加 product1_id、product2_id、product3_id

    • 问题:商品数量一旦超过设计时的上限就崩了,而且查询和统计极其痛苦。

  • 把本来独立生命周期的对象当作附属字段

    • 错误做法:把商品名称、价格直接写死在订单表,而不独立建表

    • 问题:商品信息修改,那么历史订单也会被错误的修改

  • 把多对多关系错误地用1:N或字段列表实现

    • 错误做法:在订单表用一个 product_ids 字段存逗号分隔的商品ID列表

    • 问题:无法正确维护数量、单价等关系属性,也无法利用数据库的外键约束。

直接建表的本质问题是:跳过了概念建模这一步。概念模型回答的就是:“在业务中有哪些实体,它们如何关联”。而表只是概念模型的实现,如果没有这一步,表结构会跟着业务的表面描述走,而不是根据业务本质走。

2.什么是实体、属性、关系

直接建表的本质问题是跳过了概念建模。那么概念建模是什么?

概念建模就是由实体、属性、关系这三大基本要素组合而成的。因此进入概念建模之前,必须要对这三个概念建立清晰、准确的理解。否则在后续的建模过程中很容易凭感觉画图或者直接翻译需求句子,容易浮于表面。下面是这三个基本要素的基本概念:

  • 实体(Entity):业务中可以独立识别、独立存在的事物。通常对应对应一张表。例如:用户、商品、订单

  • 属性(Attribute):描述实体或属性的特征,通常对应表中的一列。例如:用户的姓名、手机号;商品的价格、库存

  • 关系(Relationship): 实体之间的关联。 我们需要注意的是,关系本身也可以有属性。例如订单“包含”商品

如何判断一个概念到底是实体还是属性?
关键看它是否满足两个条件:

  1. 可独立识别(有自己的业务标识)

  2. 有独立生命周期或可被独立操作

简单口诀:“能不能单独拿出来管理?能不能单独对它增删改查?”
如果答案是“能”,它更可能是实体;如果只是某个实体的附属描述,那它就是属性。

3.怎么判断1:1,1:N,N:N

理解了实体、属性和关系之后,下一步就要判断实体之间的关系基数(1:1、1:N、N:N)。

那么如何判断关系呢?

判断关系的黄金问题就是:从一个实体的实例出发,另一边最多能关联多少个实例。然后反过来再问一次。下面是不同的关系说明:

  • 1:1关系 :两边都是最多一个,比如用户 与用户详细资料

  • 1:N(一对多)关系:一边最多一个,另一边可以多个。比如:用户1:N订单(一个用户有多个订单,但是订单只能属于一个用户)

  • N:N(多对多)关系:两边都可以有多个。比如:学生和课程(一个学生可以上多个课程,同时一个课程也会属于多个学生)

4. 1:N为什么外键放在N侧

在1:N关系中,外键必须放在“N”的那一侧。原因很直观:

  • 如果把外键放在“1”侧,一个字段无法存储多个值(除非用数组,但这会失去关系性数据库的约束)

  • 把外键放在“N”侧,每个“多”的记录只指向一个“1”,天然满足基数约束。

5.N:N为什么需要关系表

我们在说1:N关系的时候说过,外键必须要放在“N”侧,因为在1侧无法用一个字段存储多个值。

到了在N:N关系中,任何一边放外键都无法正确存储多个值。这时的标准做法就是引入关系表(也叫做关联表、中间表)

关系表的特点:

  • 至少包含两边的外键(通常做成联合主键或加唯一索引)

  • 本身可以没有业务意义,只是链接两个实体。

比如学生和课程的N:N关系,需要student_course表,这个表中包含student_id+course_id

6.中间表什么时候回变成业务实体

中间表是描述两个实体之间的关系,但是关系本身也有它的属性,比如学生和课程这个关系中,选课时间就是关系的属性。 这个属性叫做关系属性。

当一个中间表拥有他自己的属性的时候,或者开始承载独立的业务操作时,那么这个时候中间表就不再是存粹的连接器,而是升级为业务实体。

最典型的例子就是电商中的「订单明细」:

  • 订单和商品是 N:N 关系

  • 但中间表需要记录数量、单价、小计等

  • 因此 order_item 就成为了真正的业务实体,而不是单纯的中间表。

7. 完整示例

我们用这个业务需求来一步一步分析出需要有哪些表:

“用户可以下多个订单,每个订单可以包含多个商品,每个商品在订单中有数量和当时的成交单价。”

  1. 识别实体:用户(User)、订单(Order)、商品(Product)

  2. 判断关系:

    • 用户 1:N 订单

    • 订单 N:N 商品(因为一个订单多个商品,一个商品也可出现在多个订单)

  3. 因为“数量 + 成交单价”是关系属性,所以中间表升级为业务实体 OrderItem

  4. 最终表:

    • user

    • product

    • order(外键指向 user)

    • order_item(外键分别指向 order 和 product,并记录 quantity、price)

总结:从业务需求到表的决策路径

上面我们分别讲清了实体、属性、关系以及它们如何落地到表结构。现在把这些内容串成一条完整的决策路径:

  1. 先识别业务中的核心实体

  2. 区分哪些事真正的属性(固定、从属、无独立操作),哪些应该独立成表(数量可变、独立操作、独立声明周期)

  3. 画出实体之间的关系,区分为是什么关系

  4. 1:N外键放在N侧,N:N关系要引入关系表

  5. 当关系本身有属性或者业务意义时,把中间表升级为业务实体(OrderItem)

  6. 永远记住:查询习惯和SQL写法不决定关系类型,要从业务考虑。

当我们按照以上步骤思考,从一段业务出发,得到的就不再是"感觉有哪些表",而是一套有明确理由、可扩展、可维护的数据模型。无论是后续加字段、该关系还是优化查询,都有清晰的概念锚点。

以上就是"从业务需求到数据模型"的核心方法论:先想清楚世界有什么、它们如何关联、再决定表该怎么建。