返回正文目录

TM 语法手册

TM(Table Model,表模型)用于定义数据库表的结构和关联关系。本文档详细介绍 TM 的完整语法规范。

1. 基本结构

TM 文件使用 JavaScript ES6 模块语法,导出一个 model 对象:

export const model = {
    name: 'FactSalesModel',      // 模型名称(必填,唯一标识)
    caption: '销售事实表',         // 模型显示名称
    description: '销售订单明细数据', // 模型描述
    tableName: 'fact_sales',     // 对应的数据库表名(必填)
    idColumn: 'sales_key',       // 主键列名

    dimensions: [...],           // 维度定义(关联其他表)
    properties: [...],           // 属性定义(本表字段)
    measures: [...]              // 度量定义(可聚合字段)
};

1.1 模型基础字段

字段 类型 必填 说明
name string 是 模型唯一标识,QM 中通过此名称引用
caption string 否 模型显示名称,建议填写,使用mcp时会传递给AI
description string 否 模型详细描述,建议填写,使用mcp时会传递给AI
tableName string 是¹ 对应的数据库表名、mongo集合名
viewSql string 否¹ 视图SQL,与 tableName 二选一
schema string 否 数据库 Schema(跨 Schema 访问时使用)
idColumn string 否 主键列名
type string 否 模型类型,默认 jdbc、mongo、vector
deprecated boolean 否 标记为废弃,默认 false
dataSourceName string 否 命名数据源引用,支持不同模型使用不同数据源
autoLoadDimensions boolean 否 自动加载维度定义
autoLoadMeasures boolean 否 自动加载度量定义
preAggregations array 否 预聚合配置列表,详见第11章

¹ tableName 和 viewSql 二选一,优先使用 tableName

1.2 AI 增强配置

可为模型、维度、属性、度量添加 ai 配置,用于优化 AI 自然语言查询:

{
    name: 'salesAmount',
    caption: '销售金额',
    type: 'MONEY',
    ai: {
        enabled: true,              // 是否激活AI分析(默认 true)
        prompt: '客户实际支付金额',   // 替代 description 的提示词
        levels: [1, 2]              // 激活等级列表
    }
}
字段 类型 说明
enabled boolean 是否激活AI分析,默认 true
prompt string 提示词,若填写则替代 description
levels number[] 激活等级列表,字段可属于多个级别

2. 维度定义 (dimensions)

维度用于定义与其他表的关联关系,查询时自动生成 JOIN。

2.1 基本维度

dimensions: [
    {
        name: 'customer',              // 维度名称(用于查询时引用)
        caption: '客户',                // 维度显示名称
        description: '购买商品的客户信息', // 维度描述

        tableName: 'dim_customer',     // 关联的维度表名
        foreignKey: 'customer_key',    // 本表的外键字段
        primaryKey: 'customer_key',    // 维度表的主键字段
        captionColumn: 'customer_name', // 维度的显示字段

        keyCaption: '客户Key',          // 主键字段的显示名称
        keyDescription: '客户代理键,自增整数', // 主键字段的描述

        // 维度属性(维度表中可查询的字段)
        properties: [
            {
                column: 'customer_id',
                caption: '客户ID',
                description: '客户唯一标识'
            },
            {
                column: 'customer_type',
                caption: '客户类型',
                description: '客户类型:个人/企业'
            },
            { column: 'province', caption: '省份' },
            { column: 'city', caption: '城市' },
            { column: 'member_level', caption: '会员等级' }
        ]
    }
]

2.2 维度字段说明

字段 类型 必填 说明
name string 是 维度名称,查询时使用 维度名$属性名 格式引用
caption string 否 维度显示名称
description string 否 维度详细描述
tableName string 是¹ 关联的维度表名
viewSql string 否¹ 维度视图SQL,与 tableName 二选一
schema string 否 维度表的 Schema
foreignKey string 是 事实表中的外键字段
primaryKey string 是 维度表的主键字段
captionColumn string 否 维度的显示字段,用于 维度名$caption(简单形式)
captionDef object 否 Caption 高级定义(优先级高于 captionColumn),见下文
keyCaption string 否 主键字段的显示名称,默认为 ${caption}Key
keyDescription string 否 主键字段的描述信息
type string 否 维度类型:NORMAL/DAY/DATETIME/DICT/BOOL/INTEGER/DOUBLE
properties array 否 维度表中可查询的属性列表
forceIndex string 否 强制使用的索引名称
alias string 否 维度别名(重定义列名前缀)
deprecated boolean 否 标记为废弃
dimensionDataSql function 否 维度数据 SQL 函数(用于访问控制)
onBuilder function 否 维度连接构建函数

DAY vs DATETIME:DAY 仅包含日期部分(yyyy-MM-dd),DATETIME 包含日期和时间(yyyy-MM-dd HH:mm:ss)。

captionDef 高级定义

当 captionColumn 不够用时(例如需要跨数据库方言的公式),可以使用 captionDef:

{
    name: 'orderDate',
    captionDef: {
        // 方式1:通用公式(所有方言生效)
        formulaDef: {
            value: 'DATE_FORMAT(order_date, "%Y-%m")'
        },

        // 方式2:方言专属公式(优先级最高)
        dialectFormulaDef: {
            mysql: { value: 'DATE_FORMAT(order_date, "%Y-%m")' },
            postgresql: { value: "TO_CHAR(order_date, 'YYYY-MM')" },
            sqlserver: { value: "FORMAT(order_date, 'yyyy-MM')" }
        },

        // 方式3:直接列引用(最低优先级)
        column: 'order_date'
    }
}

