不可能的规范化:为什么你的数据库讨厌生物学 🧬

发布: (2025年12月30日 GMT+8 00:00)
6 min read
原文: Dev.to

Source: Dev.to

用户对象

我们需要讨论 用户对象

如果你在网页开发领域已经超过五分钟,你可能已经构建过电子商务后端或待办事项应用。数据建模通常是这样的:

  • 存在一个 User(用户)。
  • 存在一个 Product(产品)(它有 SKU、价格和描述)。
  • 用户购买该产品。

这很清晰且确定。如果我购买一件 红色 T‑Shirt(Size L),该对象不会改变。它在仓库中存放,随后发货,最终以 红色 T‑Shirt 的形式到达。

现在,想象一下这件 红色 T‑Shirt 可以根据天气自行改变尺码,或者自称为“有点红粉色,但触摸时会疼”。

欢迎来到 HealthTech

“简单”症状陷阱

假设你正在为患者构建一个追踪症状的应用。我们内心的初级开发者会立刻想到:“很简单。SQL 表。”

CREATE TABLE symptoms (
  id SERIAL PRIMARY KEY,
  user_id INT,
  name VARCHAR(255),   -- "Headache"
  severity INT,        -- 1 to 10
  created_at TIMESTAMP
);

直接上船,对吧?

错误。
两天后,用户写道:“我的头痛是 7/10,但具体在左眼后方,站起来时会跳动。”

你的 name 列已经崩溃。你需要位置(左眼后方)、性质(跳动)以及诱因(站立)。

于是你想:“好吧,我改用 NoSQL 文档存储。我直接扔一个 JSON 块!”

{
  "symptom": "Headache",
  "meta": {
    "location": "Left retro-orbital",
    "quality": "Pulsating",
    "trigger": "Orthostatic"
  }
}

太好了。现在尝试在 10,000 名用户中查询所有“头部问题”。做不到,因为用户 A 写了 “Headache”,用户 B 写了 “Migraine”,而用户 C 写了 “My head hurts”。

进入本体论:SNOMED‑CT 和 LOINC

为了解决这个问题,业界发明了标准术语。这里的大老板是 SNOMED‑CT,它为几乎所有医学概念分配唯一代码。头痛不再是 “Headache”,而是 SCTID: 25064002

这听起来像是开发者的梦想(规范化!),但实际上会变成实现噩梦。生物学是一个图,而不是列表。某种特定类型的头痛是 “Pain” 的 子概念,而 “Pain” 又是 “Clinical Finding” 的 子概念

要正确存储这些信息,我们通常会使用 FHIR(快速医疗互操作资源)。下面是一个体温观察的简化 JSON 示例:

{
  "resourceType": "Observation",
  "status": "final",
  "category": [
    {
      "coding": [
        {
          "system": "http://terminology.hl7.org/CodeSystem/observation-category",
          "code": "vital-signs",
          "display": "Vital Signs"
        }
      ]
    }
  ],
  "code": {
    "coding": [
      {
        "system": "http://loinc.org",
        "code": "8310-5",
        "display": "Body temperature"
      }
    ]
  },
  "valueQuantity": {
    "value": 39.1,
    "unit": "degrees C",
    "system": "http://unitsofmeasure.org",
    "code": "Cel"
  }
  // ... timestamps, performers, device used ...
}

我们从 temperature: 39.1 变成了一个约 30 行的 JSON 对象,因为在生物学中,语境就是一切。口服测得的 39.1 °C 与腋下测得的 39.1 °C 是不同的。如果不存储这些元数据,数据就会变得在医学上极其危险。

地图不是领土

我们作为工程师面临的最大哲学障碍是 人类健康是连续的,而数据库是离散的

当用户从下拉菜单中选择“中度疼痛”时,我们把一种复杂、流动的生物感受压缩成一个明确的枚举,失去了分辨率。

我在个人博客上写了更多关于这种数据分辨率损失的内容——如果你对更深入的架构理论感兴趣,查看我的其他文章

挑战在于创建一种看起来有人情味的 UI(例如,“疼痛部位在哪里?”),同时将其映射到像 FHIR 和 SNOMED 这样刚性的、复杂的本体论,而用户根本不需要了解这些标准。

那么,解决方案是什么?

没有灵丹妙药,但以下模式在实际使用中往往有效:

  • 存储原始意图: 保留用户实际输入或点击的内容。绝不要丢失真相来源。
  • 后期映射: 使用数据摄取管道将 “My head hurts” 转换为 SCTID: 25064002。不要强迫用户直接使用 SNOMED。
  • 混合模式: 对高层数据(患者 ID、日期)使用关系型列,对临床负载(FHIR 资源)使用 JSONB 列。

这很乱、令人沮丧,确实比卖 T‑恤要困难得多。但至少永远不会无聊。

还有人现在正为 HL7/FHIR 集成而苦恼吗?在评论里一起抱怨吧。 😭

Back to Blog

相关文章

阅读更多 »