2025年9月15日·阅读约1分钟

将 Google Sheet 转为关系型模式:分步建模计划

将 Google Sheet 转为关系型模式的分步说明:识别重复组、选择主键、映射关系,并防止以后数据混乱。

将 Google Sheet 转为关系型模式:分步建模计划

为什么电子表格在变成数据库时会混乱

电子表格很适合小清单。你可以随时改列、随处加注释、凭肉眼修正问题。但当文件成为共享的真实来源(source of truth)时,这种自由就会失效。

随着数据增长,同样的问题会反复出现。你会看到重复数据,因为没有一个地方统一存放客户或产品。你会看到冲突的值,因为两行会对同一项(比如电话号码)给出不同答案。筛选和报表变得令人沮丧,因为有些列里藏着列表("Tags", "Products", "Attendees")或混合了格式("$1,200", "1200", "1.2k")。

从 Google Sheet 转到关系型模式是为了安全性。数据库强制更清晰的结构,让你可以查询、校验并更新数据而不会制造新的矛盾。

一个有用的心智模型是:一行应只表示一件真实事物。如果一行同时表示一个交易、一个客户和一串产品,那么以后更新其中任何一项都会很痛苦。

一个快速测试:单行是否会为同一个字段需要两个值?

  • 一笔订单有多个产品
  • 一个项目有多个团队成员
  • 一个客户有多个地址

如果答案是肯定的,这不是“宽行”的问题,而是需要“单独表”来解决。一旦你把模型做清楚,就可以在其上构建表单和校验,而不是依赖脆弱的手动编辑。

先定义这个表格到底在记录什么

表格看起来井井有条,但可能对不同人意味着不同的东西。在把 Google Sheet 转成关系型模式之前,先就表格在追踪的内容达成一致。

从要支持的决策开始,而不是列。数据应该支持什么决策:周度收入报告、逾期工单清单、分配跟进的工作流,还是客户来电时的快速查阅?如果你无法说出一个决策,这个字段通常不属于数据库。

接着,把表头和注释中隐藏的名词挑出来。这些通常会成为表:customers、orders、products、invoices、tickets、agents、locations。如果一列混合了两个名词(比如 “Customer + Company”),说明你在一个地方存了多种东西。

提前就意义达成一致

细微的意义差异会在后期演变成大规模清理。把基本概念弄清楚:

  • 什么算作一笔“订单”(报价、已付购买,或两者)?
  • 什么是“客户”(个人、公司,还是两者)?
  • 一笔订单可以有多个产品吗?
  • 一个邮箱可以属于多个客户吗?
  • “状态”应表示当前状态还是历史记录?

举例:如果表里每行是“Order”,但“Products”单元格是逗号分隔的列表,需决定该行代表结账(checkout)、出货(shipment)还是发票(invoice)。不同选择会导致不同的模式。

把原始表冻结为只读副本,用它来验证新表是否仍能回答同样的问题。

清理表格以便结构可见

在把 Google Sheet 转成关系型模式之前,先让表看起来像数据而不是报表。数据库需要一致的行和列。装饰性布局会隐藏你需要建模的模式。

移除像合并单元格、多行表头和放在数据区内的小计这类布局手法。只保留一行表头,下面都是记录行。如果需要总计,把它放到单独的汇总页签,这样不会混入真实记录。

然后让每列的格式保持一致。数据库不能猜测 "1/2/24", "2024-02-01" 和 "Feb 1" 是同一天。同样的规则适用于电话号码、货币和姓名。选一个格式并在所有地方使用,即便它看起来有点严格。

一个简短的清理步骤通常很值得:

  • 确保每行代表一个事物(一个订单、一个客户、一个工单)。
  • 删除空白的间隔行和列。
  • 为 “N/A”、"-" 和空字符串 选定一个统一表示规则并替换。
  • 标注哪些列是计算出来的,哪些是人工输入的。

最后,标记任何包含多个值的单元格,比如某列中写着 “red, blue, green”。先不要改模式,只是标记这些列,以便你记得它们以后会变成独立的行。

识别重复组和隐藏列表的字段

电子表格数据建模中最大的警告信号就是重复。表格常常把“多个东西”压缩到一行里,通过重复列或在单元格内打包多个值来实现。短期有效,但一旦需要筛选、报表或一致更新就会崩溃。

通常意味着“应该是另一张表”的模式

扫描这些形状:

  • 编号列,如 Item 1, Item 2, Item 3Phone 1, Phone 2
  • 重复块,比如为 “Home” 和 “Work” 重复的地址字段。
  • 用逗号、换行或 “和” 组合的单元格(例如 “Mouse, Keyboard, Monitor”)。
  • 一列混合两个概念,比如 “Approved 2025-01-10” 或 “Alex (Manager)”。
  • 一行同时表示两个层级,比如一行既是 Order,又试图存放所有 Order Items。