优先级:dialectFormulaDef > formulaDef > column > 外层 captionColumn

¹ tableName 和 viewSql 二选一,优先使用 tableName

2.3 嵌套维度(雪花模型)

嵌套维度用于实现雪花模型,即维度表之间存在层级关系。

{
    // 一级维度:产品(与事实表直接关联)
    name: 'product',
    tableName: 'dim_product',
    foreignKey: 'product_key',       // 事实表上的外键
    primaryKey: 'product_key',
    captionColumn: 'product_name',
    caption: '商品',

    properties: [
        { column: 'product_id', caption: '商品ID' },
        { column: 'brand', caption: '品牌' },
        { column: 'unit_price', caption: '单价', type: 'MONEY' }
    ],

    // 嵌套子维度:品类(与产品维度关联,而非事实表)
    dimensions: [
        {
            name: 'category',
            alias: 'productCategory',   // 别名,简化 QM 访问
            tableName: 'dim_category',
            foreignKey: 'category_key', // 在父维度表(dim_product)上的外键
            primaryKey: 'category_key',
            captionColumn: 'category_name',
            caption: '品类',

            properties: [
                { column: 'category_id', caption: '品类ID' },
                { column: 'category_level', caption: '品类层级' }
            ],

            // 继续嵌套:品类组(与品类维度关联)
            dimensions: [
                {
                    name: 'group',
                    alias: 'categoryGroup',
                    tableName: 'dim_category_group',
                    foreignKey: 'group_key',  // 在父维度表(dim_category)上的外键
                    primaryKey: 'group_key',
                    captionColumn: 'group_name',
                    caption: '品类组',

                    properties: [
                        { column: 'group_id', caption: '品类组ID' },
                        { column: 'group_type', caption: '组类型' }
                    ]
                }
            ]
        }
    ]
}

嵌套维度关键点:

字段 说明
alias 维度别名,用于在 QM 中简化列名访问,避免路径过长
foreignKey 重要:嵌套维度的 foreignKey 指向父维度表上的列
dimensions 子维度列表,可继续嵌套形成多层结构

语法设计原则:嵌套维度引用使用两种分隔符,各管一件事:

分隔符 职责 示例
.(点号) 维度层级导航 — 定位到哪个维度 product.category.group
$(美元符) 属性访问 — 取维度的哪个字段 category$caption

组合使用:product.category$caption = 沿 product → category 路径,取 caption 属性。不能用多个 $ 替代 .(如 product$category$caption),否则解析器无法区分维度路径和属性名。

QM 中引用嵌套维度(三种等效写法):

// 写法1:别名(推荐,简短直观)
// 需在 TM 中定义 alias,如 alias: 'productCategory'
{ ref: fs.productCategory$caption }
{ ref: fs.categoryGroup$caption }

// 写法2:完整路径(精确,无需 alias)
{ ref: fs.product.category$caption }
{ ref: fs.product.category.group$caption }

// 写法3:DSL 查询中使用下划线格式(输出列名格式)
columns: ["product_category$caption", "product_category_group$caption"]

输出列名转换:路径中的 . 在输出时自动转为 _,避免 JavaScript 属性名冲突:

QM 引用 输出列名
product$caption product$caption
product.category$caption product_category$caption
product.category.group$caption product_category_group$caption

生成的 SQL JOIN:

SELECT ...
FROM fact_sales f
LEFT JOIN dim_product p ON f.product_key = p.product_key
LEFT JOIN dim_category c ON p.category_key = c.category_key
LEFT JOIN dim_category_group g ON c.group_key = g.group_key

2.4 父子维度(层级结构)

父子维度用于处理树形层级结构数据(如组织架构、商品分类),通过闭包表实现高效查询。

{
    name: 'team',
    tableName: 'dim_team',
    foreignKey: 'team_id',
    primaryKey: 'team_id',
    captionColumn: 'team_name',
    caption: '团队',
    description: '销售所属团队',
    keyDescription: '团队ID,字符串格式',

    // 父子维度配置
    closureTableName: 'team_closure',  // 闭包表名(必填)
    parentKey: 'parent_id',            // 闭包表祖先列(必填)
    childKey: 'team_id',               // 闭包表后代列(必填)

    properties: [
        { column: 'team_id', caption: '团队ID', type: 'STRING' },
        { column: 'team_name', caption: '团队名称', type: 'STRING' },
        { column: 'parent_id', caption: '上级团队', type: 'STRING' },
        { column: 'team_level', caption: '层级', type: 'INTEGER' },
        { column: 'manager_name', caption: '负责人', type: 'STRING' }
    ]
}

父子维度专用字段:

字段 类型 必填 说明
closureTableName string 是 闭包表名称
closureTableSchema string 否 闭包表 Schema
parentKey string 是 闭包表中的祖先列(如 parent_id)
childKey string 是 闭包表中的后代列(如 team_id)

闭包表结构示例:

CREATE TABLE team_closure (
    parent_id VARCHAR(50),  -- 祖先节点ID
    child_id  VARCHAR(50),  -- 后代节点ID
    depth     INT,          -- 层级深度(0表示自己)
    PRIMARY KEY (parent_id, child_id)
);

