元数据系统能够回答四类问题,字段清单只是其中一项:数据是什么、是否能用、怎样用、哪些场景不能用。元数据系统由两部分组成:元数据资产和元数据语义,元数据资产负责保存并展示可核验的数据事实;元数据语义在这些事实之上补充业务叫法、问题边界和查询样例。两者结合,AI 才能先找到正确的数据,再生成符合粒度、时效性与使用限制的 SQL。

为什么有了表结构,AI 仍然会写错 SQL

数据库已经保存表名、字段名和类型,但这些信息不足以支持业务查询。比如 close_price 是收盘价,per_change 是涨跌百分比的数值,这类含义可以写进字段描述,但是“昨天”应该映射到哪个时间字段、这张表是否支持实时查询、能否直接求和,则必须有更详细的业务语义。只把 DDL 交给模型,模型可以写出语法正确的 SQL,却未必是我们需要的。

落地时需要把信息拆成两个相互关联但职责不同的部分:

  • 元数据资产记录物理表、业务分类、粒度、主键、字段、DDL、负责人、生命周期和运行状态。
  • 元数据语义记录别名、检索关键词、时间字段、关联方式、问题样例、SQL 样例、支持范围和限制。

这种拆分保留了事实来源。数据库采集到的结构不应被语义编辑覆盖;业务人员补充的语义也不应伪装成自动采集结果。两部分可以独立演进,但发布时需要绑定到明确的元数据版本。

Mermaid 图 1

元数据资产:把元数据详情做成可判断的资产视图

以表 main.dwd_fund_idx_index_data_di 为例,中文名是“指数明细数据”。元数据要做的事情就是:先确认这是什么数据,再确认字段能否满足需求,最后检查数据是否处于可用状态。

基本信息先回答“这张表代表什么”

以下图为例,该模型归属 DWD 层,模型类型为事务事实表,主题域是基金,主题是指数,业务过程是基金收盘。最重要的一项是数据粒度:每个 busi_dateindex_name 组合一行,业务主键也是这两个字段。

粒度不是普通说明文字。它可以决定是否可以上卷或者下钻、Join 是否会放大数据、自然语言中的“每天每个指数”如何落到分组键。若粒度缺失,AI 很容易把明细值当成已经汇总的指标,或者在关联后产生重复记录。

元数据资产还记录数据源是什么、数据引擎是哪个、生命周期是“在线”还是已经下线以及负责人是谁。这些信息分别服务于路由、方言选择、可用性判断和问题追责。即使元数据属性为空也应如实展示,例如部门、分区、更新周期和更新类型当前为“暂无”,不能为了让页面看起来完整而自动编造。 粘贴图片

字段、约束与 DDL 必须能相互校验

看一下字段信息,dwd_fund_idx_index_data_di 共有12 个字段。id 是代理主键;busi_dateindex_name 是业务主键;close_priceprice_changeper_changevolumeamount 承载行情明细;data_sourcecreate_timeupdate_time 记录来源和装载时间。字段区还展示类型、描述、是否分区以及脱敏策略。 粘贴图片

DDL 区给出了同一张表的建表语句,其中 id 是主键,busi_dateindex_name 构成唯一约束。 粘贴图片

版本和运行状态决定“现在能不能用”

运行状态给出了最后更新时间 2026-08-21 07:00:22、最新分区“非分区表”和当天任务执行结果“成功”。使用建议据此说明最新执行实例成功,可正常使用。 粘贴图片

结构正确不代表数据新鲜。一个可用于 AI 查询的元数据资产至少要判断生命周期、调度结果和数据时效。当数据任务运行失败时,AI仍然可以返回这张表的数据,但查询编译器应把状态风险反馈给用户,而不是静默生成 SQL。

Mermaid 图 2

版本管理需要区分草稿与已发布快照。发布后生成不可变快照版本,供检索和 SQL 编译使用。这样才能回答“某条 SQL 当时依据的是哪一版元数据”。 粘贴图片

