05 | 召回前置准备:根据业务数据库生成各数据库(读取配置阶段)
项目地址:github.com/frontzhm/n2...
每一步对应的完整代码都在仓库里,跟着文档卡住了就去翻源码。
这是一篇系列文,请按照顺序阅读。
终态回顾:元数据库在整个流程中的位置
sql
dw.sql(业务数据仓库的 CREATE TABLE 语句)
│
│ 人工分析 ← 本文第一步
▼
conf/meta_config.yaml(人可读的元数据描述文件)
│
│ 脚本自动 ← 本文第二步
▼
MySQL meta 库(机器可查的元数据表)
│
│ 后续文章
▼
Qdrant 向量库 + ES 全文索引(召回用的基础设施)
元数据库是"数据的数据"------它不存业务数据,存的是描述业务数据长什么样的说明书。
问题来了:这份说明书从哪来?
答案是从业务数据仓库倒推 。你有 dw.sql(定义了什么表、什么字段),人工读一遍,翻译成 meta_config.yaml,再写一个脚本把它灌进 MySQL 的 meta 库。
第一步:从 dw.sql 到 meta_config.yaml
读 dw.sql,提取表结构
打开 docker/mysql/dw.sql,里面定义了 5 张表:
| 表名 | 角色猜测 | 说明 |
|---|---|---|
dim_region |
dim(维度表) | 前缀 dim_,存地区信息 |
dim_customer |
dim(维度表) | 前缀 dim_,存客户信息 |
dim_product |
dim(维度表) | 前缀 dim_,存商品信息 |
dim_date |
dim(维度表) | 前缀 dim_,存时间信息 |
fact_order |
fact(事实表) | 前缀 fact_,存订单交易记录 |
以 fact_order 为例,看它的 DDL:
sql
CREATE TABLE fact_order (
order_id VARCHAR(30) PRIMARY KEY,
customer_id VARCHAR(20),
product_id VARCHAR(20),
date_id INT,
region_id VARCHAR(20),
order_quantity INT,
order_amount FLOAT
);
逐字段分析:从 DDL 到元数据描述
拿 fact_order 的每个字段来解释"为什么要加这项元数据":
① name: 字段名 --- 直接从 DDL 抄过来,这是代码里调用的名字。
② role: 字段角色 --- 这是人工判断的,决定后续行为:
| 字段 | DDL 线索 | 判断的角色 | 原因 |
|---|---|---|---|
order_id |
PRIMARY KEY |
primary_key |
SQL 里直接标注了 |
customer_id |
命名含 _id,非主键 |
foreign_key |
关联到 dim_customer 的外键 |
product_id |
命名含 _id,非主键 |
foreign_key |
关联到 dim_product 的外键 |
date_id |
命名含 _id,非主键 |
foreign_key |
关联到 dim_date 的外键 |
region_id |
命名含 _id,非主键 |
foreign_key |
关联到 dim_region 的外键 |
order_quantity |
INT,非 id |
measure |
数值字段,可聚合(SUM/AVG) |
order_amount |
FLOAT,非 id |
measure |
数值字段,可聚合 |
角色为什么重要?合并节点会补全
primary_key和foreign_key防止 JOIN 漏掉;measure角色告诉 LLM 哪些字段能 SUM/AVG/COUNT。
③ description: 字段描述 --- 语义化解释这个字段是什么。DDL 里没有注释,需要人工补充。这是向量检索的关键------用户说中文,向量靠描述来匹配:
arduino
用户说 "销售额" → 向量 → 命中 order_amount(描述:"订单金额")
描述写得越好,向量匹配越准。
④ alias: 中文别名 --- 用户可能用不同说法指代同一个字段:
css
order_amount → 别名:[销售额, 订单金额, 收入]
order_quantity → 别名:[销量, 购买数量, 件数]
别名也会参与向量化,扩充匹配面。
⑤ sync: 是否同步到 ES 全文检索 --- 维度字段才需要同步(如 province 的"北京""上海"),主键、外键、度量的值没有搜索价值:
bash
region_id → sync: false(搜"R001"没意义)
province → sync: true(搜"上海"有意义)
order_amount → sync: false(搜"8999.00"没意义)
转化成 YAML
把上面的分析结果写成 conf/meta_config.yaml。以 fact_order 为例,完整 YAML 如下:
yaml
tables:
- name: fact_order
role: fact
description: 订单事实表,记录订单数量和金额等核心指标。
columns:
- name: order_id
role: primary_key
description: 订单唯一标识。
alias: [订单ID]
sync: false
- name: customer_id
role: foreign_key
description: 关联客户维度的外键。
alias: [客户ID, 用户ID]
sync: false
- name: product_id
role: foreign_key
description: 关联商品维度的外键。
alias: [商品ID, 产品ID]
sync: false
- name: date_id
role: foreign_key
description: 关联时间维度的外键。
alias: [日期, 下单日期]
sync: false
- name: region_id
role: foreign_key
description: 关联地区维度的外键。
alias: [地区ID, 区域ID]
sync: false
- name: order_quantity
role: measure
description: 订单中商品的购买数量。
alias: [销量, 购买数量, 件数]
sync: false
- name: order_amount
role: measure
description: 订单金额。
alias: [销售额, 订单金额, 收入]
sync: false
另外还需要定义指标 。指标是跨字段的计算逻辑,比如 GMV = SUM(order_amount)。指标也作为 tables 下的一个条目,名字就是 MySQL 里的表名 metric_info:
yaml
- name: metric_info
role: metric
description: 指标定义表,存储跨字段的计算指标,如 GMV、AOV 等。
columns:
- name: GMV
description: 全称Gross Merchandise Value,表示所有订单的成交金额总和。
alias: [成交总额, 订单总额]
relevant_columns:
- fact_order.order_amount
- name: AOV
description: 全称Average Order Value,表示所有订单的成交金额平均值。
alias: [平均单价, 平均订单金额]
relevant_columns:
- fact_order.order_quantity
把指标放在
tables下面而不是另起一个metrics顶级 key,是为了结构统一------tables里的每一项对应meta库的一张表,role字段区分是dim(维度表)、fact(事实表)还是metric(指标表)。
完整的 meta_config.yaml 已放在 conf/ 目录下,覆盖了 dw.sql 的 5 张表和 2 个指标。
总结:YAML 作为"单一事实来源"
meta_config.yaml 的核心价值是一份文件描述整个数据库的元数据。为什么不全自动从 dw.sql 解析?
- 角色(role)无法自动判断 :
customer_id到底是主键还是外键?要给dim_customer才知道。order_quantity是度量还是普通整数?SQL 分不出。 - 描述和别名没法自动生成 :
order_quantity可以叫"订单数量"也可以叫"购买件数",没有 AI 能替业务方做这个决定。 - sync 标记是业务决策:哪些字段值值得被搜索引擎索引?这是人定的。
所以元数据的生产路线是半自动:SQL 提供骨架(表名、字段名、类型),人补充语义(角色、描述、别名、sync),输出 YAML。
第二步:从 meta_config.yaml 到 MySQL meta 库
YAML 是人读的,MySQL 表是机器读的。这一步写一个 Python 脚本,读取 YAML,INSERT 进 MySQL。
目标表结构回顾
docker/mysql/meta.sql 建了四张表:
| 表 | YAML 中的对应位置 | id 格式 |
|---|---|---|
table_info |
tables[] |
{table_name} |
column_info |
tables[].columns[] |
{table_name}_{column_name} |
metric_info |
tables[] 中 role: metric 的条目 |
{metric_name} |
column_metric |
tables[].columns[].relevant_columns[] |
联合主键 |
id用VARCHAR而不是自增整数的原因是:后续代码中直接用 id 拼出表名和字段名,不用 JOIN 查。column_info.id = "fact_order_order_amount",一眼就知道这是fact_order表的order_amount字段。
映射关系(YAML → SQL)
ini
meta_config.yaml MySQL table_info / column_info / metric_info
───────────────── ────────────────────────────────────────────
tables[0].name "dim_region" ──────→ table_info.id = "dim_region"
tables[0].role "dim" ──────→ table_info.role = "dim"
tables[0].description "地区..." ──────→ table_info.description = "地区..."
tables[0].columns[0]:
name "region_id" ──────→ column_info.id = "dim_region_region_id"
role "primary_key" ──────→ column_info.role = "primary_key"
description "地区唯一标识。" ──────→ column_info.description = "..."
alias ["地区ID","区域ID"] ──────→ column_info.alias = '["地区ID","区域ID"]' (JSON)
sync false ──────→ (不存数据库,脚本里控制 ES 同步逻辑)
─ → column_info.table_id = "dim_region"
tables[N].columns[M] (role: metric):
name "GMV" ──────→ metric_info.id = "GMV"
description "成交金额总和..." ──────→ metric_info.description = "..."
alias ["成交总额","订单总额"] ──────→ metric_info.alias = '["成交总额","订单总额"]'
relevant_columns:
"fact_order.order_amount" ──────→ column_metric(column_id, metric_id)
column_id = "fact_order_order_amount"
metric_id = "GMV"
读取配置
用 pyyaml 读两个 YAML 文件。
py
import yaml
from pathlib import Path
CONF_DIR = Path(__file__).resolve().parent # conf/
META_CONFIG_PATH = CONF_DIR / "meta_config.yaml"
APP_CONFIG_PATH = CONF_DIR / "app_config.yaml"
def load_meta_config(META_CONFIG_PATH: Path = META_CONFIG_PATH) -> dict:
"""从 YAML 文件加载元数据配置,返回原生 dict。"""
if not META_CONFIG_PATH.exists():
raise FileNotFoundError(f"配置文件不存在:{META_CONFIG_PATH.absolute()}")
with open(META_CONFIG_PATH, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def load_app_config(APP_CONFIG_PATH: Path = APP_CONFIG_PATH) -> dict:
"""从 YAML 文件加载应用配置,返回原生 dict。"""
if not APP_CONFIG_PATH.exists():
raise FileNotFoundError(f"配置文件不存在:{APP_CONFIG_PATH.absolute()}")
with open(APP_CONFIG_PATH, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
app_config = load_app_config()
meta_config = load_meta_config()
# ── 快速自测 ──
if __name__ == "__main__":
print(app_config)
print(meta_config)
然后执行uv run python conf/get_config.py就先配置就位
脚本设计
conf/sync_db.py 是同步脚本,核心逻辑分三步------把 meta_config.yaml 里的元数据分别灌入三处:
py
# conf/sync_db.py 调用骨架
meta_config = load_meta_config()
# 第一步:写入 MySQL meta 库(结构化元数据)
# → table_info, column_info, metric_info, column_metric
sync_to_mysql(meta_config)
# 第二步:字段向量化 → 写入 Qdrant(语义检索)
# → 每个字段的 description + alias 生成 embedding
# → 存入 Qdrant collection
sync_to_qdrant(meta_config)
# 第三步:维度值 → 写入 Elasticsearch(全文检索)
# → sync=true 的维度字段,从 dw 库 SELECT DISTINCT 值
# → 写入 ES index
sync_to_elasticsearch(meta_config)
第一步:写 MySQL
把 YAML 里的表→table_info、字段→column_info、指标→metric_info、关联→column_metric 四张表填满。映射关系上文已经画过图表,这里就是读 YAML → 拼 INSERT。代码暂时是空函数,后续实现。
第二步:写 Qdrant
Qdrant 是向量数据库,专门做语义匹配。用户问"各个地区的销售额",系统要找到 order_amount 这个字段,靠的就是向量。
具体做法:把每个字段的 name + description + alias 拼接成一段文本,用 BGE 模型把它变成一个 1024 维的浮点数数组(向量),存入 Qdrant。用户 query 来了也做同样的向量化,在 Qdrant 里找余弦相似度最高的几个字段。
yaml
column_info 中的一条记录:
name: order_amount
description: 订单金额
alias: [销售额, 订单金额, 收入]
↓ 拼接
"订单金额 order_amount 销售额 订单金额 收入"
↓ BGE 模型向量化
[0.023, -0.451, 0.892, ...] (1024 个 float)
↓ 存入 Qdrant
collection: column_vectors
id: fact_order_order_amount
vector: [0.023, -0.451, 0.892, ...]
payload: {table: "fact_order", name: "order_amount", role: "measure", ...}
用户问"销售额"时,"销售额"的向量和 order_amount 的向量很接近,Qdrant 就把它排在第一位返回。
同理,metric_info 里的 GMV、AOV 也需要向量化存入 Qdrant,这样用户说"成交总额"才能命中 GMV 指标。
第三步:写 Elasticsearch
ES 用来做字段值 的模糊搜索。比如用户问"手机类商品的销售额",ES 负责找到 dim_product 表中 category 的值 "手机数码"。
具体做法:遍历 sync=true 的维度字段,对每个字段去 dw 库执行 SELECT DISTINCT 拿到不重复的值列表,把每条值写入 ES index。ES 的 IK 中文分词器会把 "手机数码" 切成 "手机"、"数码",用户搜"手机"也能命中。
css
column_info 中 sync=true 的字段:
dim_product.category → SELECT DISTINCT category FROM dim_product
→ [手机数码, 家用电器, 鞋靴, 服饰, 食品饮料, 休闲零食]
dim_product.brand → SELECT DISTINCT brand FROM dim_product
→ [苹果, 三星, 华为, 戴森, 美的, ...]
dim_region.province → SELECT DISTINCT province FROM dim_region
→ [广东省, 浙江省, 四川省, 北京市, 上海市, 湖北省]
...
↓ 逐条写入 ES
ES index: data_agent
doc 1: {field: "dim_product_category", value: "手机数码"}
doc 2: {field: "dim_product_category", value: "家用电器"}
...
为什么这样做?------ "可重放的同步"
这整套设计的底层思想是幂等同步:
不管执行多少次,结果都一样,不会丢数据、不会重复。
具体做法:每次执行脚本时,先 DELETE meta 库中所有旧数据,再全部重新 INSERT。看起来粗暴,但有三个巨大好处:
-
改 YAML 即改完 :加一张表、改一个字段的 alias、增删一个指标------只要改
meta_config.yaml,跑一遍脚本,元数据库就同步了。 -
不怕脚本中途崩溃:如果中间报错退出,mysql 默认 autocommit=1 会导致部分数据写入。解决方案是用事务包起来:
pythonasync with pool.begin() as conn: # 开启事务 await conn.execute("DELETE FROM column_metric") await conn.execute("DELETE FROM metric_info") await conn.execute("DELETE FROM column_info") await conn.execute("DELETE FROM table_info") # 批量 INSERT... # 事务结束自动 COMMIT------要么全成功,要么全回滚 -
Git 友好 :
meta_config.yaml是一份纯文本 YAML,改了什么一目了然。团队协作时,改字段 alias 不再需要靠脑子记,git diff 直接看。
当前目录结构
txt
n2sql-agent/
├── conf/
│ ├── meta_config.yaml # 元数据定义(人读)
│ ├── app_config.yaml # 连接配置
│ ├── get_config.py # 读取配置
│ └── sync_db.py # 同步脚本(三步灌入)
├── docker/
│ └── mysql/
│ ├── meta.sql # meta 库 DDL(机器读,空壳表)
│ └── dw.sql # dw 库 DDL + 测试数据(业务数据)
└── ...
下一步
元数据库就绪后,下一篇文章开始写真正的召回节点------从 meta 库读数据、灌入 Qdrant 和 ES、再用关键词去查。
科普:元数据库
一句话理解
元数据库(Meta Database)就是「描述数据的数据」。
业务数据库里存的是订单、客户、商品这些真实数据;元数据库里存的是这些表"长什么样"的说明书。
以图书馆来类比:
| 概念 | 图书馆类比 | n2sql-agent 中 |
|---|---|---|
| 业务数据 | 书架上的真实书籍 | dw.sql 里的 dim_product、fact_order 表 |
| 元数据 | 图书馆编目卡片(书名、作者、书架号、分类) | meta_config.yaml 里的字段描述、别名、角色 |
| 元数据库 | 存放编目卡片的卡片柜(可查询、排序、检索) | MySQL 里的 meta 库(table_info、column_info 等表) |
为什么需要元数据库?
用户问题:"各地区的 GMV 变化趋势怎么样?"
n2sql-agent 需要回答三个问题才能写出 SQL:
- "GMV"是什么 ?→ 查
metric_info表,发现 GMV = SUM(order_amount) - "各地区"对应哪个字段 ?→ 查
column_info表,用向量匹配"地区" → 命中dim_region.region_name - GMV 从哪张表取 ?→ 查
metric_info.relevant_columns→fact_order.order_amount
没有元数据库,AI 只能靠"猜",猜错了 SQL 就跑不了。
核心概念
① 表(table_info)
| 字段 | 含义 | 示例 |
|---|---|---|
id |
表编号 | dim_region |
name |
表名称 | dim_region |
role |
表类型 | dim(维度表)/ fact(事实表)/ metric(指标表) |
description |
表的功能说明 | "地区维度表,用于描述订单发生的地理区域信息" |
② 列(column_info)
| 字段 | 含义 | 示例 |
|---|---|---|
id |
列编号,格式 {表名}_{字段名} |
fact_order_order_amount |
name |
列名称 | order_amount |
type |
数据类型 | FLOAT |
role |
列角色 | measure(指标量)/ dimension(维度) / foreign_key / primary_key |
description |
列的业务含义 | "订单金额" |
alias |
中文别名列表(JSON) | ["销售额", "订单金额", "收入"] |
examples |
数据示例(JSON) | [8999.00, 9499.00, 125.00] |
table_id |
所属表 | fact_order |
③ 指标(metric_info)
| 字段 | 含义 | 示例 |
|---|---|---|
id |
指标编码 | GMV |
name |
指标名称 | GMV |
description |
指标的定义和计算方式 | "所有订单的成交金额总和" |
relevant_columns |
涉及哪些字段(JSON) | ["fact_order.order_amount"] |
alias |
中文别名(JSON) | ["成交总额", "订单总额"] |
元数据如何参与召回?
整个流程是三层递进的:
vbnet
用户说 "销售额"
│
├── 向量检索(Qdrant):用 "销售额" 的向量去匹配 column_info 的 description + alias 的向量
│ → 命中 order_amount(描述:"订单金额")
│
├── 全文检索(ES):用 "销售额" 去 ES 里搜索字段对应的值
│ → 在 dim_product 中搜 "手机" → 返回 "iPhone 15 Pro"、"Mate 60 Pro"
│
└── 元数据表(MySQL meta):根据命中结果补全上下文
→ order_amount 属于 fact_order 表
→ fact_order 通过 foreign_key 关联 dim_region、dim_product 维度表
→ 要写 JOIN 才能查出"各地区的销售额"
没有元数据库,这三层都做不到。它是一切召回的基础数据源。
科普:维度表和事实表
一句话理解
维度表(Dimension Table):描述"谁、什么、何时、何地"。 事实表(Fact Table):记录"发生了什么"。
这两类表合在一起,构成了星型模型------数据仓库最经典的设计模式。
markdown
dim_region(何地)
│
dim_customer──┼── fact_order ──dim_date(何时)
(谁) │ (何时)
│
dim_product(什么)
中心是 fact_order(事实表),四个角是四张维度表,像一颗星星。
类比:超市收银小票
你去超市买完东西,拿到的小票长这样:
arduino
超市小票
─────────────────────────
日期:2025-01-15 ← "何时"信息
商品:康师傅红烧牛肉面 ×2 ← "什么"信息(商品名、数量)
金额:¥19.60 ← "发生了多少钱"(事实)
收银台:2号 ← "何地"信息
会员卡号:8800123 ← "谁"信息
─────────────────────────
| 小票上的信息 | 属于 | 对应本项目表 | 为什么 |
|---|---|---|---|
| 金额 ¥19.60、数量 2 | 事实 | fact_order(order_amount, order_quantity) |
可量化的、会变化的数据 |
| 日期 2025-01-15 | 维度 | dim_date(year, month, day) |
描述"何时" |
| 商品 康师傅红烧牛肉面 | 维度 | dim_product(product_name, category) |
描述"什么" |
| 会员卡号 8800123 | 维度 | dim_customer(customer_name, gender) |
描述"谁" |
| 收银台 2号 | 维度 | dim_region(province, region_name) |
描述"何地" |
判断规则:这字段是维度还是事实?
一个简单的判断方法:
看这个字段能不能做 SUM / AVG / COUNT 聚合运算。能 → 事实表;不能 → 维度表。
| 字段 | 能做 SUM 吗? | 角色 |
|---|---|---|
order_amount(订单金额) |
可以,SUM 后 = 总销售额 | 事实 |
order_quantity(购买数量) |
可以,SUM 后 = 总销量 | 事实 |
gender(性别) |
不能,SUM("男") 毫无意义 | 维度 |
province(省份) |
不能,SUM("广东省") 毫无意义 | 维度 |
product_name(商品名) |
不能 | 维度 |
事实表的关键特征(以 fact_order 为例)
yaml
order_id customer_id product_id date_id region_id order_quantity order_amount
────────── ─────────── ────────── ──────── ───────── ────────────── ────────────
ORD2025... C001 P001 20250101 R001 1 8999.00
ORD2025... C005 P003 20250101 R005 1 6999.00
...
- 行数最多:每笔订单一条记录,随业务增长不断涨
- 全是外键 + 度量 :
customer_id、product_id等外键指向维度表;order_quantity、order_amount是度量值 - 几乎全数字:事实表的列要么是数字 ID(外键),要么是能算的数值(度量)
- 自己"说不清" :看到
customer_id = C001你不知道是谁,必须 JOIN 维度表才知道是"李伟"
维度表的关键特征(以 dim_product 为例)
erlang
product_id product_name category brand
────────── ──────────────────────── ────────── ──────
P001 iPhone 15 Pro 手机数码 苹果
P002 Galaxy S24 Ultra 手机数码 三星
...
- 行数稳定:商品种类不会频繁变化
- 描述性的列:都是文字(名称、类别、品牌),不是数字
- 自描述 :看到
product_id = P001→ 查到iPhone 15 Pro,信息完整
为什么要分开存?------ 避免数据冗余
如果不用星型模型,把商品信息直接塞进每笔订单会怎样?
yaml
❌ 不用星型模型(信息冗余):
order_id product_name category brand order_amount
ORD2025... iPhone 15 Pro 手机数码 苹果 8999.00
ORD2026... iPhone 15 Pro 手机数码 苹果 8999.00
ORD2027... iPhone 15 Pro 手机数码 苹果 8999.00
→ "iPhone 15 Pro / 手机数码 / 苹果" 这个组合重复了 100 万次!
✅ 用星型模型(只存 ID):
order_id product_id order_amount → fact_order 只有三列
ORD2025... P001 8999.00
ORD2026... P001 8999.00 → 商品信息只在 dim_product 存一次
ORD2027... P001 8999.00
这样维度表里的 iPhone 15 Pro 改了只要改一个地方,不会漏掉。
科普:YAML
一句话理解
YAML(YAML Ain't Markup Language)是一种「给人看比给机器看更舒服」的配置文件格式。
和 JSON 一样是描述数据的,但 JSON 全是括号和引号看着累,YAML 靠缩进和短横线,更像笔记。
JSON vs YAML(同样描述一张表)
JSON 写法:
json
{
"name": "fact_order",
"role": "fact",
"description": "订单事实表,记录订单数量和金额等核心指标。",
"columns": [
{
"name": "order_amount",
"role": "measure",
"description": "订单金额。",
"alias": ["销售额", "订单金额", "收入"],
"sync": false
}
]
}
YAML 写法:
yaml
name: fact_order
role: fact
description: 订单事实表,记录订单数量和金额等核心指标。
columns:
- name: order_amount
role: measure
description: 订单金额。
alias: [销售额, 订单金额, 收入]
sync: false
同样一段数据,YAML 少了 {}、""、逗号,视觉噪音少得多。这就是 n2sql-agent 选 YAML 存元数据配置的原因------人要反复改 alias 和 description,JSON 看得眼花,YAML 一眼扫过去就懂了。
YAML 的核心语法
① 键值对 --- 基础单元,用冒号分隔:
yaml
name: fact_order # 字符串
role: fact # 字符串可以不加引号
order_quantity: 100 # 数字
sync: true # 布尔值
② 列表 --- 用短横线 - 开头:
yaml
# 行内写法(短列表推荐)
alias: [销售额, 订单金额, 收入]
# 多行写法(元素复杂时推荐)
columns:
- name: order_id
role: primary_key
description: 订单唯一标识。
- name: order_amount
role: measure
description: 订单金额。
③ 嵌套 --- 靠缩进(空格,不能用 Tab)表示层级:
yaml
tables: # 第一层
- name: fact_order # 第二层(tables 的子项)
columns: # 第三层(fact_order 的子项)
- name: order_amount # 第四层(columns 的子项)
role: measure # 第五层(order_amount 的子项)
YAML 里容易踩的坑
| 坑 | 错误写法 | 原因 |
|---|---|---|
| 缩进混用 Tab 和空格 | name: xxx(Tab 缩进) |
YAML 只认空格 |
| 冒号后没空格 | name:fact_order |
解析器分不清是键值对还是普通字符串 |
| 字符串含特殊字符没加引号 | description: 订单:包括金额、数量 |
冒号被当成键值对分隔符 |
| 整数前导零 | month: 08 |
08 在 YAML 里被当作八进制数,会出错 |
安全写法:不确定就加引号。
yaml
description: "订单:包括金额、数量。" # 有特殊字符时加引号保平安
本项目中 YAML 扮演的角色
markdown
人 ←──── YAML ────→ Python 脚本 ────→ MySQL
(读起来舒服) (pyyaml 库解析) (INSERT)
meta_config.yaml 是人和机器之间的中间语言:
- 对人来说:看起来像笔记,改 alias 改描述跟改文本文档一样简单
- 对机器来说:
yaml.safe_load()一行代码就变成 Python 的 dict 和 list,直接遍历写入数据库
python
import yaml
with open("conf/meta_config.yaml") as f:
config = yaml.safe_load(f)
# config 现在是 Python 字典,直接遍历:
for table in config["tables"]:
print(table["name"], table["role"])
这就是选 YAML 不选 JSON 也不选纯数据库管理的核心理由------配置类数据,先让人舒服,再让机器舒服。