详细说明请参考 父子维度文档


3. 属性定义 (properties)

属性用于定义表自身的字段(非聚合字段)。

3.1 基本属性

properties: [
    {
        column: 'order_id',        // 数据库列名(必填)
        name: 'orderId',           // 属性名称(可选)
        caption: '订单ID',          // 显示名称
        description: '订单唯一标识', // 详细描述
        type: 'STRING'             // 数据类型
    },
    {
        column: 'order_status',
        caption: '订单状态',
        type: 'STRING'
    },
    {
        column: 'created_at',
        caption: '创建时间',
        type: 'DATETIME'
    }
]

3.2 字典引用属性

使用 dictRef 将数据库值映射为显示标签:

import { dicts } from '../dicts.fsscript';

properties: [
    {
        column: 'order_status',
        caption: '订单状态',
        type: 'STRING',
        dictRef: dicts.order_status  // 引用字典
    },
    {
        column: 'payment_method',
        caption: '支付方式',
        type: 'STRING',
        dictRef: dicts.payment_method
    }
]

字典定义示例 (dicts.fsscript):

import { registerDict } from '@jdbcModelDictService';

export const dicts = {
    order_status: registerDict({
        id: 'order_status',
        caption: '订单状态',
        items: [
            { value: 'PENDING', label: '待处理' },
            { value: 'CONFIRMED', label: '已确认' },
            { value: 'SHIPPED', label: '已发货' },
            { value: 'COMPLETED', label: '已完成' },
            { value: 'CANCELLED', label: '已取消' }
        ]
    }),

    payment_method: registerDict({
        id: 'payment_method',
        caption: '支付方式',
        items: [
            { value: '1', label: '现付' },
            { value: '2', label: '到付' },
            { value: '3', label: '货到付款' }
        ]
    })
};

3.3 计算属性

使用 formulaDef 定义计算字段。builder 函数接收表别名,返回原生 SQL 表达式:

properties: [
    {
        column: 'customer_name',
        name: 'fullName',
        caption: '客户全名',
        type: 'STRING',
        formulaDef: {
            builder: (alias) => {
                return `CONCAT(${alias}.first_name, ' ', ${alias}.last_name)`;
            },
            description: '拼接姓和名'
        }
    },
    {
        column: 'amount',
        name: 'amountInWan',
        caption: '金额(万元)',
        type: 'MONEY',
        formulaDef: {
            builder: (alias) => {
                return `${alias}.amount / 10000`;
            },
            description: '将金额换算为万元'
        }
    }
]

::: warning 方言注意 builder 生成的是原生 SQL,直接嵌入查询。涉及 JSON 提取等方言差异时,需按目标数据库编写:

场景 MySQL PostgreSQL SQL Server
JSON 文本提取 col ->> '$.key' col ->> 'key' JSON_VALUE(col, '$.key')
类型转换 CAST(col AS SIGNED) col::integer CAST(col AS INT)

通用函数(CONCAT、COALESCE、ROUND 等)在所有方言下可安全使用。 :::

3.4 属性字段说明

字段 类型 必填 说明
column string 是 数据库列名
name string 否 属性名称,默认为 column 的驼峰形式
alias string 否 属性别名
caption string 否 显示名称
description string 否 详细描述,若字段含义复杂,建议填写,有助于AI推断
type string 否 数据类型(见 5. 数据类型)
format string 否 格式化模板(用于日期等)
dictRef string 否 字典引用,用于值到标签的转换
formulaDef object 否 公式定义(见 3.5)

3.5 公式定义 (formulaDef)

字段 类型 说明
builder function SQL 构建函数,参数 alias 为表别名
value string 公式表达式(基于度量名称)
description string 公式的文字描述

builder 和 value 二选一,builder 更灵活,可直接操作 SQL


4. 度量定义 (measures)

度量用于定义可聚合的数值字段。

4.1 基本度量

measures: [
    {
        column: 'quantity',         // 数据库列名(必填)
        name: 'salesQuantity',      // 度量名称(可选)
        caption: '销售数量',          // 显示名称
        description: '商品销售件数',  // 详细描述
        type: 'INTEGER',            // 数据类型
        aggregation: 'sum'          // 聚合方式(必填)
    },
    {
        column: 'sales_amount',
        name: 'salesAmount',
        caption: '销售金额',
        type: 'MONEY',
        aggregation: 'sum'
    },
    {
        column: 'unit_price',
        caption: '单价',
        type: 'MONEY',
        aggregation: 'avg'          // 平均值聚合
    }
]

4.2 计算度量

使用 formulaDef 定义计算度量:

measures: [
    {
        column: 'tax_amount',
        name: 'taxAmount2',
        caption: '税额*2',
        description: '用于测试计算字段',
        type: 'MONEY',
        formulaDef: {
            builder: (alias) => {
                return `${alias}.tax_amount + 1`;
            },
            description: '税额加一'
        }
    }
]

4.3 COUNT 聚合

不基于具体列的计数:

measures: [
    {
        name: 'recordCount',
        caption: '记录数',
        aggregation: 'count',
        type: 'INTEGER'
    }
]

4.4 度量字段说明

