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
dataSourceNamestring命名数据源引用,支持不同模型使用不同数据源
autoLoadDimensionsboolean自动加载维度定义
autoLoadMeasuresboolean自动加载度量定义
preAggregationsarray预聚合配置列表,详见第11章

¹ 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(简单形式)
captionDefobjectCaption 高级定义(优先级高于 captionColumn),见下文
keyCaptionstring主键字段的显示名称,默认为 ${caption}Key
keyDescriptionstring主键字段的描述信息
typestring维度类型:NORMAL/DAY/DATETIME/DICT/BOOL/INTEGER/DOUBLE
propertiesarray维度表中可查询的属性列表
forceIndexstring强制使用的索引名称
aliasstring维度别名(重定义列名前缀)
deprecatedboolean标记为废弃
dimensionDataSqlfunction维度数据 SQL 函数(用于访问控制)
onBuilderfunction维度连接构建函数

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

captionDef 高级定义

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

javascript
{
    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

¹ 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 定义计算字段。builder 函数接收表别名,返回原生 SQL 表达式:

javascript
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: '将金额换算为万元'
        }
    }
]

方言注意

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

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

通用函数(CONCATCOALESCEROUND 等)在所有方言下可安全使用。

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 字段表示相似度

11. 预聚合 (preAggregations)

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

11.1 基本结构

javascript
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 预聚合字段

字段类型必填说明
namestring预聚合名称(模型内唯一)
captionstring显示名称
tableNamestring预聚合表名
schemastring数据库 schema(默认使用主表 schema)
prioritynumber优先级 1-100(默认 50,越高越优先匹配)
enabledboolean是否启用(默认 true)
dimensionsstring[]包含的维度名称列表
granularityobject时间维度粒度配置
measuresarray度量定义列表
filtersarray永久过滤条件
refreshobject刷新配置

11.3 时间粒度 (granularity)

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

11.4 度量定义 (measures)

字段类型必填说明
namestring引用 TM 中的度量名称
aggregationstring聚合方式(SUM/COUNT/MIN/MAX/AVG)
columnNamestring预聚合表中的列名(默认 name_aggregation

11.5 刷新配置 (refresh)

字段类型必填说明
strategystringFULL(全量)/ INCREMENTAL(增量),默认 FULL
schedulestringCron 表达式
watermarkColumnstring水位线列名(增量刷新用)
lookbackDaysnumber回溯天数(处理迟到数据)

11.6 匹配规则

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

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

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


12. 访问控制 (accesses)

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

12.1 维度数据 SQL

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

javascript
{
    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

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

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

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


下一步