2 分钟

从用户故事到数据库模式:AI 指导的方法

学习一种实用方法,将用户故事、实体和工作流转成清晰的数据库模式,并了解 AI 推理如何帮助检查缺失项与规则。

从用户故事到数据库模式:AI 指导的方法

你要构建的东西:与真实工作相匹配的模式

一个 数据库模式 是应用如何记住事物的计划。实操上,它包括:

  • 表(Tables):信息的“桶”(Customers、Orders、Tickets)
  • 字段(列):你为每个对象存储的细节(customer_name、order_date)
  • 关系:这些桶如何相连(一个 Order 属于一个 Customer;一个 Customer 可以有多个 Orders)

当模式与真实工作匹配时,它反映人们实际做的事——创建、审核、批准、排程、分配、取消——而不是白板上听起来整洁的概念。

为什么从用户故事开始?

用户故事和验收标准用通俗语言描述真实需求:谁在做什么,以及“完成”意味着什么。如果以这些为来源,模式不太可能漏掉关键细节(比如“我们必须记录谁批准了退款”或“一次预订可以被多次重新安排”)。

从故事出发还能让你对范围保持诚实。如果故事(或工作流)里没有它,就把它当作可选,而不是偷偷构建复杂模型“以防万一”。

AI 在这里能做和不能做的事

AI 可以帮助你更快地推进:

  • 抽出候选实体(故事中重要的“东西”)
  • 建议验收标准隐含的字段(时间戳、状态、引用)
  • 发现可能的关系和遗漏(“提到了审批但没有存储审批人”)

AI 不能可靠地做到:

  • 知道你没有写下的隐藏业务规则或边缘情况
  • 在没有权衡的情况下选择“正确”的细节层级(简单 vs 灵活)
  • 保证模式满足你的报表、安全或合规需求

把 AI 当作强力助理,而非最终决策者。

如果你想把这个助理变成执行力,像 Koder.ai 这样的 vibe-coding 平台可以帮助你更快地把模式决策变成一个可运行的 React + Go + PostgreSQL 应用——同时让你保持对模型、约束和迁移的控制。

设定预期:迭代式,而非一次到位

模式设计是一个循环:草案 → 针对故事测试 → 发现缺失数据 → 细化。目标不是一次性完美输出,而是能将每张表追溯到特定用户故事并自信地说:“是的,我们可以存储这个工作流所需的一切——并且能解释每张表存在的理由。”

输入:用户故事、验收标准和真实示例

在把需求转成表之前,先弄清你在建模什么。一个好的模式很少从空白页开始——它始于人们实际做的具体工作,以及你以后需要的证明(屏幕、输出和边缘情况)。

希望集中在一处的典型输入

用户故事是主线,但单靠它们不够。收集:

  • 用户故事 + 角色(谁在做什么、为什么)
  • 验收标准(“必须为真”的规则)
  • 表单/屏幕(用户输入、选择或看到的字段)
  • 报表/导出(哪些内容需要汇总、分组、过滤)
  • 真实示例(样例订单、发票、工单、日历——任何具有代表性的东西)

如果你使用 AI,这些输入能让模型不过度发明结构。AI 可以快速提出实体和字段,但需要真实工件来避免与产品不匹配的设计。

验收标准:隐藏的约束来源

验收标准通常包含最重要的数据库规则,即使它们没有显式提到数据。寻找类似的陈述:

  • “电子邮件必须唯一”(唯一性)
  • “状态可以是 Draft、Submitted、Approved”(允许值)
  • “只有经理可以批准”(权限,可能需要审计字段)
  • “不能删除有付款的发票”(参照完整性规则)

早期要修正的常见陷阱

模糊的故事(“作为用户,我可以管理项目”)常常隐藏多个实体和工作流。另一个常见缺口是漏掉边缘情况,如取消、重试、部分退款或重新分配。