字段 类型 必填 说明
column string 否¹ 数据库列名
name string 否 度量名称,默认为 column 的驼峰形式
alias string 否 度量别名
caption string 否 显示名称
description string 否 详细描述
type string 否 数据类型(见 5. 数据类型)
aggregation string 是 聚合方式(见 4.5)
formulaDef object 否 公式定义(见 3.5)

¹ count 聚合可以不指定 column

4.5 聚合方式

值 说明 适用类型
sum 求和 数值类型
avg 平均值 数值类型
count 计数 所有类型
max 最大值 数值/日期类型
min 最小值 数值/日期类型
none 不聚合 所有类型

5. 数据类型

5.1 类型列表

类型 别名 说明 Java 类型 使用场景
STRING TEXT 字符串 String 文本、编��
INTEGER - 整数 Integer 计数、枚举
BIGINT LONG 长整数 Long 大数值主键
MONEY NUMBER, BigDecimal 金额/精确小数 BigDecimal 金额、价格
DATETIME - 日期时间 Date 时间戳
DAY DATE 日期 Date 日期(yyyy-MM-dd)
BOOL Boolean 布尔值 Boolean 是/否标志
DICT - 字典值 Integer 字典编码
VECTOR - 向量 List<Float> 向量检索字段

5.2 类型选择建议


6. 完整示例

6.1 事实表模型

// FactSalesModel.tm
/**
 * 销售事实表模型定义
 *
 * @description 电商测试数据 - 销售事实表(订单明细)
 *              包含日期、商品、客户、门店、渠道、促销等维度关联
 */
import { dicts } from '../dicts.fsscript';

export const model = {
    name: 'FactSalesModel',
    caption: '销售事实表',
    tableName: 'fact_sales',
    idColumn: 'sales_key',

    // 维度定义 - 关联维度表
    dimensions: [
        {
            name: 'salesDate',
            tableName: 'dim_date',
            foreignKey: 'date_key',
            primaryKey: 'date_key',
            captionColumn: 'full_date',
            caption: '销售日期',
            description: '订单发生的日期',
            keyDescription: '日期主键,格式yyyyMMdd,如20240101',

            properties: [
                { column: 'year', caption: '年', description: '销售发生的年份' },
                { column: 'quarter', caption: '季度', description: '销售发生的季度(1-4)' },
                { column: 'month', caption: '月', description: '销售发生的月份(1-12)' },
                { column: 'month_name', caption: '月份名称' },
                { column: 'day_of_week', caption: '周几' },
                { column: 'is_weekend', caption: '是否周末' }
            ]
        },
        {
            name: 'product',
            tableName: 'dim_product',
            foreignKey: 'product_key',
            primaryKey: 'product_key',
            captionColumn: 'product_name',
            caption: '商品',
            description: '销售的商品信息',

            properties: [
                { column: 'product_id', caption: '商品ID' },
                { column: 'category_name', caption: '一级品类名称' },
                { column: 'sub_category_name', caption: '二级品类名称' },
                { column: 'brand', caption: '品牌' },
                { column: 'unit_price', caption: '商品售价', type: 'MONEY' },
                { column: 'unit_cost', caption: '商品成本', type: 'MONEY' }
            ]
        },
        {
            name: 'customer',
            tableName: 'dim_customer',
            foreignKey: 'customer_key',
            primaryKey: 'customer_key',
            captionColumn: 'customer_name',
            caption: '客户',

            properties: [
                { column: 'customer_id', caption: '客户ID' },
                { column: 'customer_type', caption: '客户类型' },
                { column: 'gender', caption: '性别' },
                { column: 'age_group', caption: '年龄段' },
                { column: 'province', caption: '省份' },
                { column: 'city', caption: '城市' },
                { column: 'member_level', caption: '会员等级' }
            ]
        }
    ],

    // 属性定义 - 事实表自身属性
    properties: [
        {
            column: 'sales_key',
            caption: '销售代理键',
            type: 'BIGINT'
        },
        {
            column: 'order_id',
            caption: '订单ID',
            type: 'STRING'
        },
        {
            column: 'order_line_no',
            caption: '订单行号',
            type: 'INTEGER'
        },
        {
            column: 'order_status',
            caption: '订单状态',
            type: 'STRING',
            dictRef: dicts.order_status
        },
        {
            column: 'payment_method',
            caption: '支付方式',
            type: 'STRING',
            dictRef: dicts.payment_method
        },
        {
            column: 'created_at',
            caption: '创建时间',
            type: 'DATETIME'
        }
    ],

    // 度量定义
    measures: [
        {
            column: 'quantity',
            caption: '销售数量',
            type: 'INTEGER',
            aggregation: 'sum'
        },
        {
            column: 'sales_amount',
            name: 'salesAmount',
            caption: '销售金额',
            type: 'MONEY',
            aggregation: 'sum'
        },
        {
            column: 'cost_amount',
            name: 'costAmount',
            caption: '成本金额',
            type: 'MONEY',
            aggregation: 'sum'
        },
        {
            column: 'profit_amount',
            name: 'profitAmount',
            caption: '利润金额',
            type: 'MONEY',
            aggregation: 'sum'
        }
    ]
};

6.2 维度表模型

// DimProductModel.tm
/**
 * 商品维度模型定义
 *
 * @description 电商测试数据 - 商品维度表
 */
