MCP Server 使用说明

更新时间:
复制 MD 格式

MCP Server 基于 MetricFlow 引擎,在数据库表之上构建语义模型层。语义模型通过 YAML 文件定义实体、维度和度量,将数据库表映射为业务概念,使 AI 客户端能够理解业务语义并自动生成正确的 SQL 查询。本文介绍语义模型的定义规范、高级指标、工作空间管理和常见错误排查。

语义模型概述

语义模型是对数据库表的业务描述。通过 YAML 文件定义以下信息:

  • 表的主键是什么(实体)。

  • 有哪些字段可以用来分组(维度)。

  • 哪些字段需要聚合计算(度量)。

定义好语义模型后,用户可以使用自然语言查询(例如"给我看每月订单总额"),系统自动生成正确的 SQL 语句。

快速上手

步骤 1:编写 YAML 文件

每个 YAML 文件描述一张表,放到 models/ 目录下:

# models/orders.yaml
---
semantic_model:
  name: orders
  db_table: dw.orders
  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:验证并提交

  1. 在 WebUI 中单击验证,检查模型是否正确。系统自动检查表是否存在、列名是否正确、实体和维度是否完整。

  2. 确认无误后单击提交使模型生效。

  3. 使用 list_metrics 查看可用指标,使用 query_metric 查询数据。

语义模型结构

一个完整的 semantic_model 包含以下字段:

字段

类型

必填

说明

name

字符串

是

全局唯一的模型名称。小写字母开头,可含数字和下划线。

db_table

字符串

是

物理表名,库名.表名 格式,如 dw.orders。

defaults

对象

是

默认配置,必须包含 agg_time_dimension。

entities

列表

是

实体定义,至少包含一个 type: primary 的主实体。

dimensions

列表

是

可用于分组和筛选的维度。

measures

列表

推荐

聚合计算定义,定义后自动成为可查询的指标。

说明

命名规则:所有名称必须以小写字母开头,只能包含小写字母、数字和下划线,不能包含两个连续下划线,至少 2 个字符。

实体

实体定义了行与行之间的唯一性和关联关系。每张表都必须有一个主实体。

类型

说明

primary

主键。每行唯一,覆盖全部记录。每张表必须有且仅有一个 primary 实体。

foreign

外键。可以有重复值和空值,用于关联其他表。

unique

唯一键。每行唯一,可能不覆盖全部记录。

natural

自然键。现实世界中的唯一标识,如商品条形码、员工工号。

示例

entities:
  - name: order_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)

维度

维度定义了如何对数据进行分组和筛选,包括时间维度和分类维度两种类型。

时间粒度

粒度

含义

示例

day

按天

2025-01-15

week

按周

2025-W03

month

按月

2025-01

quarter

按季度

2025-Q1

year

按年

2025

示例

dimensions:
  - name: order_date               # 时间维度(按天)
    type: time
    type_params:
      time_granularity: day

  - 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)

维度支持使用 substring、concat、coalesce、cast 等标准 SQL 函数作为表达式。

度量

度量定义了对数据列的聚合运算。每个度量自动生成一个同名的查询指标。

聚合类型

含义

典型用法

sum

求和

总金额、总次数

count

计数

订单数、用户数

count_distinct

去重计数

独立用户数、活跃设备数

average

平均值

平均金额、平均时长

min

最小值

最低价格

max

最大值

最高价格

median

中位数

订单金额中位数

percentile

百分位数

P99 延迟、P95 金额

示例

measures:
  - name: total_amount
    agg: sum
    expr: amount

  - name: unique_customers
    agg: count_distinct
    expr: user_id

  - name: p99_amount
    agg: percentile
    expr: amount
    agg_params:
      percentile: 0.99

  - name: internal_counter         # 不暴露为指标
    agg: sum
    expr: raw_value
    create_metric: false

完整示例

以下是一个电商场景的完整语义模型定义,包含订单事实表、用户维表和商品维表三个模型文件。

orders.yaml — 订单事实表

# 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

users.yaml — 用户维表

# 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

products.yaml — 商品维表

# models/products.yaml — 商品维表
---
semantic_model:
  name: products
  description: "商品信息维表"
  db_table: dw.products
  defaults:
    agg_time_dimension: order_date

  entities:
    - name: product
      type: primary
      expr: product_id

  dimensions:
    - name: order_date
      type: time
      type_params:
        time_granularity: day
      expr: product_id
    - name: product_name
      type: categorical

  measures:
    - name: product_count
      agg: count
      expr: product_id