举例:如果销售跟踪表用 Order ID, Customer, Product 1, Qty 1, Product 2, Qty 2,你会遇到问题。有些订单只有1件,有些有8件。表要么向横向无限扩展,要么开始丢失数据。在关系模型中,Orders 会是一个表,而 Order Items 会是另一个表,每个订单商品占一行。

对于“单元格内的列表”,把每个值当作独立记录处理。像 “Email, SMS” 的单元格通常意味着你需要一张单独的表(或连接表)来清楚地记录渠道。

混合列更隐蔽但同样危险。尽早拆分它们,让每个字段只存一个清晰的事实。

根据识别到的实体创建表

快速设计干净的表
使用数据设计器替换宽行、单元格内列表和重复数据。
Start Modeling

一旦能把表格里的事物命名清楚,就把每个事物变成一张表。你的电子表格就不再是一个大网格,而是一组更小、更有目的的清单。

如果一行混合了两个不同事物的详情,它很可能需要两张表。销售跟踪的一行可能包含客户信息(姓名、电话)、订单信息(日期、状态)和产品信息(SKU、价格)。客户不会因为订单改变而改变,产品也不依赖于单一订单。把它们拆开可以避免重复编辑和不匹配的值。

在最终确定之前,为每张表写一句话说明它的目的。如果你不能在一句话里描述一个表而不说 “还有”,那它通常太宽泛了。

一些实用规则:

  • 把描述同一事物并且生命周期相同的属性放在一起(客户姓名和客户邮箱)。
  • 任何可能出现多次的东西都移到自己的表(多个订单项、多个地址)。
  • 如果一个单元格包含列表(逗号分隔、重复列),那就是一个单独的表。
  • 如果两组字段因不同原因变化,就分开(订单状态 vs 客户联系信息)。

然后给列起清晰一致的名字。偏好简单名词,避免模糊标签如 “Info” 或 “Details”。

选择随时间稳定的键

将你的模式做成应用
可视化建模你的表和关系,然后把它们变成真实的应用。
Try AppMaster

尽早为每张表选主键。一个好的键很无聊:它从不改变、总是存在,并且能唯一标识一行。

自然键(现实世界的值)可以用,但前提是它们确实稳定。SKU 通常是好的自然键,因为它设计上是永久的。邮箱听起来稳定,但人会换邮箱、共享邮箱,或产生像 “john@” 和 “john.work@” 这样的重复。姓名、电话号码和地址会变而且不保证唯一。

一个安全的默认是自动生成的 ID(比如 customer_id, order_id)。把自然标识当作普通字段保留,只有在符合业务规则时才加唯一性约束。如果邮箱变了,customer_id 不变,相关订单仍然指向正确的客户。

简单的键规则:

  • 当真实标识可能会变、更可能缺失或被重用时,使用自动 ID。
  • 只有在你能控制且设计为永久时才用自然键(例如 SKU)。
  • 只有在重复真的会造成错误时才把字段标记为唯一。
  • 仅当 “未知” 是一个有效状态时才允许 NULL;否则要求有值。
  • 写清楚“唯一”是什么意思(在表内唯一、对公司唯一或按时间段唯一)。

举例:在 Contacts 表里,用 contact_id 作为主键。当你的规则是一人一邮箱时,再把 email 设为唯一。允许 phone 为空,因为并不是每个人都有或愿意分享。

在不猜测的前提下映射关系

大多数严重错误来自于对关系的猜测。用一个简单规则:如果一行“拥有”许多某物,那就是一对多。在“多”的一侧放外键。

举例:一个 Customer 可以有多笔 Orders。Orders 表应存 customer_id。如果你在 Customers 里保留以逗号分隔的订单号列表,很快就会出现重复和缺失。

多对多是表格中的常见陷阱。如果一笔 Order 可以包含许多 Product,且一个 Product 也可以出现在许多 Order 中,就需要一个连接表(常称为 line items)。它通常包含 order_idproduct_id,并额外存数量和购买时的价格等字段。

一对一关系很少见。它们适合当额外数据是可选的或出于隐私/性能分离时(例如 User 和 UserProfile)。如果你只是因为表格有两个标签页就拆表,那要小心。

历史记录需要自己的结构。如果某些值会随时间变化(状态、价格、地址),避免覆盖单个列。把变化存成历史表的行,这样你就能回答“那天真实是什么?”之类的问题。

规范化到能防止矛盾的程度