export const model = {
    name: 'DimProductModel',
    caption: '商品维度',
    tableName: 'dim_product',
    idColumn: 'product_key',

    dimensions: [],  // 维度表通常不关联其他维度

    properties: [
        {
            column: 'product_key',
            caption: '商品代理键',
            type: 'INTEGER'
        },
        {
            column: 'product_id',
            caption: '商品业务ID',
            type: 'STRING'
        },
        {
            column: 'product_name',
            caption: '商品名称',
            type: 'STRING'
        },
        {
            column: 'category_id',
            caption: '一级品类ID',
            type: 'STRING'
        },
        {
            column: 'category_name',
            caption: '一级品类名称',
            type: 'STRING'
        },
        {
            column: 'sub_category_id',
            caption: '二级品类ID',
            type: 'STRING'
        },
        {
            column: 'sub_category_name',
            caption: '二级品类名称',
            type: 'STRING'
        },
        {
            column: 'brand',
            caption: '品牌',
            type: 'STRING'
        },
        {
            column: 'unit_price',
            caption: '售价',
            type: 'MONEY'
        },
        {
            column: 'unit_cost',
            caption: '成本',
            type: 'MONEY'
        },
        {
            column: 'status',
            caption: '状态',
            type: 'STRING'
        },
        {
            column: 'created_at',
            caption: '创建时间',
            type: 'DATETIME'
        }
    ],

    measures: []  // 维度表通常没有度量
};

6.3 日期维度表模型

// DimDateModel.tm
/**
 * 日期维度模型定义
 *
 * @description 电商测试数据 - 日期维度表
 */
export const model = {
    name: 'DimDateModel',
    caption: '日期维度',
    tableName: 'dim_date',
    idColumn: 'date_key',

    dimensions: [],

    properties: [
        {
            column: 'date_key',
            caption: '日期键',
            description: '日期主键,格式为yyyyMMdd的整数,如20240101',
            type: 'INTEGER'
        },
        {
            column: 'full_date',
            caption: '完整日期',
            description: '完整日期,格式为yyyy-MM-dd',
            type: 'DAY'
        },
        {
            column: 'year',
            caption: '年',
            description: '年份,如2024',
            type: 'INTEGER'
        },
        {
            column: 'quarter',
            caption: '季度',
            description: '季度数字,1-4表示第一到第四季度',
            type: 'INTEGER'
        },
        {
            column: 'month',
            caption: '月',
            description: '月份数字,1-12',
            type: 'INTEGER'
        },
        {
            column: 'month_name',
            caption: '月份名称',
            description: '月份中文名,如一月、二月、十二月',
            type: 'STRING'
        },
        {
            column: 'week_of_year',
            caption: '年度周数',
            description: '一年中的第几周,1-53',
            type: 'INTEGER'
        },
        {
            column: 'day_of_week',
            caption: '周几',
            description: '一周中的第几天,1=周一,7=周日',
            type: 'INTEGER'
        },
        {
            column: 'is_weekend',
            caption: '是否周末',
            description: '是否为周末(周六或周日)',
            type: 'BOOL'
        },
        {
            column: 'is_holiday',
            caption: '是否节假日',
            description: '是否为法定节假日',
            type: 'BOOL'
        }
    ],

    measures: []
};

7. 维度复用最佳实践

在实际项目中,同一个维度表(如日期维度、客户维度)往往被多个事实表引用。TM 文件使用 FSScript(类 JavaScript)语法,支持函数封装和模块导入,可以将通用维度配置抽取为工厂函数,实现维度定义的复用。

7.1 创建维度构建器

将常用维度封装为函数,存放在独立文件中:

// dimensions/common-dims.fsscript
/**
 * 通用维度构建器
 * 提供可复用的维度定义工厂函数
 */

/**
 * 构建日期维度
 * @param {object} options - 配置选项
 * @param {string} options.name - 维度名称,默认 'salesDate'
 * @param {string} options.foreignKey - 外键列名,默认 'date_key'
 * @param {string} options.caption - 显示名称,默认 '日期'
 * @returns {object} 维度配置对象
 */
export function buildDateDim(options = {}) {
    const {
        name = 'salesDate',
        foreignKey = 'date_key',
        caption = '日期',
        description = '业务发生的日期'
    } = options;

    return {
        name,
        tableName: 'dim_date',
        foreignKey,
        primaryKey: 'date_key',
        captionColumn: 'full_date',
        caption,
        description,
        keyDescription: '日期主键,格式yyyyMMdd,如20240101',
        type: 'DATETIME',

        properties: [
            { column: 'year', caption: '年', type: 'INTEGER', description: '年份' },
            { column: 'quarter', caption: '季度', type: 'INTEGER', description: '季度(1-4)' },
            { column: 'month', caption: '月', type: 'INTEGER', description: '月份(1-12)' },
            { column: 'month_name', caption: '月份名称', type: 'STRING' },
            { column: 'week_of_year', caption: '年度周数', type: 'INTEGER' },
            { column: 'day_of_week', caption: '周几', type: 'INTEGER' },
            { column: 'is_weekend', caption: '是否周末', type: 'BOOL' },
            { column: 'is_holiday', caption: '是否节假日', type: 'BOOL' }
        ]
    };
}

/**
 * 构建客户维度
 * @param {object} options - 配置选项
 */