元数据语义:可执行的Agent使用说明

还是以表 main.dwd_fund_idx_index_data_di 为例,在元数据语义中关注点已经改变,它不再重复解释数据库结构,而是说明业务问题如何命中这张表,以及命中后应该怎样查询。

时间、关联和问题边界是查询前置条件

页面指定时间字段为 busi_date,慢变维标记为“否”。这使“昨天”“最近一周”等时间表达有了明确落点。Join 属性当前尚未配置,因此系统不应凭表名或字段相似度自动推断关联条件。缺失关系时,正确行为是保持单表查询或提示需要补充关联,而不是生成看似合理的 Join。 粘贴图片 页面还明确了两条限制:数据是 T+1 离线数据,不支持实时场景;直接查询返回明细数据,不是聚合数值。限制必须进入检索结果和生成提示上下文。若用户询问实时价格,系统应该拒绝把这张表作为完全匹配的数据源;若用户要求“最近一周收盘价格总和”,则需要显式使用聚合函数,并进一步判断这种汇总是否有业务意义。 粘贴图片

别名和关键词解决“用户不会说表名”的问题

语义详情为该资产配置了“每日股票指数明细、股票指数明细、股票指数、指数明细”等别名,也配置了“股票指数、指数、指数明细、指数价格、指数收盘价格、指数涨跌幅”等检索关键词。它们将用户语言映射到稳定资产标识 index.dwd_fund_idx_index_data_di粘贴图片 别名不是越多越好。维护时应记录词项来源、审核状态和命中反馈,避免把相邻但不同的概念塞入同一资产。词项发布后应进入搜索索引;删除或修改词项则需要重新构建对应索引,并保留版本关系。

一个实用的检索文档可以由以下字段组成:

{
  "semantic_id": "index.dwd_fund_idx_index_data_di",
  "metadata_fqn": "main.dwd_fund_idx_index_data_di",
  "metadata_version": "v1",
  "display_name": "指数明细数据",
  "aliases": ["每日股票指数明细", "股票指数明细", "股票指数", "指数明细"],
  "keywords": ["指数价格", "指数收盘价格", "指数涨跌幅"],
  "time_field": "busi_date",
  "grain": ["busi_date", "index_name"],
  "freshness": "T+1",
  "supports_realtime": false,
  "lifecycle": "在线"
}

metadata_version 不能省略。没有版本绑定,语义对象引用的字段可能已在物理模型中删除或更名,检索仍能命中,但生成阶段会得到错误的结果。

问题与 SQL 样例把解释变成可测试的契约

页面配置的问题是“昨天上证指数的收盘价格和涨跌幅”,这组样例同时证明了时间映射、实体过滤和字段选择。 粘贴图片

时间条件必须使用声明的时间字段;样例问题中提到的业务对象应在过滤条件或分组中出现。具备只读测试环境时,还可以使用 EXPLAIN 或限定行数执行,检查方言和权限。

import sqlite3


def explain_read_only(sql: str, db_path: str) -> None:
    normalized = sql.lstrip().lower()
    if not normalized.startswith("select"):
        raise ValueError("样例 SQL 只允许 SELECT")

    connection = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
    try:
        connection.execute("EXPLAIN QUERY PLAN " + sql).fetchall()
    finally:
        connection.close()

支持的问题示例还列出“最近一周上证指数的收盘价格”“2026年1月1号纳斯达克综合指数的价格”等支持问题。这些表达可以转成回归用例,用来验证改动别名、时间解析或字段描述之后,正确的元数据资产是否仍排在检索结果前列。

Mermaid 图 3

小结

元数据资产把表结构、业务分类、粒度、字段、版本和运行状态组织为一个可判断的数据资产;元数据语义详情把别名、关键词、时间、样例和限制组织为一个可执行的使用契约。 完成这一步后,AI 问数链路不再依赖松散说明,它获得一份带来源、状态和边界的证据包。