Skip to content

TM 语法手册

下载本文档

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

1. 基本结构

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

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

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

1.1 模型基础字段

字段类型必填说明
namestring模型唯一标识,QM 中通过此名称引用
captionstring模型显示名称,建议填写,使用mcp时会传递给AI
descriptionstring模型详细描述,建议填写,使用mcp时会传递给AI
tableNamestring是¹对应的数据库表名、mongo集合名
viewSqlstring否¹视图SQL,与 tableName 二选一
schemastring数据库 Schema(跨 Schema 访问时使用)
idColumnstring主键列名
typestring模型类型,默认 jdbcmongovector
deprecatedboolean标记为废弃,默认 false

¹ tableNameviewSql 二选一,优先使用 tableName

1.2 AI 增强配置

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

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

2. 维度定义 (dimensions)

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

2.1 基本维度

javascript
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 维度字段说明

字段类型必填说明
namestring维度名称,查询时使用 维度名$属性名 格式引用
captionstring维度显示名称
descriptionstring维度详细描述
tableNamestring是¹关联的维度表名
viewSqlstring否¹维度视图SQL,与 tableName 二选一
schemastring维度表的 Schema
foreignKeystring事实表中的外键字段
primaryKeystring维度表的主键字段
captionColumnstring维度的显示字段,用于 维度名$caption
keyCaptionstring主键字段的显示名称,默认为 ${caption}Key
keyDescriptionstring主键字段的描述信息
typestring维度类型,如 DATETIME 表示时间维度
propertiesarray维度表中可查询的属性列表
forceIndexstring强制使用的索引名称

¹ tableNameviewSql 二选一,优先使用 tableName

2.3 嵌套维度(雪花模型)

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

javascript
{
    // 一级维度:产品(与事实表直接关联)
    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 中引用嵌套维度(三种等效写法):

javascript
// 写法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$captionproduct$caption
product.category$captionproduct_category$caption
product.category.group$captionproduct_category_group$caption

生成的 SQL JOIN

sql
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 父子维度(层级结构)

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

javascript
{
    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' }
    ]
}

父子维度专用字段

字段类型必填说明
closureTableNamestring闭包表名称
closureTableSchemastring闭包表 Schema
parentKeystring闭包表中的祖先列(如 parent_id
childKeystring闭包表中的后代列(如 team_id

闭包表结构示例

sql
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 基本属性

javascript
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 将数据库值映射为显示标签:

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

javascript
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 定义计算字段。常见场景包括 JSON 字段提取、字符串拼接等:

javascript
properties: [
    {
        column: 'send_addr_info',  // JSON 类型字段
        name: 'sendStreet',
        caption: '收货街道',
        description: '从地址 JSON 中提取街道信息',
        type: 'STRING',
        formulaDef: {
            builder: (alias) => {
                return `${alias}.send_addr_info ->> '$.send_street'`;
            },
            description: '提取收货地址中的街道字段'
        }
    },
    {
        column: 'customer_name',
        name: 'fullName',
        caption: '客户全名',
        type: 'STRING',
        formulaDef: {
            builder: (alias) => {
                return `CONCAT(${alias}.first_name, ' ', ${alias}.last_name)`;
            },
            description: '拼接姓和名'
        }
    }
]

3.4 属性字段说明

字段类型必填说明
columnstring数据库列名
namestring属性名称,默认为 column 的驼峰形式
aliasstring属性别名
captionstring显示名称
descriptionstring详细描述,若字段含义复杂,建议填写,有助于AI推断
typestring数据类型(见 5. 数据类型
formatstring格式化模板(用于日期等)
dictRefstring字典引用,用于值到标签的转换
formulaDefobject公式定义(见 3.5)

3.5 公式定义 (formulaDef)

字段类型说明
builderfunctionSQL 构建函数,参数 alias 为表别名
valuestring公式表达式(基于度量名称)
descriptionstring公式的文字描述

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


4. 度量定义 (measures)

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

4.1 基本度量

javascript
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 定义计算度量:

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

4.3 COUNT 聚合

不基于具体列的计数:

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

4.4 度量字段说明

字段类型必填说明
columnstring否¹数据库列名
namestring度量名称,默认为 column 的驼峰形式
aliasstring度量别名
captionstring显示名称
descriptionstring详细描述
typestring数据类型(见 5. 数据类型
aggregationstring聚合方式(见 4.5)
formulaDefobject公式定义(见 3.5)

¹ count 聚合可以不指定 column

4.5 聚合方式

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

5. 数据类型

5.1 类型列表

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

5.2 类型选择建议

  • 金额字段:使用 MONEY,避免浮点精度问题
  • 主键字段:代理键用 INTEGERBIGINT,业务键用 STRING
  • 日期字段:时间戳用 DATETIME,仅日期用 DAY
  • 枚举字段:优先使用 dictRef + STRING,而非创建维度表

6. 完整示例

6.1 事实表模型

javascript
// 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 维度表模型

javascript
// 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 日期维度表模型

javascript
// 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 创建维度构建器

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

javascript
// 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 文件中使用维度构建器

javascript
// 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: [
        // ... 度量定义
    ]
};
javascript
// 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 高级技巧:属性扩展与覆盖

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

javascript
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 嵌套维度的复用

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

javascript
// 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 父子维度的复用

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

javascript
// 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 文件命名

  • TM 文件:{模型名}Model.tm
  • 事实表:Fact{业务名}Model.tm,如 FactSalesModel.tm
  • 维度表:Dim{业务名}Model.tm,如 DimCustomerModel.tm
  • 字典文件:dicts.fsscript

8.2 字段命名

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

8.3 模型设计建议

  1. 事实表

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

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

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

9. 高级特性

9.1 扩展数据

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

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

9.2 废弃标记

标记过时的模型或字段:

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

10. 向量模型

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

10.1 基本结构

javascript
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'指定模型类型为向量模型
tableNameMilvus 集合名称
type: 'VECTOR'属性类型为向量字段

10.3 向量字段元数据

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

10.4 向量模型限制

  • 不支持维度关联(dimensions)
  • 不支持 JOIN 操作
  • 仅支持 similarhybrid 操作符进行检索
  • 查询结果包含 _score 字段表示相似度

下一步