export function buildCustomerDim(options = {}) {
    const {
        name = 'customer',
        foreignKey = 'customer_key',
        caption = '客户',
        description = '客户信息'
    } = options;

    return {
        name,
        tableName: 'dim_customer',
        foreignKey,
        primaryKey: 'customer_key',
        captionColumn: 'customer_name',
        caption,
        description,
        keyDescription: '客户代理键,自增整数',

        properties: [
            { column: 'customer_id', caption: '客户ID', description: '客户唯一标识' },
            { column: 'customer_type', caption: '客户类型', description: '个人/企业' },
            { column: 'gender', caption: '性别' },
            { column: 'age_group', caption: '年龄段' },
            { column: 'province', caption: '省份' },
            { column: 'city', caption: '城市' },
            { column: 'member_level', caption: '会员等级' }
        ]
    };
}

/**
 * 构建商品维度
 * @param {object} options - 配置选项
 */
export function buildProductDim(options = {}) {
    const {
        name = 'product',
        foreignKey = 'product_key',
        caption = '商品',
        description = '商品信息'
    } = options;

    return {
        name,
        tableName: 'dim_product',
        foreignKey,
        primaryKey: 'product_key',
        captionColumn: 'product_name',
        caption,
        description,
        keyDescription: '商品代理键,自增整数',

        properties: [
            { column: 'product_id', caption: '商品ID' },
            { column: 'category_name', caption: '一级品类名称' },
            { column: 'sub_category_name', caption: '二级品类名称' },
            { column: 'brand', caption: '品牌' },
            { column: 'unit_price', caption: '商品售价', type: 'MONEY' },
            { column: 'unit_cost', caption: '商品成本', type: 'MONEY' }
        ]
    };
}

/**
 * 构建门店维度
 */
export function buildStoreDim(options = {}) {
    const {
        name = 'store',
        foreignKey = 'store_key',
        caption = '门店',
        description = '门店信息'
    } = options;

    return {
        name,
        tableName: 'dim_store',
        foreignKey,
        primaryKey: 'store_key',
        captionColumn: 'store_name',
        caption,
        description,

        properties: [
            { column: 'store_id', caption: '门店ID' },
            { column: 'store_type', caption: '门店类型' },
            { column: 'province', caption: '省份' },
            { column: 'city', caption: '城市' }
        ]
    };
}

7.2 在 TM 文件中使用维度构建器

// model/FactSalesModel.tm
import { dicts } from '../dicts.fsscript';
import {
    buildDateDim,
    buildCustomerDim,
    buildProductDim,
    buildStoreDim
} from '../dimensions/common-dims.fsscript';

export const model = {
    name: 'FactSalesModel',
    caption: '销售事实表',
    tableName: 'fact_sales',
    idColumn: 'sales_key',

    dimensions: [
        // 使用构建器,自定义名称和描述
        buildDateDim({
            name: 'salesDate',
            caption: '销售日期',
            description: '订单发生的日期'
        }),

        // 使用默认配置
        buildCustomerDim(),
        buildProductDim(),
        buildStoreDim(),

        // 混合使用:构建器 + 内联维度
        {
            name: 'channel',
            tableName: 'dim_channel',
            foreignKey: 'channel_key',
            primaryKey: 'channel_key',
            captionColumn: 'channel_name',
            caption: '渠道',
            properties: [
                { column: 'channel_id', caption: '渠道ID' },
                { column: 'channel_type', caption: '渠道类型' }
            ]
        }
    ],

    properties: [
        // ... 属性定义
    ],

    measures: [
        // ... 度量定义
    ]
};
// model/FactOrderModel.tm
import { buildDateDim, buildCustomerDim } from '../dimensions/common-dims.fsscript';

export const model = {
    name: 'FactOrderModel',
    caption: '订单事实表',
    tableName: 'fact_order',
    idColumn: 'order_key',

    dimensions: [
        // 订单模型使用不同的维度名称
        buildDateDim({
            name: 'orderDate',
            foreignKey: 'order_date_key',
            caption: '订单日期'
        }),
        buildCustomerDim(),

        // 订单可能有多个日期维度
        buildDateDim({
            name: 'shipDate',
            foreignKey: 'ship_date_key',
            caption: '发货日期',
            description: '订单发货的日期'
        })
    ],

    properties: [...],
    measures: [...]
};

7.3 高级技巧:属性扩展与覆盖

构建器返回的对象可以通过展开运算符进行扩展或覆盖:

import { buildCustomerDim } from '../dimensions/common-dims.fsscript';

export const model = {
    name: 'FactVIPSalesModel',
    caption: 'VIP销售事实表',
    tableName: 'fact_vip_sales',

    dimensions: [
        // 扩展客户维度,添加额外属性
        {
            ...buildCustomerDim({ caption: 'VIP客户' }),
            // 添加 VIP 特有属性
            properties: [
                ...buildCustomerDim().properties,
                { column: 'vip_level', caption: 'VIP等级' },
                { column: 'vip_points', caption: '积分余额', type: 'INTEGER' },
                { column: 'vip_expire_date', caption: '会员到期日', type: 'DAY' }
            ]
        }
    ],

    // ...
};

7.4 嵌套维度的复用

对于雪花模型的嵌套维度,可以构建包含子维度的完整层级:

// dimensions/product-hierarchy.fsscript
/**
 * 构建带品类层级的商品维度(雪花模型)
 */
