HU1237 是一个完整的业务对象,不能拆分为"HU"和"1237"两个字段存入数据库。
如果你第一次听到这句话,可能会觉得奇怪——HU 明明是航空公司代码,1237 是航班序号,拆开不是很"规范化"吗?这不正是数据库范式设计所鼓励的吗?
恰恰相反。这种"理所当然"的拆分,是业务对象与数据之间最常见的断裂点。本文就以航班号为例,阐述什么是业务对象、为什么它的完整性不可侵犯,以及从业务对象到数据映射的三条核心原则。
什么是业务对象?
业务对象(Business Object),是业务人员在日常工作中直接使用、直接理解、直接决策的语义单元。它不是技术概念,不是数据库表,不是字段——它是"业务语言中的名词"。
| 领域 | 业务对象示例 |
| 航空 | 航班号(HU1237)、PNR 订座记录、航段(PEK→SHA) |
| 零售 | SKU(库存保有单位)、订单、会员卡号 |
| 金融 | 合约编号、交易流水号、产品代码 |
| 制造 | 工单号、物料编码、BOM 版本号 |
| 医疗 | 病历号、处方编号、ICD 诊断编码 |
一个业务对象有三个基本特征:
- 语义完整性:拆开就失去业务含义。把"HU1237"拆成"HU"和"1237",航司代码仍然有意义,但航班号这个业务对象就消失了。
- 业务可操作性:业务人员用它来完成工作。"帮我查一下 HU1237 今天的准点率"——这个句子里的主语就是航班号。
- 跨系统一致性:无论在订座系统、离港系统、运控系统还是财务系统,HU1237 指的都是同一件事。
航班号的解剖:为什么不能拆?
3.1 看起来有两个"部分"
以中国民航的航班号为例:
HU 1 2 3 7
│ │
│ └── 数字部分(1~4位):航班序号
└────── 字母部分(2位):航空公司 IATA 代码
- HU = 海南航空(IATA 两字码)
- 1237 = 该航司内部航班序号
从编码规则上看,它们确实是两个独立的信息源:航司代码由 IATA 分配,航班序号由航司自行编排。但从业务语义上看,它们必须同时存在才构成一个航班号。
3.2 拆分的灾难
假设你设计了这样一张表:
-- 错误示范:拆分航班号
CREATE TABLE flights (
airline_code VARCHAR(2), -- HU, CA, MU...
flight_number VARCHAR(4), -- 1237, 7071, 5101...
...
);
这看似"规范化",实则埋下了三颗雷:
雷一:1100 ≠ 1100
| 航班号 | 含义 |
| HU1100 | 海航 1100 号航班 |
| CA1100 | 国航 1100 号航班 |
| MU1100 | 东航 1100 号航班 |
把 flight_number = 1100 拿出来做聚合、对比、排名——没有任何业务意义。不同航司的"1100"是完全不同的东西,就像不同城市的"中山路"一样,不可比较。
雷二:业务查询被迫拼接
-- 每次查询都要重新"造"航班号
SELECT airline_code || flight_number AS flight_no
FROM flights
WHERE ...;
原本是原子的业务对象,现在变成了每次使用都要缝合的破碎零件。一旦某个系统忘记拼接,就会出现"CA" + NULL = NULL 的数据丢失。
雷三:跨系统对齐成本激增
订座系统返回 CA7071,你的数据库存的是 airline_code='CA', flight_number='7071'。导入时要拆,导出时要合,API 对接时要转。每多一个环节,就多一个出错点。而所有这些环节,都在做一件毫无业务增值的事——把航班号拆开再拼回去。
3.3 正确的设计
-- 正确示范:航班号是一个字段
CREATE TABLE flights (
flight_no VARCHAR(6) NOT NULL, -- HU1237, CA7071, MU5101
airline_code VARCHAR(2) GENERATED ALWAYS AS (LEFT(flight_no, 2)) STORED,
flight_number VARCHAR(4) GENERATED ALWAYS AS (RIGHT(flight_no, 4)) STORED,
...
);
航班号作为主存储字段保持完整;航司代码和航班序号作为派生字段(计算列)当且仅当确实需要单独使用时才生成。这样既保证了业务对象的完整性,又不丧失对子信息的访问能力。
核心原则:存储完整,派生子集。不要反过来。
业务对象到数据映射的三条原则
原则一:不要拆散原子业务对象
如果一个东西在业务对话中作为一个整体被使用,它就应该作为一个字段被存储。
反例:
- 把 SKU "APL-iP15P-256-BLK" 拆成 brand / product / storage / color 四个字段
- 把 身份证号 "110101199001011234" 拆成省份 / 出生日期 / 性别 / 顺序码
- 把 URL "https://example.com/products/123" 拆成 protocol / domain / path
正例:
- 存储完整的 SKU、身份证号、URL
- 通过计算列或视图派生子信息(出生日期、性别等)
原则二:聚合的意义来自业务,不来自数据
只有业务上属于同一类的东西,聚合才有意义。
- 所有"航班号"可以按航司分组——因为航司是航班号的业务属性
- 但所有"1100"不能跨航司聚合——因为"数字 1100"不是业务属性,只是编码片段
- 同理:所有"中山路"的销售额不能加总,但"上海市中山路 100 号"作为一个门店是完整的业务对象
数据的聚合能力,取决于业务对象的完整性。
原则三:存储层不优化查询层
为了"查询方便"而拆分业务对象,是典型的以技压业。
你可能会说:"可是我经常要按航司筛选呀,拆开不是更快?"
回答问题:
LEFT(flight_no, 2)有索引的情况下,性能差异可以忽略- 用计算列 / 虚拟列,一样可以建索引
- 即使拆成两个字段,你的 SQL 也省不了几个字符
- 但你损失的,是业务语义的完整性和系统的长期可维护性
优先保护业务语义,其次才是性能优化。 而且绝大多数情况下,二者并不矛盾。
更多例子:看看你身边有没有这样的"拆分"
例子一:订单号
ORD-2026-0429-0038
│ │ │ │
│ │ │ └── 当日序号
│ │ └────── 日期
│ └─────────── 年份
└──────────────── 订单前缀
错误做法:拆成 order_prefix、order_year、order_date、order_seq 四个字段。
正确做法:order_no VARCHAR 作为完整存储,派生日期字段用于筛选。
理由:订单号作为一个整体在客服、物流、财务之间流转。没有人会说"帮我把那个 2026 年 0429 第三十八号单退一下"——他们会说"ORD-2026-0429-0038 这个单子"。
例子二:银行卡号
6222 0200 0001 2345 678
│ │
│ └── 发卡行识别码(IIN)
└────── 卡组织标识
错误做法:拆成卡组织、发卡行、卡序号。
正确做法:card_no VARCHAR 完整存储,通过 BIN 表关联发卡行信息。
理由:银行卡号在任何业务场景中都是一个整体。支付接口按完整卡号处理,对账单按完整卡号展示。把卡号拆开,除了增加 PCI-DSS 合规风险外,没有任何好处。
例子三:车牌号
京 A·12345
│ │
│ └── 序号部分
└────── 省份简称 + 发牌机关代号
错误做法:拆成 plate_province、plate_code、plate_number。
正确做法:plate_no VARCHAR 完整存储。需要按省份统计时,用 LEFT(plate_no, 1) 派生。
理由:车牌号是车辆在交通管理体系中的唯一标识,"京A12345"和"沪A12345"在 ETC、违章、保险等场景中是完全不同的对象。拆开之后,"12345"这个片段没有任何业务含义。
当拆分是合理的:一个判断框架
当然,不是所有带"编码格式"的东西都不能拆。什么时候拆是合理的?
| 条件 | 可以拆 | 不能拆 |
| 拆开后各部分有独立业务含义? | 是 | 否 |
| 拆开后各部分可以跨实例比较? | 是 | 否 |
| 业务中是否单独使用某一部分? | 经常 | 从不 |
| 拆分是不是为了消解多值依赖? | 是(范式化) | 否 |
典型合理拆分的例子:
- 地址:省/市/区/街道 拆分是合理的,因为"广东省"本身有独立的统计意义,"深圳市"本身可以作为筛选条件
- 日期:年/月/日 拆分为独立维度字段,用于多维分析
- 姓名:姓和名在某些文化中有独立使用场景(如按姓氏分组统计)
关键区别在于:地址的"省"单独拿出来,依然是"省"这个业务对象。但航班号的"1237"单独拿出来,它不是"航班"——它什么都不是。
总结
业务对象到数据的映射,本质上是业务语言到数据结构的翻译。翻译的第一原则是"信"——忠于原文。
- | 原则 | 一句话 |
- | 原子性 | 业务中作为一个整体使用的对象,作为一个字段存储 |
- | 聚合语义 | 只有业务上同类的东西,聚合才有意义 |
- | 存储优先 | 存储完整业务对象,查询需求用派生字段满足 |
- | 拆分的标准 | 拆开后各部分本身能作为独立的业务对象存在时,才拆分 |
回到开头那个反直觉的结论:HU1237 是一个完整的业务对象。 不是因为它的字母和数字连在一起,而是因为——在整个航空业务的语境中,"HU1237"就是那个不可再分的、有独立语义的、被所有人共同理解的最小编码单元。
把它拆开,你就不是在建模数据;你是在拆解业务。
——唯有知识让我们免于平庸
📖 相关文章
● 【致知篇44】逻辑世界:数据、佛法与体系
● 从问题到可视化:业务分析通识
● [航空] 旅客航司权限控制:两种方法对比与最佳实践
● 8.1 计算的演进及分类:从Excel、SQL到Tableau
● Global Education Center Shift Infographic
——————————————————————————————
No comments yet