选择你的部署路径
部署到 AppMaster Cloud、AWS、Azure、Google Cloud,或导出源代码。
Deploy Now

一句话规则:一个事实只存一处。如果客户电话出现在五行,没人能保证会同时更新所有五处。

用通俗话说的规范化:

1NF、2NF、3NF 的实用解释

第一范式(1NF)意味着每个单元格只包含一个值。如果某列包含 “red, blue, green” 或 “SKU1|SKU2|SKU3”,那就是隐藏的列表。把它拆到相关表的多行中。

第二范式(2NF)多数在订单项中出现。如果你有 OrderItems,并且主键是 (OrderID, ProductID),那么诸如 CustomerName 的字段不属于这里——它依赖于订单,而不是商品。

第三范式(3NF)意思是非键字段不应依赖于其他非键字段。举例:如果你存 ZipCode 和 City,而 City 由 ZipCode 决定,你就有不匹配的风险。

快速自检:

  • 同一值会在多个地方被编辑吗?
  • 一次变化会迫使你更新很多行吗?
  • 你是否存了可以由 ID 推导出的标签?
  • 总计是否存放在产生它们的原始行旁?

何时允许反规范化

只有为了解决读取性能或报表需求时才反规范化,并且要安全地把报告表当作可重建的副本。把规范化表当作真实来源(source of truth)。

对于派生值(总计、余额、状态),除非有明确的重算规则,否则不要复制它们。实用做法是存原始事务、在查询时计算总计,只有在性能要求时才缓存总计并定义明确的更新/重建规则。

常见建模陷阱会带来后续清理工作

大多数“表格能用”的问题来自于语义而非工具。目标是让每行都以相同且清晰的方式表达一件事。

常见陷阱:

  • 使用姓名当 ID。"John Smith" 不是唯一标识,姓名会变化。用生成的 ID(或经验证的邮箱/电话),把显示名当作标签。
  • 把列表塞进一个单元格。看起来简单,但会破坏搜索、校验和报表。列表属于相关表。
  • 把当前状态与历史混在一起。单个 Status 列不能同时告诉你最新状态和变化历史。如果时间重要,把状态变化记录为带时间戳的事件。
  • 把一张表过载为多种含义。一个同时含客户、供应商和员工的 Contacts 表通常会产生只对部分行适用的字段。按角色拆表,或保留一个共享的 Person 表并添加角色专属表。
  • 忽视必填与可选字段。如果关键字段可以为空,你将得到无法正确关联的行。尽早决定哪些是必填并强制执行。

如果你的 Orders 表有 Item 1, Item 2, Item 3 这类列,你就是在看重复组。规划 Orders 表加 OrderItems 表。

在确定模式前的快速检查清单

用表单替代手动编辑
创建简单表单,让团队停止直接编辑表格并破坏数据。
Build Forms

在锁定模式之前,再做一遍清晰性检查。大多数后续数据库痛点来自那些早期看似无害的小捷径。

问问自己每张表是否只回答一个简单问题。“Customers” 应该就是指客户,而不是客户加他们的最近订单加通话记录。如果你不能用一句短语描述一张表,它就在混合多种含义。

最终检查:

  • 你能指出哪列(或哪组列)能唯一标识每行吗,即使名称会变?
  • 是否有单元格包含多个值(逗号分隔标签、多个邮箱、Item1/Item2 列)?如果有,拆为子表。
  • 每个关系是否以外键的形式有意存储?多对多是否有连接表?
  • 重要字段是否有规则(关键字段必填、对可能造成伤害的字段唯一)?
  • 你能否在唯一一个地方更新某个事实(客户地址、产品价格、员工角色)?

现实测试:想象有人把同一个客户稍微拼写不同地录入两次。如果你的模式让这件事很容易发生,考虑改用更稳健的键或唯一规则。

示例:把销售跟踪表转换为清晰的表结构

快速交付内部工具
把你的模式变成团队每天能用的网页界面。
Build Web App

想象一个销售跟踪表,每行是一笔交易,包含 Customer Name、Customer Email、Deal Amount、Stage、Close Date、Products(逗号分隔列表)和 Notes(有时在一个单元格里有多条备注)。

这一行隐藏了两个重复组:products(一个交易可以包含多个产品)和 notes(一个交易可以有多条备注)。这是转换常错的地方,因为单元格里的列表难以查询且易出矛盾。

一个符合实际业务行为的清晰“后”模型:

  • Customers (CustomerId, Name, Email)
  • Deals (DealId, CustomerId, Amount, Stage, CloseDate)
  • Products (ProductId, Name, SKU)
  • DealProducts (DealId, ProductId, Quantity, UnitPrice)
  • DealNotes (NoteId, DealId, NoteText, CreatedAt)