常见场景

同一列作为不同粒度的维度

一张日期列可以同时定义 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        # 同一列

使用 SQL 表达式

当列名不够直观或需要计算字段时,可以使用 SQL 表达式。支持 substring、concat、coalesce、cast 等标准 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)  # 计算字段

分区表

如果表有分区列,可以通过 is_partition: true 标记分区维度:

dimensions:
  - name: ds
    type: time
    type_params:
      time_granularity: day
    is_partition: true              # 标记为分区列
    expr: ds

隐藏内部度量

有些度量只想作为中间计算使用,不想对终端用户暴露。设置 create_metric: false 即可隐藏:

measures:
  - name: total_amount            # 公开指标
    agg: sum
    expr: amount

  - name: _raw_count              # 不暴露为指标
    agg: count
    expr: order_id
    create_metric: false

高级指标定义

除度量自动生成的简单指标外,还可以通过独立的 YAML 文件(顶层使用 metric:)定义高级指标。支持四种类型:

类型

说明

典型场景

ratio

分子 ÷ 分母

转化率、利润率

derived

对已有指标做表达式计算

环比增长、同比变化

cumulative

按时间窗口累加

近 7 日销量、月累计注册数

conversion

两个事件的转化分析

下单转化率、注册转化率

比率指标

# models/conversion_rate.yaml
---
metric:
  name: order_conversion_rate
  description: "订单转化率:下单数 / 访问数"
  type: ratio
  type_params:
    numerator: order_count
    denominator: visit_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
        alias: prev_revenue
        offset_window: 1 month

累积指标

# models/weekly_sales.yaml
---
metric:
  name: weekly_sales
  description: "近 7 天销售额"
  type: cumulative
  type_params:
    measure:
      name: total_amount
    window: 7 days

转化指标

# models/order_conversion.yaml
---
metric:
  name: visit_to_order_conversion
  description: "从访问到下单的转化率"
  type: conversion
  type_params:
    conversion_type_params:
      base_measure:
        name: visit_count
      conversion_measure:
        name: order_count
      entity: user
      calculation: conversion_rate

进阶功能

过滤器

可以在度量或指标定义中添加 SQL 过滤条件,在聚合计算前应用。

# 度量级过滤:只计算已完成状态的订单总额
measures:
  - name: completed_amount
    agg: sum
    expr: amount
    filter: {{ render_dimension_template('status') }} = 'completed'

过滤器语法:使用 {{ Dimension('name') }} 引用维度,使用 {{ Entity('name') }} 引用实体,后跟 SQL 条件。

预定义查询

将常用的指标、分组和筛选组合保存为查询模板,用户可直接调用。

# models/weekly_report.yaml
---
saved_query:
  name: weekly_revenue_report
  description: "周度收入报告"
  query_params:
    metrics:
      - total_amount
      - order_count
    group_by:
      - order_id__order_week
      - order_id__channel
    order_by:
      - "-order_id__order_week"
    limit: 52

维度引用格式为 实体名__维度名(双下划线连接)。

空值填充与时间轴对齐

metric:
  name: daily_orders
  type: simple
  type_params:
    measure:
      name: order_count
    fill_nulls_with: 0          # 无数据日期显示 0
    join_to_timespine: true    # 对齐到时间轴,补全无数据日期

非加性度量与缓慢变化维度

有些度量不能简单求和(如库存量、账户余额),需要在特定维度下取快照值。通过 non_additive_dimension 指定非加性维度和窗口选择方式:

# 非加性度量:库存量(取月末快照)
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                # 标记为结束时间

原生表引用格式

除了 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 模式,控制在时间窗口内的聚合行为:

metric:
  name: end_of_week_inventory
  type: cumulative
  type_params:
    cumulative_type_params:
      measure:
        name: inventory_count
      window: 7 days
      period_agg: last       # 取窗口内最后一天的值(默认)

      # period_agg: average  # 窗口内每日均值
      # period_agg: first    # 窗口内第一天的值

period_agg

说明

last

窗口内最后一天的值(默认)

average

窗口内每日均值

first

窗口内第一天的值

转化指标固定属性