建模前的快速故事质量清单

  • 行为者/角色是否明确。
  • 对象是否具体(不是“数据”或“东西”)。
  • 是否至少有一个真实示例。
  • 验收标准是否包括验证和边界。
  • 错误和“如果发生 X”情况是否被提及(或明确延后)。

第 1 步 — 从故事中提取实体(名词)

在考虑表或图表之前,读一遍用户故事并标出名词。在需求写作中,名词通常指向系统必须记住的“东西”——这些通常会成为你的实体

一个简单的心理模型:名词成为实体,而动词成为操作或工作流。例如故事说 “A manager assigns a technician to a job”,很有可能的实体是 manager, technician, 和 job——而“assigns”暗示你以后要建模的关系。

如何判断一个名词是否是真正的实体

并非每个名词都值得独立成表。当一个名词满足以下条件时,它就是实体的强候选:

  • 有自己的身份:你可以指向一个特定实例(Job #1042、Customer A)。
  • 随时间变化:它有生命周期(一个 job 从 scheduled → completed)。
  • 在多处使用:多个故事引用它,或多个工作流程会接触到它。

如果一个名词只出现一次,或只是描述别的东西(“红色按钮”、“星期五”),它可能不是实体。

属性 vs 独立实体(“地址”和“标签”的测试)

一个常见错误是把每个细节都做成表。用下面的经验法则:

  • 如果它是描述某物的单个值,通常是属性(例如 Customer.phone_number)。
  • 如果它是可重复、可共享或有结构的,通常是独立实体

两个经典示例:

  • Address(地址):如果你需要保存发货和账单地址、保留历史,或在客户/地点间重用地址,Address 可能是独立实体。如果你只需要一个邮寄地址且不会重用,地址可以作为属性。
  • Tag(标签):标签几乎总是做成独立实体,因为它们是可重复的并且是多对多关系(一个 Job 有多个 Tags;一个 Tag 适用于多个 Job)。

谨慎使用 AI 建议候选实体

AI 可以通过扫描故事来加速实体发现并返回按主题分组的候选名词(人员、工作项、文档、地点)。一个有用的提示是:“提取必须存储的数据的名词,并合并重复/同义词。”

把输出当作起点,不是最终答案。继续问:

  • “这些中哪些有生命周期或需要独立 ID?”
  • “哪些实际上是状态、类别或属性?”
  • “有没有同义词(例如 ‘client’ vs ‘customer’)?”

第 1 步的目标是得出一个简短、干净的实体列表,并能通过指向真实故事来为其辩护。

第 2 步 — 将细节变成字段(你必须存储的提醒)

一旦你命名了实体(如 Order, Customer, Ticket),下一步是捕捉以后会用到的细节。在数据库里,这些细节是字段(也叫属性)——系统不能忘记的提醒。

如何在不猜测的情况下选择字段

从用户故事开始,然后把验收标准当作必须存储的清单来读。

如果需求说 “Users can filter orders by delivery date”,那么 delivery_date 就不是可选的——它必须存在为字段(或能可靠地从其他存储的数据推导)。如果需求说 “Show who approved the request and when”,你可能需要 approved_byapproved_at

一个实用测试:"有人需要用来显示、搜索、排序、审计或计算这个值吗?" 如果需要,它很可能应该是字段。

干净字段的简单规则

  • 保持原子性:如果你会按名字搜索或排序,就把“名”和“姓”分开。避免把多个值塞进一个字段(例如 “red, blue”)。
  • 使用一致类型:日期用日期类型,货币用小数,布尔值用 true/false——不要混用格式如 “$10”, “10 USD”, “10”。
  • 避免重复文本:不要把客户地址复制到每一行订单项。把它放在恰当的位置并引用它。

受控词汇:状态、类型和分类

许多故事包含“status”、“type”或“priority”之类的词。把它们当作受控词汇——一组允许的有限值。

如果集合很小且稳定,简单的枚举(enum)字段可以胜任。若可能扩展、需要标签或需要权限(例如管理员管理的分类),使用单独的查找表(如 status_codes)并存储引用。

这是把故事转成可被信任字段的方式——可搜索、可报表、且不易输错。

第 3 步 — 用关系连接实体

列出实体(User、Order、Invoice、Comment 等)并起草它们的字段后,下一步是把它们连接起来。关系是故事暗含的“这些东西如何交互”的那一层。

三种关系形态(通俗)

一对一(1:1) 意味着“一个东西恰好有另一个东西的一个实例”。

  • 故事表述:“每个用户有一个个人资料。”
  • 建模思路:UserProfile(通常可以合并,除非有分离的理由)。

一对多(1:N) 意味着“一个东西可以有很多另一种东西”。这是最常见的。

  • 故事表述:“一个用户可以有很多订单。”
  • 建模思路:UserOrder(在 Order 上存 user_id)。

多对多(M:N) 意味着“多个东西可以互相关联”。需要额外的表。

  • 故事表述:“一个订单可以包含很多产品,且一个产品可以出现在很多订单中。”

多对多:连接表技巧

数据库不能把“产品 ID 列表”整齐地存在 Order 里而不造成后患(查询、更新、报表难办)。相反,要建一个连接表来表示关系本身。

示例:

  • Order
  • Product
  • OrderItem(连接表)

OrderItem 通常包含:

  • order_id
  • product_id
  • 故事中出现的额外细节,如 quantity, unit_price, discount

注意故事的细节(“数量”)往往属于关系本身,而不是任一实体。

必需 vs 可选(不用术语)

故事还会告诉你某个连接是必须的还是有时不存在

  • “一个订单必须属于一个用户” → 每个 Order 需要 user_id(不可为空)。
  • “一个用户可以有电话号码” → phone 可以为空。
  • “一个订单可以有一个运送地址(如果是实物)” → shipping_address_id 对数字产品可能为空。

快速检查:如果故事暗示在创建记录时必须有这个关联,就把它设为必需;如果故事里用到“可以”/“可能”等词,就把它设为可选。

把故事句子改写为关系句子

阅读故事时,把它改写为简单的配对句:

  • “用户可以留下许多评论” → User 1:N Comment
  • “一个评论属于一个用户” → Comment N:1 User

为故事中每个交互做这件事。到最后,你会得到一个在建任何 ER 图工具前就与工作流程相匹配的连通模型。

第 4 步 — 用工作流找出状态、事件和遗漏

快速生成模式草案
根据验收标准起草表、字段和关系,然后快速完善。

用户故事告诉你人们想要什么。工作流则展示工作如何一步步流转。把工作流转成数据是发现“我们忘记存储某项信息”的最快方法之一——在你动手建之前就能发现问题。

从简单工作流开始

把工作流写成动作和状态变化的序列。例如:

  • Create request → Draft
  • Submit request → Submitted
  • Manager reviews → Approved or Rejected
  • If approved, work is scheduled → In progress
  • Completed → Done

这些加粗词通常会成为一个 status 字段(或一个小的“状态”表),包含明确的允许值。

工作流会暴露缺失字段

在逐步梳理每一步时,问:“以后我们需要知道什么?”工作流通常会揭示以下字段:

  • 时间戳submitted_at, approved_at, completed_at
  • 所有权created_by, assigned_to, approved_by
  • 原因/上下文rejection_reason, approval_note
  • 排序:多步骤流程的 sequence

如果工作流包括等待、升级或交接,通常至少需要一个时间戳和一个“当前由谁持有”的字段。

工作流会暴露缺失的表

有些工作步骤不只是字段——它们是独立的数据结构:

  • 审计日志 / 历史:记录“谁何时更改了状态”
  • 审批:用于多审批人或条件性审批规则
  • 附件:用户在某步骤上传文件时
  • 评论:当讨论是流程的一部份

用 AI 做交叉检查找遗漏

同时给 AI 两类资料:(1)用户故事与验收标准;(2)工作流步骤。让它列出每一步需要的数据(状态、执行者、时间戳、输出),然后高亮当前字段/表无法支持的需求。

在像 Koder.ai 这样的工具里,这种“差距检查”尤其实用,因为你可以快速迭代:调整模式假设、重新生成脚手架,并快速继续,而不是陷入大量手工样板代码中。

键、唯一性与基础约束(通俗)

把用户故事转成表时,你不仅是在列字段——你还在决定数据如何在时间里保持可识别和一致。

主键:每行的稳定“身份证”

主键 唯一标识一条记录——把它想成该行的永久身份证。

为什么每行都需要一个:故事里暗示会有更新、引用和历史。如果故事说 “支持可以查看订单并发起退款”,你需要一个稳定的方式指向该订单——即便客户改了邮箱、地址被编辑或订单状态改变。

在实践中,这通常是一个内部的 id(数字或 UUID),且永不改变。

外键:表间的指针

外键 是一张表安全地指向另一张表的方式。如果 orders.customer_id 引用 customers.id,数据库可以强制每个订单都属于真实存在的客户。

这对应类似“作为用户,我可以看到我的发票”的故事。发票不是漂浮的;它挂在客户(常常也挂在订单或订阅)下面。

唯一性规则:把“必须唯一”变成约束

用户故事经常包含隐藏的唯一性要求:

  • “用户用邮箱注册” → 在 email 上强制唯一(或在多租户场景下按租户唯一)。
  • “财务按发票号查询” → 强制 invoice_number 唯一。

这些规则能防止日后出现令人困惑的重复数据。

索引(高层):让常用查询更快

索引能加速“按邮箱查找客户”或“按客户列出订单”等查询。先从与你最常用查询和唯一性规则一致的索引开始。

需要推迟的:为罕见报表或推测性过滤加重索引。把这些需求记在故事里,先验证模式,再基于真实使用和慢查询证据做优化。

保持数据一致:实用范式检查清单

部署你的模型
从模式决策直接到部署与托管,无需繁琐设置。

范式的唯一目标是避免冲突性重复。如果同一事实能被保存在两个地方,迟早会出现不一致(两个拼写、两种价格、两个“当前”地址)。规范化的模式把每个事实只存一次,然后引用它。

对任何草案模式都可以运行的快速清单

  1. 注意重复组

如果你看到像“Phone1、Phone2、Phone3”或“ItemA、ItemB、ItemC”这样的模式,那就是应该拆成单独表(例如 CustomerPhones, OrderItems)。重复组让搜索、验证和扩展都变难。

  1. 不要把同一名称/细节复制到多表

如果 CustomerName 出现在 Orders, Invoices, 和 Shipments,你就出了多个真相来源。把客户细节放在 Customers,其他地方只存 customer_id

  1. 避免“为同一件事设置多列”

billing_address, shipping_address, home_address 在概念上可以,但如果你实际在建模“多地址及其类型”,就用 Addresses 表并加 type 字段。

  1. 把查找表和自由文本分开

如果用户从已知集合中选择(状态、类别、角色),保持一致建模:要么受限的枚举,要么查找表。这样能避免 “Pending” vs “pending” vs “PENDING”。

  1. 检查每个非 ID 字段依赖的是正确的对象

一个有用的直觉检验:在一张表里,如果某列描述的不是该表的主要实体,它很可能属于别的表。示例:除非它表示“下单时的价格快照”,否则 Orders 不应存 product_price

何时可接受反规范化(作为后续选择)

有时你会故意保存重复:

  • 报表/性能:预聚合总计或汇总表。
  • 缓存:存储计算值以避免昂贵重算。
  • 审计/历史:保存“购买时的名称”以保留过去的真实情况。

关键是要有意为之:记录哪个字段是事实源,以及副本如何更新。

AI 能帮忙的地方——和需要人来决定的地方

AI 能标出可疑重复(重复列、相似字段名、不一致的“status”字段)并建议拆成表。人类则根据产品的实际使用场景在简洁性、灵活性和性能间做取舍。

存储 vs 计算:什么该放数据库里

一个有用的规则:存储那些你无法可靠重建的事实;其他的都计算。

存储 vs 计算(派生)数据

存储数据 是事实源:单行项目、时间戳、状态变更、谁做了什么。 计算数据 是从这些事实推导出的结果:总计、计数、像“is overdue”这样的标记,以及像“当前库存”的汇总。

如果两个值可以从相同的底层事实计算出来,优先存事实并计算其余部分。否则你会遇到矛盾。

为什么存派生值会导致不匹配

派生值会在其输入改变时变化。如果你同时保存输入和派生结果,就必须在每条工作流和边界情况下保持它们同步(编辑、退款、部分发货、回溯更改)。一次遗漏更新,数据库就会讲两个不同的故事。

示例:既存 order_total 又存 order_items。如果有人改了数量或应用折扣而总价没被完美更新,财务看到一个数字,购物车显示另一个。

用工作流决定什么必须被存储(历史和快照)

工作流会显示你何时需要历史真相,而不仅仅是“当前真相”。如果用户需要知道某个值“当时是什么”,就要存快照。

对于订单,你可能会存:

  • 明细行和价格(事实)
  • 在结账时捕获的 order_total(快照),因为税费、折扣和定价规则以后可能改变

对于库存,“库存水平”通常由变动(入库、销售、调整)计算而来。但如果你需要审计轨迹,就存变动记录并可选地定期存储快照以提高报表速度。

对于登录追踪,保存 last_login_at 作为事实(事件时间戳)是合理的。“过去 30 天是否活跃?”这类问题仍然作为计算得出。

实战示例:从 5 条用户故事到 ER 模型

我们用一个熟悉的支持工单应用为例。从五条用户故事出发,得到一个简单的 ER 模型(实体 + 字段 + 关系),然后把它与一个工作流做对照。

5 条用户故事 → 名词 → 实体

  1. 作为客户,我可以创建一个带有主题、描述和分类的支持工单。
  2. 作为客服,我可以把工单指派给自己或其他客服。
  3. 作为客服,我可以对工单添加内部备注和公开回复。
  4. 作为客户,我可以看到我的工单何时更新以及何时关闭。
  5. 作为经理,我可以追踪工单开放多久以及谁关闭了它们。

由这些名词我们得出核心实体:

  • User(客户、客服、经理)
  • Ticket(工单)
  • Message(公开回复 + 内部备注)
  • Category(分类)
  • TicketEvent(审计/历史)

字段与关系(简洁 ER 模型)

  • User: id, name, email, role
  • Category: id, name
  • Ticket: id, subject, description, status, created_at, updated_at, closed_at
    • 关系: Ticket.category_id → Category.id
    • 关系: Ticket.requester_id → User.id(客户)
    • 关系: Ticket.assignee_id → User.id(客服,nullable)
  • Message: id, ticket_id, author_id, body, is_internal, created_at
    • 关系: Message.ticket_id → Ticket.id
    • 关系: Message.author_id → User.id
  • TicketEvent: id, ticket_id, actor_id, type, from_status, to_status, created_at

工作流映射:创建 → 更新 → 关闭

  • 创建:插入 Ticket(status = “open”,created_at),插入 TicketEvent(type = “created”)。
  • 更新(指派、回复):插入 Message 或更新 Ticket.assignee_id,并插入 TicketEvent(type = “assigned”/“replied”,updated_at)。
  • 关闭:更新 Ticket.status = “closed”,设置 closed_at,并插入 TicketEvent(type = “closed”,actor_id = closer)。

“前后对比”:AI 抓到的缺失约束

之前(常见遗漏):Ticket 有 assignee_id,但我们忘了确保只有客服(agent)可以被指派。

之后:AI 报告该问题,你可以增加一条实际规则:assignee 必须是 role = “agent” 的 User(通过应用层验证或数据库约束/策略来实现,视你的技术栈而定)。这能防止“把工单指派给客户”这类会破坏报表的数据。

验证模式:把每个故事追溯回去

用 Vibe-code 构建你的下一个应用
通过简单对话创建 Web、服务端或移动应用,然后迭代数据模型。

只有当每条用户故事都能用你真实能存储并查询的数据回答时,模式才算“完成”。最简单的验证步骤是逐条拿起故事并问:

“我们能从数据库可靠地回答这个问题吗?如果不能,模型就有缺口。”

把每条故事改写成数据库问题

把每条用户故事改写为一个或多个测试问题——你期望报表、屏幕或 API 会提出的问题。例如:

  • 报表:"按客户显示最近 30 天的所有未完成订单及其总计。"
  • 权限:"哪些用户被允许为该店铺批准退款?"
  • 边缘情况:"一个订单可以没有运送地址吗?数字商品如何处理?"
  • 删除:"如果删除客户,订单、发票和备注会怎样?"

如果你无法把故事表达为清晰问题,说明故事本身不清楚。如果能表达但模式无法回答,则缺少字段、关系、状态/事件或约束。

用示例数据做快速健查

创建一个小数据集(关键表每张表 5–20 行),包含正常情况和棘手情况(重复、缺失、取消)。然后用这些数据“演练”故事。你会很快发现诸如“我们无法区分购买时使用的是哪个地址”或“我们没有地方存谁批准了变更”之类的问题。

让 AI 帮你找未处理的情况

让 AI 为每条故事生成验证问题(包括边缘情况和删除场景),并列出回答这些问题所需的数据。把这个列表与你的模式对照:任何不匹配都是一个具体的待办项,而不是一种模糊的“不对劲”感觉。

安全使用 AI 并保持模式可维护

AI 能加快建模,但也会带来泄露敏感信息或把糟糕假设硬编码进去的风险。把它当作非常迅速的助理:有用,但需要护栏。

可以与 AI 分享什么(以及应避免什么)

分享足够真实以便建模、但已脱敏以保证安全的输入:

  • 已脱敏的用户故事(把客户、产品、地点改名)
  • 验收标准 和边缘情况(“14 天内退款”、“每账号一个生效订阅”)
  • 示例字段与伪数据(例如 invoice_total: 129.50, status: "paid"
  • 现有 CSV 表头 / 现有表结构(结构通常是安全的;内容通常不是)

避免任何能识别个人或泄露机密运营的信息:

  • 真实姓名、邮箱、电话、地址
  • 真实订单历史、工单、内部备注
  • API 密钥、数据库凭证、含私人数据的截图

如果需要真实性,生成符合格式和范围的合成样本——绝不要复制生产行。

在模式旁记录假设

模式失败多数源于“每个人都默认了不同的理解”。在 ER 图旁(或同一仓库里)保留一个简短的决策日志:

  • 定义(“什么算作‘活跃’账户?”)
  • 约束(“用户可以属于多个组织”)
  • 权衡(“我们在每张发票上存货币代码以便审计”)

这会把 AI 输出变成团队知识,而不是一次性的产物。

为变更做计划:版本与迁移

模式会随新故事演进。用下列方式保证安全:

  • 为模式变更版本化(把迁移文件放在 Git)
  • 尽量写可回滚的迁移
  • 更新种子数据和示例查询,让变更可测试
  • 像审查普通代码一样审查 AI 生成的迁移

如果你用像 Koder.ai 这样的工具,利用快照与回滚等护栏来迭代模式变更,并在需要更深层次定制或传统审查时导出源码。

一个简单可复用的流程

  1. 脱敏用户故事 + 生成 5–10 个合成示例。
  2. 让 AI 建议实体、字段、关系与约束。
  3. 团队评审;在旁边记录假设。
  4. 实施迁移;运行小规模“故事追溯”测试(每条故事都能被模型满足)。
  5. 在故事变化时重复此流程;保持模式与决策记录同步。

常见问题

How do I extract database entities from user stories?

从故事开始,找出那些表示系统必须记住的名词(例如 Ticket, User, Category)。

当一个名词满足下列条件时,可以提升为实体:

  • 需要自己的 ID
  • 会随时间变化(有生命周期/状态)
  • 在多个故事中出现

保持一个可以通过指向具体故事句子来证明的简短实体清单。

When should something be a field vs. its own table?

使用“属性 vs 实体”的判别法:

  • 如果它是描述单条记录的单个值(例如 customer.phone_number),把它作为字段
  • 如果它是可重复的、可共享的、结构化的,或需要历史记录(例如 多个地址、标签、附件),把它做成独立表

一个快速提示:如果你可能需要“多个这样的项”,通常就需要另一张表。

How do acceptance criteria translate into fields and constraints?

把验收条件当作存储清单。如果某个需求需要过滤/排序/展示/审计某个内容,你必须存储它(或能可靠地从已有数据推导出来)。

示例:

  • “显示谁批准以及何时” → approved_by, approved_at
  • “按交付日期过滤” → delivery_date
  • “电子邮件必须唯一” → 在 email 上添加唯一约束/索引
How do I turn story text into table relationships (1:1, 1:N, M:N)?

把故事句子改写成关系句子:

  • “一个客户可以有很多订单” → 1:N(在 orders 上放 customer_id
  • “一个订单包含许多产品” → M:N(加一个连接表,如 order_items

如果关系本身包含数据(数量、价格、角色),这些数据应该放在连接表上。

What’s the right way to model many-to-many relationships?

用一个连接表来建模 M:N,并在里面存两张表的外键以及关系专属字段。

典型模式:

  • orders
  • products
  • order_items,包含 order_id, product_id, quantity, unit_price

避免把“ID 列表”存成单个列——查询、更新和完整性维护会变得棘手。

How do workflows help me find missing tables or fields?

逐步演练工作流并问:“后来我们要证明这一步发生过,需要什么信息?”

常见添加项:

  • 时间戳:submitted_at, closed_at
  • 执行者:created_by, assigned_to, closed_by
  • 原因/备注:rejection_reason

如果你需要“谁何时更改了什么”,加一个事件/审计表,而不是覆盖单一字段。

Which constraints should I add first (keys, uniqueness, indexes)?

先从以下开始:

  • 每张表一个稳定的主键(id
  • 为关系添加外键(orders.customer_id → customers.id
  • 从需求中提取唯一性规则(邮箱、发票号)

然后为常用查找添加索引(如 email, customer_id, status + created_at)。把推测性的索引留到看见真实查询模式后再做。

How do I know if my schema is normalized enough without overdoing it?

运行一个快速的一致性检查:

  • 如果看到重复组(如 Phone1/Phone2),拆成子表。
  • 如果同一事实出现在多张表里,选定一个事实源并引用它。
  • 如果某列描述的不是该表的主要实体,把它移到合适的表。

只有在有明确理由(性能、报表、审计快照)时再去反规范化,并记录哪个字段是权威来源。

What should be stored vs. calculated in the database?

把那些无法可靠重建的事实存下来;其他的都计算得出。

适合存储的:

  • 事件和时间戳
  • 明细行和历史价格
  • 审计用的“谁做了什么”

适合计算的:

  • 从明细计算出的总计
  • 类似“是否逾期”的标记(从日期计算)

如果决定存派生值(比如 order_total),要明确如何保持同步,并测试退款/编辑/部分发货等边界情况。

How can I use AI safely to speed up schema design without making bad assumptions?

把 AI 当作起草和交叉检查的工具,然后根据你的工件验证结果。

实用提示:

  • “从这些故事中提取候选实体和同义词。”
  • “列出验收条件暗示的字段(时间戳、执行者、状态)。”
  • “给定这个工作流,每一步需要哪些数据?”

防护措施:

  • 清理输入(不要包含真实 PII、凭证或生产行)
  • 在 schema 旁边保留决策记录
  • 把 AI 生成的迁移当作普通代码来审查并在 Git 中版本化

Related posts