export function buildProductWithCategoryDim(options = {}) {
    const {
        name = 'product',
        foreignKey = 'product_key',
        caption = '商品'
    } = options;

    return {
        name,
        tableName: 'dim_product',
        foreignKey,
        primaryKey: 'product_key',
        captionColumn: 'product_name',
        caption,

        properties: [
            { column: 'product_id', caption: '商品ID' },
            { column: 'brand', caption: '品牌' },
            { column: 'unit_price', caption: '售价', type: 'MONEY' }
        ],

        // 嵌套品类维度
        dimensions: [
            {
                name: 'category',
                alias: 'productCategory',
                tableName: 'dim_category',
                foreignKey: 'category_key',
                primaryKey: 'category_key',
                captionColumn: 'category_name',
                caption: '品类',

                properties: [
                    { column: 'category_id', caption: '品类ID' },
                    { column: 'category_level', caption: '品类层级' }
                ],

                // 继续嵌套品类组
                dimensions: [
                    {
                        name: 'group',
                        alias: 'categoryGroup',
                        tableName: 'dim_category_group',
                        foreignKey: 'group_key',
                        primaryKey: 'group_key',
                        captionColumn: 'group_name',
                        caption: '品类组',

                        properties: [
                            { column: 'group_id', caption: '品类组ID' },
                            { column: 'group_type', caption: '组类型' }
                        ]
                    }
                ]
            }
        ]
    };
}

7.5 父子维度的复用

对于组织架构等父子维度,可以参数化闭包表配置:

// dimensions/hierarchy-dims.fsscript
/**
 * 构建组织/团队父子维度
 */
export function buildOrgDim(options = {}) {
    const {
        name = 'team',
        tableName = 'dim_team',
        foreignKey = 'team_id',
        closureTableName = 'team_closure',
        caption = '团队',
        description = '组织团队'
    } = options;

    return {
        name,
        tableName,
        foreignKey,
        primaryKey: 'team_id',
        captionColumn: 'team_name',
        caption,
        description,

        // 父子维度配置
        closureTableName,
        parentKey: 'parent_id',
        childKey: 'team_id',

        properties: [
            { column: 'team_id', caption: '团队ID', type: 'STRING' },
            { column: 'team_name', caption: '团队名称', type: 'STRING' },
            { column: 'parent_id', caption: '上级团队', type: 'STRING' },
            { column: 'team_level', caption: '层级', type: 'INTEGER' },
            { column: 'manager_name', caption: '负责人', type: 'STRING' }
        ]
    };
}

/**
 * 构建区域父子维度
 */
export function buildRegionDim(options = {}) {
    const {
        name = 'region',
        foreignKey = 'region_id',
        caption = '区域'
    } = options;

    return {
        name,
        tableName: 'dim_region',
        foreignKey,
        primaryKey: 'region_id',
        captionColumn: 'region_name',
        caption,

        closureTableName: 'region_closure',
        parentKey: 'parent_id',
        childKey: 'region_id',

        properties: [
            { column: 'region_id', caption: '区域ID', type: 'STRING' },
            { column: 'region_name', caption: '区域名称', type: 'STRING' },
            { column: 'region_type', caption: '区域类型', type: 'STRING' },
            { column: 'region_level', caption: '层级', type: 'INTEGER' }
        ]
    };
}

7.6 项目推荐结构

templates/
├── dimensions/                    # 维度构建器目录
│   ├── common-dims.fsscript       # 通用维度(日期、客户、商品等)
│   ├── hierarchy-dims.fsscript    # 父子维度(组织、区域等)
│   └── product-hierarchy.fsscript # 商品雪花维度
├── model/                         # TM 模型目录
│   ├── FactSalesModel.tm
│   ├── FactOrderModel.tm
│   └── ...
├── query/                         # QM 查询模型目录
│   └── ...
└── dicts.fsscript                 # 字典定义

7.7 维度复用最佳实践总结

实践 说明
函数封装 将重复使用的维度定义封装为工厂函数
参数化配置 通过参数支持不同场景的定制需求
默认值 为常用参数提供合理的默认值
模块化组织 按维度类型组织到不同的 .fsscript 文件
属性扩展 使用展开运算符 ... 扩展或覆盖属性
统一维护 修改维度定义时只需改一处,全局生效
文档注释 为构建器函数添加 JSDoc 注释,便于使用

8. 命名约定

8.1 文件命名

8.2 字段命名

位置 规范 示例
模型 name 大驼峰 PascalCase FactSalesModel, DimCustomerModel
字段 name 小驼峰 camelCase orderId, salesAmount, customerType
数据库 column 蛇形 snake_case order_id, sales_amount, customer_type
维度属性引用 $ 分隔 customer$caption, salesDate$year

8.3 模型设计建议

  1. 事实表:

    • 包含业务事实度量(销售额、数量等)
    • 包含指向维度表的外键
    • 粒度要明确(如订单行级、订单级)
  2. 维度表:

    • 包含描述性属性
    • 使用代理键(surrogate key)作为主键
    • 维度表一般不定义度量
  3. 星型模型 vs 雪花模型:

    • 星型模型:维度表不嵌套,查询性能更好(推荐)
    • 雪花模型:维度表嵌套,节省存储空间,需要时使用

9. 高级特性

9.1 扩展数据

使用 extData 存储自定义元数据:

{
    name: 'FactSalesModel',
    caption: '销售事实表',
    extData: {
        businessOwner: '销售部',
        updateFrequency: 'daily',
        customTag: 'core-metric'
    }
}

