ChatBI 语义层设计详解

ChatBI 语义层设计详解 --- 表结构与设计合理性

基于电商数仓实际表结构 · 12 张元数据表 · MySQL JSON + 应用层 cosine 向量检索

配套文档:chatbi_semantic_layer_design.md(原始设计文档)| chatbi_semantic_layer_design_explained.html(HTML 富媒体版)


目录

  1. 六层分层架构总览
  2. [元数据 ER 关系图](#元数据 ER 关系图)
  3. [核心配置表设计详解(5 张表)](#核心配置表设计详解(5 张表))
  4. [辅助配置表设计详解(3 张表)](#辅助配置表设计详解(3 张表))
  5. [向量检索表设计详解(2 张表)](#向量检索表设计详解(2 张表))
  6. [协作治理表设计详解(3 张表)](#协作治理表设计详解(3 张表))
  7. [配置拆分映射:一条 YAML 如何拆到多表](#配置拆分映射:一条 YAML 如何拆到多表)
  8. 设计原则总结

1. 六层分层架构总览

语义层不是一层独立的"表",而是介于物理数仓(DIM/DWD/DWS/ADS)与用户交互之间的一组 结构化配置 + 确定性编译引擎。整体从底向上共 6 层:

编译器 7 步流程S0 加载(5min缓存) → S1 校验 → S2 选表合并 → S3 原子建列 → S4 派生展开 → S5 补 Join → S6 补 Where → S7 GroupBy/OrderBy

设计合理性:将"意图理解"(LLM 擅长)和"SQL 编译"(确定性逻辑)彻底分离。LLM 只输出 DSL JSON(选词填空),SQL 的 Join 路径、口径过滤、字段映射全部由语义层引擎确定性处理,避免 LLM 产生幻觉 SQL。


2. 元数据 ER 关系图

12 张表以 sl_ 为前缀,存独立元数据库 chatbi_meta,与业务数仓物理隔离。双核心辐射结构:sl_metric(指标)和 sl_dimension(维度)。

三层关注点分组

分组 职责
维度侧 sl_dimension、sl_dimension_hierarchy 维度定义与层次
指标侧 sl_metric、sl_synonym、sl_metric_caliber、sl_metric_dim_avail 指标定义、同义词、口径、可用维度
辅助配置 sl_join_path、sl_global_filter Join 路径、全局过滤器(共享配置)
向量检索 sl_vector_doc、sl_vector_doc_dead_letter 向量文档与死信补偿
协作治理 sl_user_role、sl_change_request、sl_audit_log 角色权限、审批状态机、审计回滚

3. 核心配置表设计详解(5 张表)

这 5 张表构成语义层的"业务核心配置",定义了"有哪些指标、有哪些维度、它们之间如何关联、叫什么名字、口径是什么"。

3.1 sl_metric --- 指标主表

核心表 · 47+ 个指标的元定义

设计思路 :每个指标一行,记录其 物理映射 (preferred_table + physical_expr)和 回退路径(fallback_table/where/expr)。同时区分原子指标(atomic)和派生指标(derived,通过 formula + components 引用其他指标)。

具体例子:pay_gmv(成交GMV)

复制代码
# 首选 DWS 预聚合表(80% 场景命中这里)
metric_id:        pay_gmv
preferred_table:  dws_order_day      # 已按天+渠道预聚合
physical_expr:    pay_gmv            # 直接 SUM(pay_gmv) 即可

# 回退 DWD 明细(如用户要按 SKU 下钻,DWS 没有 sku_id 时)
fallback_table:   dwd_order_info
fallback_where:   'pay_status="已支付"'
fallback_expr:    order_real_amount  # COUNT/SUM 用此字段

# 派生指标(如 avpu 客单价)
metric_type:      derived
formula:          'pay_gmv / pay_user_uv'
components:       '["pay_gmv","pay_user_uv"]'  # 编译器先算子指标再做除法

合理性分析

  • 首选/回退分离:80% 查询命中 DWS(快),20% 回退 DWD(全),兼顾性能与覆盖面
  • 派生指标公式化:avpu = pay_gmv / pay_user_uv,编译器自动拆成两个 CTE 再 JOIN,而非硬编码 SQL
  • version 字段:配合审计日志支持版本回滚,每次修改 version+1

3.2 sl_dimension --- 维度主表

核心表 · 8 个分析维度

设计思路 :每个维度一行,记录其 物理字段 (physical_column)、关联维度表 (join_table + join_key)和 展示字段(display_column)。支持退化维度(join_table 为空)和需要 Join 的维度。

具体例子

复制代码
# channel 维度:需要 Join dim_channel 拿渠道名
dim_id:           channel
physical_column:  channel_id       # 事实表里的字段
join_table:       dim_channel      # 要 Join 的维度表
join_key:         channel_id       # Join 键
display_column:   channel_name     # 展示给用户看的字段

# date 维度:退化维度,不需要 Join(dt 已在事实表里)
dim_id:           date
physical_column:  dt
join_table:       NULL             # 退化维度

合理性分析

  • 退化维度处理:date 维度不需要 Join,join_table 为空,编译器自动跳过 Join 步骤
  • 展示字段分离:事实表存 channel_id(整数),展示时 Join 拿 channel_name(中文),避免冗余存储
  • sort_order:控制维度在 UI 上的展示顺序

3.3 sl_dimension_hierarchy --- 维度层次表

核心表 · 支持下钻/上卷

设计思路:一个维度有多个层次(如商品维度有 cate1/cate2/cate3/brand),拆到独立表而非存 JSON 数组,便于按层次查询和扩展。

具体例子:goods(商品)维度的层次

dim_id hierarchy expr_sql 含义
goods cate1 cate1 一级类目下钻
goods cate2 cate2 二级类目下钻
goods cate3 cate3 三级类目下钻
goods brand brand_name 按品牌下钻

合理性 :如果将层次存在 sl_dimension 的 JSON 字段里,查询"哪些维度支持 cate1 层次"需要全表扫描 JSON。拆成独立表后,一条 SELECT dim_id FROM sl_dimension_hierarchy WHERE hierarchy='cate1' 即可,且新增层次不影响主表。


3.4 sl_synonym --- 同义词映射表

核心表 · 业务术语→指标/维度ID

设计思路 :业务方用不同名字称呼同一指标(GMV/成交额/交易额/总销售额),独立成表而非存 sl_metric 的 synonyms JSON 字段。关键:支持多人各自补充同义词

具体例子:pay_gmv 的同义词

term target_type target_id created_by
GMV metric pay_gmv xiaofei
成交额 metric pay_gmv xiaofei
交易额 metric pay_gmv lisi
销售金额 metric pay_gmv lisi
总销售额 metric pay_gmv wangwu

合理性 :如果存在 sl_metric.synonyms JSON 字段里,多人修改同一指标的同义词会产生 行锁冲突 (xiaofei 加"成交额"时,lisi 无法同时加"交易额")。独立成表后,每人各插一行,无锁冲突。同时 UNIQUE(term, target_type) 保证同一术语不重复映射到不同目标。


3.5 sl_metric_caliber --- 口径冲突仲裁表

口径表 · 同一指标多口径来源

设计思路 :同一个"GMV"在不同报表里有不同口径(下单GMV vs 成交GMV vs 老报表废弃口径),需要明确 主推口径(primary)废弃口径(deprecated),避免数据不一致。

具体例子:pay_gmv 的口径冲突

metric_id caliber_type table_name definition confirmed
pay_gmv primary dws_order_day.pay_gmv 已支付订单实付总金额 1
pay_gmv deprecated ads_old_report.gmv_amt 老报表ADS口径(重复计算优惠券) 0

合理性 :当业务方问"GMV是多少"时,编译器查 sl_metric_caliber WHERE metric_id='pay_gmv' AND caliber_type='primary' 确定使用 dws_order_day 口径。deprecated 口径标注 deprecate_until 截止日,到期后可清理。需 approver 审批确认(approved_by 字段),防止随意修改口径。


4. 辅助配置表设计详解(3 张表)

这 3 张表是 共享配置,不被某个指标独占,而是被多个指标引用。

4.1 sl_metric_dim_avail --- 指标可用维度白名单

关联表 · 指标×维度 多对多

设计思路 :不是所有指标都能按所有维度下钻(如 GMV 不能按 SKU 下钻,因为 DWS 层没有 sku_id)。用关联表记录 允许/显式禁用 的维度组合。

具体例子:pay_gmv 的可用维度

metric_id dim_id hierarchy allowed 说明
pay_gmv date NULL ✅ 1 支持按日期
pay_gmv channel NULL ✅ 1 支持按渠道
pay_gmv user level ✅ 1 支持按会员等级
pay_gmv sku NULL ❌ 0 显式禁用:DWS无sku_id

合理性 :编译器 S1 校验阶段会检查 SELECT allowed FROM sl_metric_dim_avail WHERE metric_id=? AND dim_id=?。若 allowed=0,直接返回友好错误"指标成交GMV暂不支持按SKU维度下钻",而非生成错误 SQL。比在 sl_metric 里存 available_dims JSON 数组更利于查询"某维度下有哪些可用指标"。


4.2 sl_join_path --- Join 路径图谱

关联表 · 事实表↔维度表 Join 规则

设计思路 :编译器不靠 LLM 拼 Join,而是查这张表 确定性添加 JOIN。以事实表为粒度(非指标粒度),因为同一张事实表的所有指标共用同一套 Join 路径。

具体例子:dws_order_day 的 Join 路径

fact_table dim_table join_type on_keys
dws_order_day dim_channel LEFT "channel_id"
dws_order_day dim_date LEFT "dt"
dwd_order_detail dwd_order_info INNER "order_id"
dwd_order_detail dim_goods LEFT "spu_id"

合理性 :以 而非 指标 为粒度,因为 pay_gmv、pay_user_uv、pay_order_cnt 三个指标都在 dws_order_day 上,只需查一次 Join 路径就能复用。避免在每个指标定义里重复写 Join 信息。on_keys 用 JSON 数组支持复合键(如 dws_ad_day 需 "channel_id","plan_id")。


4.3 sl_global_filter --- 全局口径过滤器

关联表 · 跨指标共享的过滤条件

设计思路 :有些过滤条件是全局通用的(排除测试数据、仅统计已支付),不应在每个指标里重复定义。指标通过 global_filters 字段引用这些共享过滤器的 name。

具体例子

复制代码
# 全局过滤器定义
- name: exclude_test
  sql_where: 'user_id not like "test_%" and order_id not like "TEST%"'

- name: pay_success
  sql_where: 'pay_status = "已支付"'

# 指标引用(YAML 配置)
pay_gmv:
  global_filters: ["exclude_test", "pay_success"]
  # 编译器 S6 阶段自动追加 WHERE 条件

合理性 :修改"排除测试数据"的规则只需改一处,所有引用该过滤器的指标自动生效。enabled 字段支持一键关闭某过滤器(如调试时暂时不排除测试数据)。


5. 向量检索表设计详解(2 张表)

方案A:MySQL JSON 列存 768 维向量 + Java 应用层 cosine 相似度计算。向量文档是 派生数据,由业务表变更事件异步生成。

5.1 sl_vector_doc --- 向量文档表

向量表 · 语义检索的索引数据

设计思路 :将指标/维度的语义信息拼接成文档文本,调用 Embedding 模型生成 768 维向量,存入 JSON 列。检索时全表读取 + Java 层 cosine 计算 + Top-K 排序。source_id 逻辑关联 metric_id/dim_id,不做物理外键

具体例子:pay_gmv 的向量文档

复制代码
{
  "doc_id": 1,
  "source_type": "metric",
  "source_id": "pay_gmv",
  "domain": "transaction",
  "doc_text": "成交GMV/GMV/成交额/交易额/总销售额: 已支付订单实付总金额。支持按date/channel/user.level/promotion下钻。口径=SUM(order_real_amount) where pay_status=已支付。",
  "embedding": "[0.012,-0.034,0.056,...]",
  "doc_version": 3
}

检索流程

合理性 :向量文档是 派生数据 ,源数据在 sl_metric/sl_synonym 等业务表里。用 source_id 逻辑关联而非物理外键,是因为向量文档允许短暂不一致(异步生成 + 死信重试)。(source_type, source_id) 唯一约束保证不重复,(source_type, domain) 索引支持按域预过滤。


5.2 sl_vector_doc_dead_letter --- 死信表

向量表 · 向量生成失败的补偿

设计思路:向量生成依赖外部 Embedding API(阿里云百炼 text-embedding-v2),可能失败。失败记录入死信表,定时任务重试,保证最终一致性。

具体例子

复制代码
{
  "source_type": "metric",
  "source_id": "pay_gmv",
  "error_msg": "connect timeout to dashscope.aliyuncs.com",
  "status": "PENDING",
  "retry_count": 0
}

# 定时任务每 10 秒扫描,最多重试 3 次
SELECT ... WHERE status='PENDING' FETCH FIRST 50 ROWS ONLY
成功 → 重建 sl_vector_doc → 死信标记 SUCCESS
失败 → retry_count+1 → 超过 3 次标记 FAILED

合理性 :向量生成是 非关键路径(不影响业务数据写入),用死信表解耦。即使 Embedding API 宕机,业务表正常写入,待 API 恢复后死信重试补齐向量,保证最终一致。


6. 协作治理表设计详解(3 张表)

多人维护语义层时,需要 角色权限审批流程审计回滚 三件套。这是 YAML 文件方案无法实现的核心能力。

6.1 sl_user_role --- 用户角色表

治理表 · 4 类角色 + 域隔离

设计思路 :4 类角色(viewer/editor/approver/admin),通过 domain_scope JSON 字段限制 editor 只能改自己负责的业务域,防止跨域误改。

具体例子

user_id username role domain_scope can_approve
xiaofei 消费分析师 editor "traffic","conversion" 0
lisi 交易数据BP editor "transaction","goods" 0
zhangsan 数据负责人 admin NULL 1
wangwu 业务方 viewer NULL 0

权限矩阵

角色 查看 新增/编辑 删除 审批口径 用户管理
viewer 只读
editor 编辑者 ✅ 仅自己域 仅自己新增的
approver 审批者 受限
admin 超管 ✅ 全部

合理性 :xiaofei 只能改 traffic/conversion 域的指标(如 UV、CTR),改不了 transaction 域的 pay_gmv。查询时 SQL 自动加 WHERE domain IN (用户域列表),从数据层隔离权限。


6.2 sl_change_request --- 审批状态机表

治理表 · DRAFT→PENDING→APPROVED→APPLIED

设计思路:口径变更必须走审批流,防止"未审批先上线"。editor 提交申请(DRAFT→PENDING),approver 审批(PENDING→APPROVED/REJECTED),apply_worker 独立进程写入目标表(APPROVED→APPLIED)。

具体例子:修改 pay_gmv 口径

复制代码
// editor xiaofei 提交变更申请
{
  "entity_type": "metric",
  "entity_id": "pay_gmv",
  "change_type": "UPDATE",
  "change_diff": "{\"preferred_table\":[\"dws_order_day\",\"ads_ec_overview_day\"]}",
  "change_note": "改用 ADS 层口径,支持按 user 下钻",
  "status": "PENDING",
  "submitter_id": "xiaofei"
}

// approver zhangsan 审批通过
{
  "status": "APPROVED",
  "approver_id": "zhangsan"
}

// apply_worker 10秒轮询,写入 sl_metric
{
  "status": "APPLIED",
  "apply_note": "已更新 sl_metric,缓存已失效"
}

状态机流转

复制代码
DRAFT 草稿 → PENDING 待审 → APPROVED 审批通过 → APPLIED 已生效
                ↓
          REJECTED 已拒绝(附 reject_reason)

合理性 :editor 不能直接 UPDATE sl_metric,必须走状态机。apply_worker 独立进程 执行写入,用 SELECT ... FOR UPDATE SKIP LOCKED 防并发冲突,version 乐观锁防两人同时审批。彻底解决"未审批先上线"+"后写覆盖" 两个高危问题。


6.3 sl_audit_log --- 审计日志表

治理表 · 变更前后快照,可回滚

设计思路:对 sl_metric、sl_metric_caliber、sl_synonym 三张核心表配置 AFTER INSERT/UPDATE/DELETE 触发器,自动将变更前后快照写入审计日志。支持任意版本回滚。

具体例子:回滚 pay_gmv 口径到上一版本

复制代码
-- 步骤1:查历史快照
SELECT log_id, old_value, new_value, created_at, change_note
FROM sl_audit_log
WHERE table_name='sl_metric' AND record_id='pay_gmv'
ORDER BY created_at DESC LIMIT 5;

-- 查到 log_id=123, old_value 里有 preferred_table='dws_order_day'

-- 步骤2:从 old_value 恢复字段
UPDATE sl_metric SET
  preferred_table = JSON_UNQUOTE(JSON_EXTRACT(
    (SELECT old_value FROM sl_audit_log WHERE log_id=123), '$.preferred_table')),
  version = version + 1,
  updated_by = CURRENT_USER()
WHERE metric_id = 'pay_gmv';

合理性 :old_value/new_value 用 JSON 存完整快照,不需要额外建历史表。回滚操作就是从 old_value 提取字段值 UPDATE 回去,version+1。可回溯任一版本的任一字段变更,满足数据治理审计要求。


7. 配置拆分映射:一条 YAML 如何拆到多表

YAML 中的「一条指标配置」并非存为单行 JSON,而是按职责拆分到多张表。以 pay_gmv 为例展示完整映射:

拆分对照表

YAML 配置项 存入表 关系 拆分理由
name_cn, domain, type, aggregation sl_metric 1:1 主表基础属性
preferred_table, physical_expr sl_metric 1:1 物理映射(首选路径)
fallback_table/where/expr sl_metric 1:1 回退路径
formula, components sl_metric 1:1 派生公式
synonyms sl_synonym 1:N 多人无锁补充
calibers sl_metric_caliber 1:N primary + deprecated
available_dims sl_metric_dim_avail N:M 双向查询
join_graph sl_join_path 共享 以表为粒度复用
global_filters sl_global_filter 共享 改一处全生效
---(派生) sl_vector_doc 1:1 事件异步生成

设计要点 :编译器通过 metric_id 反向 JOIN 这 6 张表,重新组装出完整定义(5 分钟缓存)。拆分的好处是 职责单一、避免锁冲突、支持多人并行维护。如果存成单行 JSON,一人修改同义词会锁住整行,其他人无法同时修改口径。


8. 设计原则总结

语义层 12 张表的设计遵循以下核心原则:

# 原则 说明 体现表
1 职责分离 每张表只管一件事:sl_metric 管物理映射、sl_synonym 管同义词、sl_metric_caliber 管口径 全部
2 共享配置独立 sl_join_path 和 sl_global_filter 跨指标共享,独立成表,修改一处全生效 sl_join_path, sl_global_filter
3 多对多用关联表 指标×维度是 M:N,用 sl_metric_dim_avail 而非 JSON 数组,便于双向查询和权限控制 sl_metric_dim_avail
4 派生数据解耦 sl_vector_doc 是派生数据,source_id 逻辑关联而非物理外键,允许异步生成和短暂不一致 sl_vector_doc
5 审批状态机 sl_change_request 保证口径变更必须审批,apply_worker 独立进程写入,防"未审批先上线" sl_change_request
6 审计可回滚 sl_audit_log 存 old_value/new_value JSON 快照,从快照恢复字段即可回滚到任意版本 sl_audit_log
7 域隔离权限 sl_user_role 的 domain_scope 限制 editor 只改自己域,SQL 层自动加 WHERE 过滤 sl_user_role
8 物理隔离 元数据库 chatbi_meta 与业务数仓物理隔离,sl_ 前缀统一命名,避免误操作影响业务数据 全部

核心设计哲学

语义层的本质是 结构化配置 + 确定性编译引擎。LLM 只负责意图理解(输出 DSL),SQL 编译全部由语义层引擎确定性处理。表结构设计围绕"多人协作维护 + 审批审计 + 版本回滚"展开,这是 YAML 文件方案无法实现的核心能力。

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    ChatBI 语义层设计哲学                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   LLM(意图理解)  ←──分离──→  语义层引擎(SQL 编译)           │
│        │                            │                        │
│   输出 DSL JSON              查表 + 确定性编译                 │
│   不碰 SQL                   Join/口径/字段映射全确定           │
│                                                             │
│   表结构设计围绕三大能力:                                     │
│   ┌─────────────┬─────────────┬─────────────┐               │
│   │ 多人协作维护  │  审批审计    │  版本回滚    │               │
│   │             │             │             │               │
│   │ • 角色权限   │ • 状态机     │ • 触发器快照 │               │
│   │ • 域隔离     │ • 乐观锁     │ • JSON 快照  │               │
│   │ • 行级锁     │ • 独立worker │ • version+1 │               │
│   └─────────────┴─────────────┴─────────────┘               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

附:12 张表速查表

# 表名 类型 核心职责 关键字段
1 sl_dimension 核心 维度主表(8项) dim_id, physical_column, join_table
2 sl_dimension_hierarchy 核心 维度层次(下钻/上卷) dim_id, hierarchy, expr_sql
3 sl_metric 核心 指标主表(47+项) metric_id, preferred_table, fallback_*
4 sl_synonym 核心 同义词映射 term, target_type, target_id
5 sl_metric_caliber 口径 口径冲突仲裁 metric_id, caliber_type, definition
6 sl_metric_dim_avail 关联 指标可用维度白名单 metric_id, dim_id, allowed
7 sl_join_path 关联 Join 路径图谱 fact_table, dim_table, on_keys
8 sl_global_filter 关联 全局口径过滤器 name, sql_where, enabled
9 sl_vector_doc 向量 向量文档索引 source_type, source_id, embedding
10 sl_vector_doc_dead_letter 向量 向量生成失败补偿 source_id, status, retry_count
11 sl_user_role 治理 用户角色权限 user_id, role, domain_scope
12 sl_change_request 治理 审批状态机 entity_id, status, submitter_id
13 sl_audit_log 治理 审计日志(可回滚) table_name, old_value, new_value

注:含 P0-R3 修复新增的 sl_change_request,实际为 13 张表(原始设计 10 张 + P0 修复 3 张)。

相关推荐
润乾软件8 小时前
如何给已有报表系统增加 AI 语言查询能力——AI 赋能数据分析技术探讨与实践
人工智能·chatbi
极昆仑智慧8 天前
NL2SQL vs NL2语义层:智能问数两条技术路线的深度对比
nl2sql·chatbi·智能问数·data agent·数据决策
极昆仑智慧9 天前
智能问数技术路线深度分析:纯NL2SQL退场后,五种架构路线谁主沉浮?
agent·chatbi·智能问数·data agent
damo王3 个月前
极简Agent plan指南
大模型·agent·token·向量模型·open claw·coding plan·agent plan
思迈特Smartbi3 个月前
2026 挑战杯揭榜挂帅启幕 思迈特软件发布AI数据创新重磅命题
ai·chatbi·智能问数
千桐科技4 个月前
献礼劳动节|qData 数据中台开源版 v1.3.0 正式发布:智能问数(ChatBI)来了!
开源软件·text2sql·数据中台·chatbi·问数·qdata·千桐科技
亿问DataAgent4 个月前
从 ChatBI 到 Data Agent:企业数据分析产品走过的弯路和新方向
chatbi·data agent
千桐科技4 个月前
qData 数据中台专业版 v2.0.0 正式发布:ChatBI 上线,数据建模与安全治理能力全面升级
数据治理·数据建模·数据中台·chatbi·qdata·千桐科技·高质量数据集
治数有道5 个月前
【ChatBI终结篇】向实而生:重构ChatBI的价值坐标与落地路径
数据治理·数据架构·chatbi·智能分析·智能问数·ai实践