语义模型
SelectDB MCP 服务(基于 Apache Doris MCP Server)让 AI Agent 通过 MCP 协议 直接查询 SelectDB Cloud 中的数据。它提供两条查询路径:
- 语义层:预先用 YAML 定义业务指标,Agent 用自然语言即可查询,系统自动生成正确 SQL(推荐)。
- 裸 SQL:在没有匹配指标时,回退到只读 SQL 查询。
本页介绍语义模型的编写与管理;面向终端用户的接入与查询,请看 用户指南。
什么是语义模型?
语义模型是对数据库表的业务描述。你告诉系统:
-
这张表的主键是什么(实体)
-
有哪些字段可以用来分组(维度)
-
哪些字段需要聚合计算(度量)
定义好之后,用户就可以用自然语言级别的查询("给我看每月订单总额"),系统自动生成正确的 SQL。
YAML 是说明书,MetricFlow 是翻译官,把"每月订单总额"翻译成 SELECT DATE_TRUNC('month', order_date), SUM(amount) FROM orders GROUP BY 1。
快速上手
步骤 1:写一个 YAML 文件
每个 YAML 文件描述一张表。把它放到 models/ 目录下:
# models/orders.yaml
---
semantic_model:
name: orders # 模型名称
db_table: dw.orders # 对应的 SelectDB 表
defaults:
agg_time_dimension: order_date # 默认时间维度
entities:
- name: order_id # 主键
type: primary
expr: order_id
dimensions:
- name: order_date # 时间维度
type: time
type_params:
time_granularity: day
- name: channel # 分类维度
type: categorical
measures:
- name: total_amount # 订单总额
agg: sum
expr: amount
- name: order_count # 订单数量
agg: count
expr: order_id
步骤 2:定义时间配置
需要一个 project.yaml 指定日历表(用于计算同比、环比等时间指标):
# models/project.yaml
---
time_config:
calendar:
- table: dw.dim_date
column: date_id
grain: day
步骤 3:验证 & 提交
-
点击 Validate 检查模型是否正确
-
确认无误后点击 Commit 使模型生效
-
使用
list_metrics查看可用指标,用query_metric查询数据
表是否存在、列名是否正确、实体和维度是否完整。如果有问题,会给出具体的错误信息和位置。
语义模型结构
一个完整的 semantic_model 包含以下字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | ✅ | 全局唯一的模型名称。小写字母开头,可含数字和下划线 |
db_table | 字符串 | ✅ | SelectDB 物理表。库名.表名 格式,如 dw.orders |
defaults | 对象 | ✅ | 默认配置。目前必须包含 agg_time_dimension |
entities | 列表 | ✅ | 表的实体定义。至少包含一个 type: primary 的主实体 |
dimensions | 列表 | ✅ | 可用于分组和筛选的维度 |
measures | 列表 | 推荐 | 聚合计算定义。定义后自动成为可查询的指标 |
description | 字符串 | 可选 | 模型的文字描述 |
label | 字符串 | 可选 | 显示名称 |
primary_entity | 字符串 | 条件 | 当 entities 中没有 type: primary 时,必须指定 |
所有名称(模型名、实体名、维度名、度量名)必须:
-
以小写字母开头
-
只能包含小写字母、数字和下划线
-
不能包含两个连续下划线
__ -
至少 2 个字符
✅ order_id、total_amount、user_count
❌ OrderID、order__id、a
实体 — 表的「身份证」
实体定义了行与行之间的唯一性和关联关系。每张表都必须有一个主实体。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | ✅ | 实体名称,在本模型内唯一 |
type | 枚举 | ✅ | 实体类型(见下表) |
expr | 字符串 | 推荐 | 对应数据库列名。可省略(默认使用 name);也支持 SQL 表达式 |
description | 字符串 | 可选 | 文字描述 |
label | 字符串 | 可选 | 显示名称 |
实体类型
| 类型 | 含义 | 使用场景 |
|---|---|---|
primary | 主键。每行唯一,覆盖全部记录 | 表的 ID 列。每张表必须有且仅有一个 primary 实体 |
foreign | 外键。可以有重复、空值 | 关联到其他表的列,如 customer_id、product_id |
unique | 唯一键。每行唯一,可能不覆盖全部记录 | 例如邮箱、身份证号等唯一标识 |
natural | 自然键。现实世界中的唯一标识 | 如商品条形码、员工工号等 |
示例:订单表的实体定义
entities:
- name: order_id # 主键:每笔订单的唯一 ID
type: primary
expr: order_id
- name: customer # 外键:关联到用户表
type: foreign
expr: user_id
- name: order_ref # 外键 + SQL 表达式
type: foreign
expr: substring(trace_id FROM 1 FOR 8)
如果表中没有 type: primary 的实体,可以使用 primary_entity: entity_name 在模型顶层指定。
维度 — 数据的分组方式
维度定义了如何对数据进行分组和筛选。有两种类型:时间维度和分类维度。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | ✅ | 维度名称 |
type | 枚举 | ✅ | time(时间)或 categorical(分类) |
type_params | 对象 | time 时必填 | 时间粒度配置(见下方) |
expr | 字符串 | 推荐 | 对应列名或 SQL 表达式 |
is_partition | 布尔 | 可选 | 是否为分区列。默认 false |
description | 字符串 | 可选 | 文字描述 |
label | 字符串 | 可选 | 显示名称 |
时间粒度 (time_granularity)
| 粒度 | 含义 | 示例 |
|---|---|---|
day | 按天 | 2025-01-15 |
week | 按周 | 2025-W03 |
month | 按月 | 2025-01 |
quarter | 按季度 | 2025-Q1 |
year | 按年 | 2025 |
hour | 按小时 | 2025-01-15 14:00 |
minute | 按分钟 | 2025-01-15 14:30 |
示例
dimensions:
- name: order_date # 订单日期(按天)
type: time
type_params:
time_granularity: day
expr: order_date
- name: order_month # 订单月份(按月)
type: time
type_params:
time_granularity: month
expr: order_date # 同一列,不同粒度
- name: channel # 渠道(分类)
type: categorical
expr: channel
- name: status_label # 带 SQL 表达式
type: categorical
expr: concat(status, '_', channel)
度量 — 聚合计算
度量定义了对数据列的聚合运算。每个度量会自动生成一个同名的查询指标。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | ✅ | 度量名称(同时也成为指标名) |
agg | 枚举 | ✅ | 聚合类型(见下表) |
expr | 字符串 | 推荐 | 参与计算的列名或 SQL 表达式 |
description | 字符串 | 可选 | 指标说明 |
label | 字符串 | 可选 | 显示名称 |
create_metric | 布尔 | 可选 | 设为 false 则不自动生成指标。默认 true |
agg_time_dimension | 字符串 | 可选 | 覆盖模型默认的时间维度 |
聚合类型一览
| 类型 | 含义 | 典型用法 |
|---|---|---|
sum | 求和 | 总金额、总次数 |
count | 计数 | 订单数、用户数 |
count_distinct | 去重计数 | 独立用户数、活跃设备数 |
average | 平均值 | 平均金额、平均时长 |
min | 最小值 | 最低价格、最早时间 |
max | 最大值 | 最高价格、最后时间 |
median | 中位数 | 订单金额中位数 |
percentile | 百分位数 | P99 延迟、P95 金额(需配 agg_params.percentile) |
sum_boolean | 布尔求和 | 转化次数、达标次数 |
示例
measures:
- name: total_amount # 订单总额
description: "所有订单的金额总和"
agg: sum
expr: amount
label: "订单总额"
- name: order_count # 订单数量
agg: count
expr: order_id
- name: unique_customers # 独立客户数
description: "下单的独立客户数量"
agg: count_distinct
expr: user_id
- name: p99_amount # P99 订单金额
agg: percentile
expr: amount
agg_params:
percentile: 0.99
- name: internal_counter # 不暴露为指标
agg: sum
expr: raw_value
create_metric: false
完整示例
以下是一个电商场景的完整语义模型定义——订单表:
# models/orders.yaml — 订单事实表
---
semantic_model:
name: orders
description: "电商订单事实表,每行代表一笔订单"
db_table: dw.orders
defaults:
agg_time_dimension: order_date
# ── 实体 ──
entities:
- name: order_id
description: "订单主键"
type: primary
expr: order_id
- name: customer
description: "关联用户"
type: foreign
expr: user_id
- name: product
description: "关联商品"
type: foreign
expr: product_id
# ── 维度 ──
dimensions:
- name: order_date
description: "下单日期(按天)"
type: time
type_params:
time_granularity: day
expr: order_date
- name: order_month
description: "下单月份"
type: time
type_params:
time_granularity: month
expr: order_date
- name: channel
description: "下单渠道"
type: categorical
expr: channel
- name: status
description: "订单状态"
type: categorical
expr: status
# ── 度量 ──
measures:
- name: total_amount
description: "订单总金额"
label: "总金额"
agg: sum
expr: amount
- name: order_count
description: "订单总数"
label: "订单数"
agg: count
expr: order_id
- name: unique_customers
description: "下单的独立客户数"
label: "独立客户"
agg: count_distinct
expr: user_id
- name: avg_amount
description: "平均订单金额"
label: "客单价"
agg: average
expr: amount
配套:用户表和商品表
# models/users.yaml — 用户维表
---
semantic_model:
name: users
description: "用户信息维表"
db_table: dw.users
defaults:
agg_time_dimension: register_date
entities:
- name: user_id
type: primary
expr: user_id
dimensions:
- name: register_date
type: time
type_params:
time_granularity: day
- name: city
type: categorical
- name: level
type: categorical
measures:
- name: user_count
agg: count
expr: user_id
# models/products.yaml — 商品维表(纯维度表,无度量,故省略 defaults)
---
semantic_model:
name: products
description: "商品信息维表"
db_table: dw.products
entities:
- name: product
type: primary
expr: product_id
dimensions:
- name: product_name
type: categorical
expr: name
- name: category
type: categorical
expr: category
- name: brand
type: categorical
expr: brand
高级指标定义
除了从 measures 自动生成的简单指标外,你还可以用 metric: 文档定义高级指标。高级指标通过组合已有的度量或指标,实现更复杂的计算逻辑。支持四种类型:
| 类型 | 含义 | 典型场景 |
|---|---|---|
ratio | 比率指标:分子 ÷ 分母 | 转化率、利润率、占比 |
derived | 派生指标:对已有指标做表达式计算 | 环比增长、同比变化、加权计算 |
cumulative | 累计指标:按时间窗口累加 | 近 7 日销量、月累计注册数 |
conversion | 转化指标:两个事件的转化分析 | 下单转化率、注册转化率 |
比率指标
计算两个指标的比值,如人均下单数 = 订单数 / 用户数。
# models/orders_per_user.yaml
---
metric:
name: orders_per_user
description: "人均下单数:订单数 / 用户数"
type: ratio
type_params:
numerator: order_count # 分子:引用已有指标
denominator: user_count # 分母:引用已有指标
分子和分母都必须是已经定义的指标名(可以是简单指标,也可以是其他高级指标)。
派生指标
基于一个或多个已有指标通过表达式计算。最常用于环比、同比。
# models/revenue_growth.yaml — 环比增长
---
metric:
name: revenue_growth
description: "收入环比增长率"
type: derived
type_params:
expr: (current_revenue - prev_revenue) / prev_revenue
metrics:
- name: total_amount # 当期收入
alias: current_revenue
- name: total_amount # 上期收入(同名指标,加 offset)
alias: prev_revenue
offset_window: 1 month # 向前偏移一个时间窗口
| 参数 | 说明 |
|---|---|
expr | 计算表达式,用 alias 引用各输入指标 |
metrics | 输入指标列表 |
name | 引用的指标名 |
alias | 在 expr 中使用的别名 |
offset_window | 时间偏移量,如 1 month、7 days、1 year |
offset_to_grain | 偏移到的粒度,如 month、year |
累计指标
对某个指标按时间窗口做累加,如"近 7 天销售额"。
# models/weekly_sales.yaml
---
metric:
name: weekly_sales
description: "近 7 天销售额"
type: cumulative
type_params:
measure:
name: total_amount
window: 7 days # 时间窗口:过去 7 天
| 参数 | 说明 |
|---|---|
measure | 引用的度量名(来自某个 semantic_model 的 measures) |
window | 时间窗口格式:数字 粒度,如 28 days、4 weeks、3 months |
grain_to_date | 可选。累计到指定粒度,如 month(月初至今)、year(年初至今) |
转化指标
衡量用户从一个事件(base)转化到另一个事件(conversion)的比率。常用于分析用户行为漏斗。
# models/order_conversion.yaml
---
metric:
name: register_to_order_conversion
description: "从注册到下单的转化率"
type: conversion
type_params:
conversion_type_params:
base_measure: # 基础事件(注册)
name: user_count
conversion_measure: # 转化事件(下单)
name: order_count
entity: user # 关联实体:按哪个维度计算转化
calculation: conversion_rate # 计算方式
| 参数 | 说明 |
|---|---|
base_measure | 基础事件的度量名 |
conversion_measure | 转化事件的度量名 |
entity | 关联实体名,按此维度计算转化(通常是 user 或 session) |
calculation | conversion_rate(转化率)或 conversions(绝对转化次数) |
window | 可选。转化窗口,如 7 days |
常见场景
场景 1:同一列作为不同粒度的维度
一张日期列可以同时定义 day、week、month 三个维度:
dimensions:
- name: order_date
type: time
type_params:
time_granularity: day
expr: order_date
- name: order_week
type: time
type_params:
time_granularity: week
expr: order_date # 同一列!
- name: order_month
type: time
type_params:
time_granularity: month
expr: order_date # 同一列!
场景 2:使用 SQL 表达式
当列名不够直观,或者需要计算字段时,可以用 SQL 表达式:
dimensions:
- name: user_label
type: categorical
expr: concat(level, '_', city) # 拼接字段
entities:
- name: user_short_id
type: foreign
expr: substring(trace_id FROM 1 FOR 8) # 截取
measures:
- name: net_amount
agg: sum
expr: coalesce(amount, 0) - coalesce(discount, 0) # 计算字段
substring、concat、coalesce、cast 等标准 SQL 函数。这些表达式在物理校验时会被自动识别并跳过列名校验。
场景 3:分区表
如果表有分区列,标记 is_partition: true:
dimensions:
- name: ds
type: time
type_params:
time_granularity: day
is_partition: true # 标记为分区列
expr: ds
场景 4:隐藏内部度量
有些度量只想作为中间计算使用,不想对终端用户暴露,设置 create_metric: false:
measures:
- name: total_amount # ✅ 公开指标
agg: sum
expr: amount
- name: _raw_count # ❌ 不暴露
agg: count
expr: order_id
create_metric: false
进阶功能
过滤器
可以在度量定义或指标定义中添加 SQL 过滤条件。过滤器会在聚合计算前应用。
-
measures的filter:字段 — 限定单个度量的数据范围 -
metric:的filter:字段 — 限定整个指标的数据范围 -
measure的input_measures[].filter:— 限定高级指标中引用的度量
# 示例 1:度量级过滤 — 只计算"已完成"状态的订单总额
measures:
- name: completed_amount
description: "已完成订单的金额总和"
agg: sum
expr: amount
filter: {{ render_dimension_template('status') }} = 'completed'
# 示例 2:指标级过滤
---
metric:
name: premium_user_orders
description: "高级用户订单数"
type: simple
type_params:
measure:
name: order_count
filter: {{ render_dimension_template('user_level') }} = 'premium'
-
在 YAML 中用
{{ Dimension('qualified_name') }}或{{ render_dimension_template('dimension_name') }}引用维度 -
引用实体用
{{ Entity('entity_name') }}或{{ render_entity_template('entity_name') }} -
后面跟普通 SQL 条件,如
= 'value'、IN ('a', 'b') -
query_metric的where参数可直接写裸 SQL(如"channel = 'APP'"),编译器会自动转换为 MetricFlow 模板语法
预定义查询
把常用的指标 + 分组 + 筛选组合保存为一个查询模板,用户可以直接调用。
# models/weekly_report.yaml
---
saved_query:
name: weekly_revenue_report
description: "周度收入报告:按渠道分组的订单总额和订单数"
label: "周度收入报告"
query_params:
metrics:
- total_amount
- order_count
group_by:
- order_id__order_week # 按周分组
- order_id__channel # 按渠道分组
order_by:
- "-order_id__order_week" # 按周倒序
limit: 52
| 字段 | 说明 |
|---|---|
metrics | 要查询的指标名列表 |
group_by | 分组维度。格式:实体名__维度名(双下划线连接) |
order_by | 排序,- 前缀表示降序 |
where | 筛选条件(同过滤器语法) |
limit | 最大返回行数 |
实体名__维度名(双下划线)。例如 order_id__order_date 表示 orders 表中的 order_date 维度。
非加性度量 & 缓慢变化维度 (SCD Type II)
有些度量不能简单求和(如库存量、账户余额),需要在特定维度下取快照值。
# 非加性度量:库存量(取月末快照)
measures:
- name: monthly_inventory
description: "月末库存量"
agg: sum
expr: inventory_count
non_additive_dimension:
name: snapshot_date # 非加性维度
window_choice: max # 取时间窗口内的最大值
window_groupings:
- product # 按产品分组取快照
SCD Type II(缓慢变化维度):当维度表有生效时间范围时,标记开始和结束维度:
# 维度表中标记 SCD Type II
dimensions:
- name: valid_from
description: "生效开始时间"
type: time
type_params:
time_granularity: day
validity_params:
is_start: true # 标记为开始时间
- name: valid_to
description: "生效结束时间"
type: time
type_params:
time_granularity: day
validity_params:
is_end: true # 标记为结束时间
空值填充 & 时间轴对齐
# 把 NULL 改为 0(适用于 count 类指标在无数据日期显示 0)
metric:
name: daily_orders
type: simple
type_params:
measure:
name: order_count
fill_nulls_with: 0 # 无数据日期显示 0
join_to_timespine: true # 对齐到时间轴(补全无数据日期)
| 参数 | 说明 |
|---|---|
fill_nulls_with | 将聚合结果中的 NULL 替换为指定值(通常是 0) |
join_to_timespine | 将指标结果与时间轴表做连接,确保每一天/月/年都有数据行(没有数据的日期补 NULL 或 0) |
原生表引用格式
除了 db_table 简写,也支持 MetricFlow 原生的 node_relation 格式,支持三段式目录引用:
# 两段式:库名.表名
node_relation:
schema_name: dw
alias: orders
# 三段式:目录.库名.表名
node_relation:
database: catalog
schema_name: dw
alias: orders
# db_table 同样支持三段式
db_table: catalog.dw.orders
累计指标的聚合方式
累计指标支持三种 period_agg 模式,控制在时间窗口内的聚合行为:
# last:取窗口内最后一天的值(默认行为)
metric:
name: end_of_week_inventory
type: cumulative
type_params:
cumulative_type_params:
measure:
name: inventory_count
window: 7 days
period_agg: last # 取最后一天的值
# average:窗口内日均值
period_agg: average # 7 天平均值
# first:窗口内第一天的值
period_agg: first # 取第一天的值
period_agg | 说明 |
|---|---|
last | 窗口内最后一天的值(默认) |
average | 窗口内每日均值 |
first | 窗口内第一天的值 |
转化指标的固定属性
在转化指标中,可以用 constant_properties 指定在不同事件间保持不变的属性:
# 按"流量来源"维度分析转化,要求流量来源在两次事件间不变
metric:
name: register_to_order_by_source
type: conversion
type_params:
conversion_type_params:
base_measure:
name: user_count
conversion_measure:
name: order_count
entity: user
constant_properties:
- base_property: order_id__channel # 基础事件的属性
conversion_property: order_id__channel # 转化事件的属性(必须相同)
指标时间粒度 & 偏移粒度
**指标级 time_granularity:**可以在指标定义中直接指定时间粒度(覆盖查询时的默认行为):
metric:
name: monthly_revenue
type: simple
time_granularity: month # 指定此指标默认按月聚合
type_params:
measure:
name: total_amount
**offset_to_grain:**在 derived 指标中,将偏移对齐到指定粒度(而非默认的天级别):
metric:
name: yoy_growth
type: derived
type_params:
expr: (current - prev) / prev
metrics:
- name: total_amount
alias: current
- name: total_amount
alias: prev
offset_window: 1 year
offset_to_grain: month # 偏移到月粒度(而非天)
实体角色
同一个实体(如 user_id)在表中可能有多个角色。比如订单表里的 user_id 既是"买家"也是"推荐人"。用 role 区分:
entities:
- name: user
type: foreign
expr: buyer_id
role: buyer # 此 user 实体的角色是"买家"
- name: user
type: foreign
expr: referrer_id
role: referrer # 此 user 实体的角色是"推荐人"
不指定 role 时,默认角色等同于实体名。
最佳实践
- 一张表一个文件。
文件命名用
表名.yaml,结构清晰易维护。
✅ orders.yaml、users.yaml、products.yaml
-
先定义实体,再写维度,最后加度量。 实体是语义模型的骨架,维度是分组能力,度量是查询目标。按这个顺序写,不容易遗漏。
-
给每个度量写
description。 终端用户看到的是指标名称和描述。好的描述让他们不需要看 YAML 就能理解指标含义。 -
同一张时间列可以定义多个粒度维度。 比如
order_date列可以同时有 day、week、month、quarter、year 五种粒度。 -
外键实体用有意义的业务名称。 实体名
customer比user_id更好理解——它代表"客户"这个概念,而不只是"user_id 列"。 -
先验证,再提交。 每次修改 YAML 后,点击 Validate 检查。系统会验证表存在性、列名正确性、以及命名规范。看到绿色通过再提交。
-
度量名在同一个模型内必须唯一。 不同模型的度量名可以重复,但那会导致指标覆盖——所以最好全局唯一。
常见错误 & 排查
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
Table xxx does not exist | db_table 指向的表不存在 | 检查表名拼写,确认库名和表名都正确 |
measure references missing column: dw.orders.xxx | 度量中的列名在表中不存在 | 检查 expr 是否和实际列名一致(注意大小写) |
entity references missing column | 实体中的 expr 列不存在 | 同上;如果是 SQL 表达式(如 substring(...) ),确认函数名和列名正确 |
Duplicate measure 'xxx' defined in 2 models | 两个模型定义了同名的度量 | 重命名其中一个度量,确保全局唯一 |
Duplicate semantic_model name | 两个文件用了相同的模型名 | 重命名其中一个模型 |
Did not find exactly one project configuration | 缺少 project.yaml | 创建包含 time_config 的 project.yaml |
'xxx' does not match '^(?!.*__)...$' | 命名不符合规范 | 使用小写字母开头,去除双下划线,加长到至少 2 个字符 |
No staging changes to validate | 没有待验证的变更 | 先上传/修改 YAML 文件,再点 Validate |
-
确认 SelectDB 中表存在:
DESCRIBE dw.orders -
确认列名和大小写完全一致
-
检查 YAML 缩进是否正确(必须用空格,不能用 Tab)
-
确认所有名称符合小写字母开头的规范
SelectDB MCP 服务中的语义模型管理
工作区
工作区是语义模型的隔离容器。每个工作区有独立的:
-
模型文件(YAML 定义)
-
指标列表(
list_metrics只能看到本区的指标) -
查询引擎实例(MetricFlow 编译器)
-
SelectDB 连接池
工作区 A 的指标完全不可见于工作区 B。适合按团队、项目或环境划分。
工作区名必须以英文字母开头,只能包含字母、数字和下划线。例如 marketing、finance_v2。
工作区的三种状态
调用 check_service_health 可以查看每个工作区的运行状态。一个工作区始终处于以下三种状态之一:
| 状态 | 图标 | 含义 | 触发条件 |
|---|---|---|---|
| healthy | 🟢 | 正常运行。语义模型已成功加载,指标可查询。 | YAML 文件已 Commit,bootstrap 解析成功,MetricFlow 引擎就绪。 |
| no_models | ⚪ | 空工作区。尚未上传任何 YAML 文件。 | 新创建的工作区,或所有文件已被删除。 |
| not_ready | 🔴 | 加载失败。文件存在但无法编译为语义清单。 | YAML 语法错误、表不存在、缺少 project.yaml、MetricFlow 校验失败等。检查 validate 的错误信息。 |
┌──────────────┐ 上传 YAML ┌──────────────┐ Commit 成功 ┌──────────────┐
│ no_models │ ──────────────→ │ not_ready │ ───────────────→ │ healthy │
│ (空工作区) │ │ (待修复) │ │ (正常运行) │
└──────────────┘ └──────────────┘ └──────────────┘
↑ │
│ 上传错误 YAML │
└──────────────────────────────────┘
每次重载后系统会记录版本号、指标数量、加载是否成功。可通过 check_service_health 返回的 metric_count 确认当前加载的指标数。
两层存储:Active Store 与 Staging Store
每个工作区内部有两层存储:
| 存储层 | 表名 | 用途 |
|---|---|---|
| Active Store | ||
| (已生效) | active_store_{workspace} | 已提交并加载的模型。查询引擎使用此版本。文件只读。 |
| Staging Store | ||
| (待提交) | staging_store_{workspace} | 用户上传或编辑的待定变更。可增/改/删。验证通过后提交进入 Active。 |
Staging Store Active Store
┌─────────────┐ commit ┌─────────────┐
│ edit / add │ ─────────→ │ query use │
│ validate ✓ │ │ read-only │
│ discard ✗ │ │ │
└─────────────┘ └─────────────┘
↑ ↑
用户修改 查询引擎使用
更新流程
语义模型的更新遵循编辑 → 验证 → 提交 → 自动重载的流程:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 上传/编辑 YAML | 文件进入 Staging Store(不影响正在运行的查询) |
| 2 | Validate | 必须执行!系统自动检查:表是否存在、列名是否正确、YAML 语法、MetricFlow 语义规则、命名规范、跨模型重复名 |
| 3 | Commit | Staging → Active。系统自动触发引擎重载(无需手动重启) |
| 4 | ⟳ 自动重载 | 后台线程解析新模型、编译 JSON 清单、更新函数路由。通常 2-5 秒完成 |
未经过 Validate 的 Staging 会被 Commit 拒绝,返回 "Staging must be validated before commit"。
权限模型
只有 admin 用户可以管理语义模型:
| 操作 | admin | 普通用户 |
|---|---|---|
| 查看语义模型 | ✅ | ✅ |
查询指标 (query_metric) | ✅ | ✅ |
列出指标 (list_metrics) | ✅ | ✅ |
| 上传/编辑/删除 YAML | ✅ | ❌ |
| Validate / Commit / Discard | ✅ | ❌ |
| 创建/删除工作区 | ✅ | ❌ |
执行 SQL (execute_query) | ✅ | ✅(受限) |
默认示例工作区
首次启动时,系统会自动创建 example 工作区,包含完整的种子数据:
| 内容 | 说明 |
|---|---|
dw.orders | 订单表(12 条模拟数据),含 order_id、user_id、product_id、amount、channel、status、order_date |
dw.users | 用户表(5 条模拟数据),含 user_id、name、city、level、register_date |
dw.products | 商品表(5 条模拟数据),含 product_id、name、category、brand、price |
dw.dim_date | 日期维度表(日历表),用于时间轴对齐和累计计算 |
| 语义模型 YAML | orders.yaml、users.yaml、products.yaml + project.yaml(自动注入 active_store_example) |
示例指标包括:total_amount(订单总额)、order_count(订单数)、avg_amount(客单价)、unique_users(独立用户数)来自 orders 模型;user_count(用户数)来自 users 模型。
可在配置中设置 seed_example: false 禁用自动种子。
WebUI 管理界面
WebUI(浏览器界面)
访问 http://<服务器地址>:<端口>/mcp/web,登录后可使用图形化管理界面:
| 页面 | URL | 功能 |
|---|---|---|
| 登录 | /mcp/web/login | 用 SelectDB 账号登录(admin:admin 为默认管理员) |
| 首页 | /mcp/web | 工作区列表,可切换、创建、删除工作区 |
| 模型管理 | /mcp/web/models?workspace=xxx | 查看 Active 和 Staging 文件列表、编辑、上传、删除 |
| 文件编辑 | /mcp/web/{filename}?workspace=xxx | 在线编辑 YAML 文件,支持语法高亮 |
关键操作按钮(仅 admin):
| 按钮 | 作用 |
|---|---|
| Validate | 验证 Staging 中的所有变更(物理校验 + 语义校验 + 重复检测) |
| Commit | 将验证通过的 Staging 提交到 Active,触发引擎重载 |
| Discard | 丢弃 Staging 中的所有变更,回到 Active 版本 |
| Reload | 手动触发引擎重载(通常 Commit 后自动执行) |
| + New | 新建 YAML 文件 |
下一步
- 用户指南 — 把 SelectDB MCP 服务接入 AI Agent,用自然语言查询数据。