9.2 废弃标记

标记过时的模型或字段:

{
    name: 'oldSalesAmount',
    caption: '旧版销售金额',
    column: 'old_sales_amt',
    type: 'MONEY',
    deprecated: true  // 前端配置时会显示废弃提示
}

10. 向量模型

向量模型用于与 Milvus 等向量数据库集成,支持语义相似度检索。

10.1 基本结构

export const model = {
    name: 'DocumentSearchModel',
    caption: '文档检索模型',
    type: 'vector',                    // 指定为向量模型
    tableName: 'documents',            // Milvus 集合名称

    properties: [
        { column: 'doc_id', caption: '文档ID', type: 'BIGINT' },
        { column: 'title', caption: '标题', type: 'STRING' },
        { column: 'content', caption: '内容', type: 'STRING' },
        { column: 'category', caption: '分类', type: 'STRING' },
        { column: 'embedding', caption: '向量', type: 'VECTOR' }  // 向量字段
    ],

    measures: []
};

10.2 关键配置

字段 说明
type: 'vector' 指定模型类型为向量模型
tableName Milvus 集合名称
type: 'VECTOR' 属性类型为向量字段

10.3 向量字段元数据

向量字段的维度(dimension)、索引类型(indexType)、度量类型(metricType)会自动从 Milvus 获取,无需在 TM 中手动配置。

10.4 向量模型限制


11. 预聚合 (preAggregations)

预聚合通过预先计算和存储聚合结果,显著提升大数据量场景下的查询性能。

11.1 基本结构

export const model = {
    name: 'FactSalesModel',
    tableName: 'fact_sales',

    preAggregations: [
        {
            name: 'daily_product_sales',
            caption: '按日-产品预聚合',
            tableName: 'preagg_daily_product_sales',
            priority: 80,
            enabled: true,

            dimensions: ['product', 'salesDate'],
            granularity: {
                salesDate: 'day'            // 时间维度粒度
            },

            measures: [
                { name: 'salesAmount', aggregation: 'SUM' },
                { name: 'salesQuantity', aggregation: 'SUM' },
                { name: 'salesAmount', aggregation: 'COUNT', columnName: 'sales_count' }
            ],

            filters: [
                { field: 'orderStatus', op: '=', value: 'COMPLETED' }
            ],

            refresh: {
                strategy: 'INCREMENTAL',
                schedule: '0 2 * * *',      // 每天凌晨2点
                watermarkColumn: 'salesDate$caption',
                lookbackDays: 3
            }
        }
    ],

    dimensions: [...],
    measures: [...]
};

11.2 预聚合字段

字段 类型 必填 说明
name string 是 预聚合名称(模型内唯一)
caption string 否 显示名称
tableName string 是 预聚合表名
schema string 否 数据库 schema(默认使用主表 schema)
priority number 否 优先级 1-100(默认 50,越高越优先匹配)
enabled boolean 否 是否启用(默认 true)
dimensions string[] 是 包含的维度名称列表
granularity object 否 时间维度粒度配置
measures array 是 度量定义列表
filters array 否 永久过滤条件
refresh object 否 刷新配置

11.3 时间粒度 (granularity)

粒度值 说明
minute 分钟
hour 小时
day 天
week 周
month 月
quarter 季度
year 年

11.4 度量定义 (measures)

字段 类型 必填 说明
name string 是 引用 TM 中的度量名称
aggregation string 是 聚合方式(SUM/COUNT/MIN/MAX/AVG)
columnName string 否 预聚合表中的列名(默认 name_aggregation)

11.5 刷新配置 (refresh)

字段 类型 必填 说明
strategy string 否 FULL(全量)/ INCREMENTAL(增量),默认 FULL
schedule string 否 Cron 表达式
watermarkColumn string 否 水位线列名(增量刷新用)
lookbackDays number 否 回溯天数(处理迟到数据)

11.6 匹配规则

查询时系统自动匹配预聚合表,匹配条件:

  1. 查询涉及的维度是预聚合维度的子集
  2. 查询的时间粒度 >= 预聚合的时间粒度
  3. 查询的度量和聚合方式与预聚合兼容
  4. 查询条件不与预聚合的 filters 冲突

多个预聚合匹配时,优先使用 priority 较高的。


12. 访问控制 (accesses)

访问控制在 QM 中配置,但依赖 TM 中的维度定义实现行级/列级数据安全。

12.1 维度数据 SQL

在维度中定义 dimensionDataSql,用于动态生成数据权限 SQL:

{
    name: 'store',
    tableName: 'dim_store',
    foreignKey: 'store_key',
    primaryKey: 'store_key',
    captionColumn: 'store_name',

    // 根据当前用户返回可访问的维度数据SQL
    dimensionDataSql: (context) => {
        return `SELECT store_key FROM user_store_permission WHERE user_id = '${context.userId}'`;
    }
}

12.2 命名数据源

当不同模型需要连接不同数据库时,使用 dataSourceName:

export const model = {
    name: 'FactOrderModel',
    tableName: 'fact_orders',
    dataSourceName: 'orderDb',  // 引用配置中的命名数据源

    dimensions: [...],
    measures: [...]
};

dataSourceName 的优先级高于全局默认数据源。需在应用配置中注册对应的命名数据源。


下一步