CustomerId、DealId 和 ProductId 是稳定的标识符。DealProducts 解决多对多关系:一笔交易可以包含多个产品,且一个产品可以出现在多笔交易中。DealNotes 把备注分开,避免出现 “Note 1, Note 2, Note 3” 这样的列。

在建模前,如果要做“按产品的收入”这类报表,原来可能需要拆字符串并希望名字一致。建模后,只需对 DealProducts、Deals 和 Products 做连接查询即可轻松得到结果。

下一步:把模式变成可用的应用

当纸面上的模式看起来正确后,把它放进真实数据库并用真实数据做测试。不要一次性导入全部数据。先导入一小批,修复出的问题,然后重复。

一个保持低风险的实用顺序:

  • 创建表和关系。
  • 导入 50 到 200 行,验证汇总并抽查记录。
  • 修正映射问题(字段错位、缺失 ID、重复),然后重新导入。
  • 稳定后再导入剩余数据。

尽早加入校验规则,以免返回到混乱的表格习惯。把必填字段设为真必填、限制允许的值(比如状态)、校验格式(日期和邮箱),并使用外键以防创建不存在客户的订单。

然后停止使用表格做更新。当人们使用简单的表单和清晰的工作流时,保护数据会容易得多。

如果你想把模式变成无需写代码的可用内部工具,AppMaster 可以帮助你:可视化建模表和关系,然后从同一模型生成可投入生产的后端、网页应用和原生移动应用。

常见问题

什么时候应该停止使用 Google Sheet 并切换到关系型模式?

当表格已经作为共享的真实来源(source of truth),并且你开始看到重复、冲突的值或报表变得痛苦时,就该开始迁移。如果你经常在处理逗号分隔的列表、Item 1/Item 2 列,或不断复制粘贴修复数据,关系型模式会很快节省你的时间。

如何判断某个东西是否需要单独的表?

如果单行里需要为同一个字段存放多个值,那就是重复组(repeating group)。常见例子有一笔订单有多个产品、一个客户有多个地址,或一个活动有多个参与者。这些都应该成为子表(或连接表),而不是额外的列或单元格内的列表。

在设计表之前我应该做哪些清理?

先冻存一份只读的原始表,然后移除合并单元格、多行表头和数据区内的小计行。让每列保持一致(同一种日期格式、货币表示和空值表示),这样在设计表结构前你才能看清真实结构。

我应该用邮箱/姓名作为主键,还是添加一个 ID?

默认使用自动生成的 ID 作为每张表的主键,因为它稳定且不会在用户更新邮箱、姓名或电话时改变。把真实世界的标识(例如 email 或 SKU)保留为普通字段,并且只有在业务上绝对不能重复时才加唯一性约束。

如何建模一对多和多对多关系?

按所有权来映射关系:如果一个客户可以有多笔订单,就把 customer_id 放在 Orders 表里。若是多对多(比如订单和产品),需要一个连接表(例如 OrderItems),包含 order_idproduct_id 以及数量和购买时的价格等字段。

在这个转换中,“适度规范化”到底是什么意思?

其实就是防止矛盾。把一个事实只存一处,避免同一信息出现在多处需要同时更新。你不必追求“完美规范化”,但要消除诸如同一客户电话号码散落在多行的重复情况。

逗号分隔的列表(标签、产品、参与者)应该怎么办?

把它拆成独立的行和表。单元格里的 “Email, SMS” 很难筛选和校验,也会破坏报表。为这些值建一张相关表(或连接表),把每个选择作为一条记录关联回父行。

如何处理会随时间变化的字段,比如状态或价格?

把“当前状态”和“历史”分开。如果只需要最新状态,可以保留一个当前状态字段;但当你需要按时间追溯时,应把状态变化作为带时间戳的事件或历史表记录,这样就能回答“上个月的状态是什么?”之类的问题。

迁移数据最安全的方式是什么?

先小批量导入(大约 50–200 行),核对汇总数字并抽查记录是否与冻结的原表一致。修正字段映射、缺失的 ID 和重复项,然后再导入下一批。只有在流程可重复且可靠后,再导入所有数据。

我能在不写代码的情况下把模式变成内部工具吗?

当你希望把模式变成带表单、校验和工作流的内部工具,而不想写大量代码时,无代码工具可以提供帮助。AppMaster 可以可视化建模表和关系,并从同一个模型生成可投入生产的后端、网页应用和原生移动应用。

容易上手
创造一些 惊人的东西

使用免费计划试用 AppMaster。
准备就绪后,您可以选择合适的订阅。

开始吧