在转化指标中,可以用 constant_properties 指定在不同事件间保持不变的属性:

# 按"渠道"维度分析转化,要求渠道在两次事件间不变
metric:
  name: visit_to_order_by_source
  type: conversion
  type_params:
    conversion_type_params:
      base_measure:
        name: visit_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:在派生指标中,将偏移对齐到指定粒度(而非默认的天级别):

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 既是"买家"也是"推荐人"。通过 role 区分:

entities:
  - name: user
    type: foreign
    expr: buyer_id
    role: buyer              # 此 user 实体的角色是"买家"

  - name: user
    type: foreign
    expr: referrer_id
    role: referrer           # 此 user 实体的角色是"推荐人"

不指定 role 时,默认角色等同于实体名。

工作空间管理

工作空间概述

工作空间是语义模型的隔离容器。每个工作空间拥有独立的模型文件、指标列表、查询引擎实例和数据库连接池。工作空间 A 的指标对工作空间 B 完全不可见。

工作空间有三种运行状态:

状态

图标

说明

healthy

绿色

正常运行。语义模型已加载,指标可查询。

no_models

白色

空工作区。尚未上传任何 YAML 文件。

not_ready

红色

加载失败。YAML 语法错误、表不存在或缺少 project.yaml 等。

Active Store 与 Staging Store

每个工作空间内部有两层存储:

  • Active Store(已生效):已提交并加载的模型,查询引擎使用此版本,文件只读。

  • Staging Store(待提交):用户上传或编辑的待定变更,验证通过后提交进入 Active Store。

更新流程

  1. 上传或编辑 YAML:文件进入 Staging Store,不影响正在运行的查询。

  2. 验证:系统自动检查表存在性、列名正确性、YAML 语法、命名规范和跨模型重复名。

  3. 提交:将 Staging 内容移至 Active Store,系统自动触发引擎重载。

  4. 自动重载:后台线程解析新模型、编译清单、更新路由,通常 2~5 秒完成。

重要

必须先验证再提交。未经过验证的 Staging 内容会被提交操作拒绝。

权限模型

操作

管理员

普通用户

查看语义模型、查询指标

✅

✅

上传、编辑、删除 YAML

✅

❌

验证、提交、丢弃

✅

❌

创建、删除工作空间

✅

❌

默认示例工作区

首次启动时,系统会自动创建 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 地址,使用数据库账号登录后,可进行以下操作:

按钮

说明

验证

验证 Staging 中的所有变更,包括物理校验、语义校验和重复检测。

提交

将验证通过的内容提交到 Active Store,触发引擎重载。

放弃

丢弃 Staging 中的所有变更,回到 Active Store 版本。

重载

手动触发引擎重载(通常提交后自动执行)。

新建

新建 YAML 模型文件。

最佳实践

  • 一张表一个文件:文件命名使用 表名.yaml,结构清晰易维护。

  • 按顺序定义:先定义实体,再写维度,最后加度量。实体是语义模型的骨架,维度是分组能力,度量是查询目标。

  • 添加描述:给每个度量写 description,终端用户通过描述理解指标含义。

  • 多粒度维度:同一张时间列可以定义多种粒度的维度,如 order_date 列同时定义 day、week、month、quarter、year。

  • 有意义的实体名称:外键实体使用业务名称,如 customer 比 user_id 更易理解。

  • 先验证再提交:每次修改 YAML 后,先执行验证确认无误再提交。

  • 度量名全局唯一:不同模型的度量名重复会导致指标覆盖,建议全局唯一。

常见错误排查

错误信息

解决方法

Table xxx does not exist

检查 db_table 的库名和表名拼写。

measure references missing column

检查 expr 是否与实际列名一致(注意大小写)。

Duplicate measure 'xxx'

重命名其中一个度量,确保全局唯一。

Did not find exactly one project configuration

创建包含 time_config 的 project.yaml。

'xxx' does not match naming rules

使用小写字母开头,去除双下划线,加长到至少 2 个字符。

No staging changes to validate

先上传或修改 YAML 文件,再执行验证。

快速排查步骤:

  1. 确认数据库表存在:执行 DESCRIBE dw.orders。

  2. 确认列名和大小写完全一致。

  3. 检查 YAML 缩进是否正确(必须用空格,不能用 Tab)。

  4. 确认所有名称符合小写字母开头的命名规范。