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否模型类型,默认 jdbc、mongo、vector
deprecatedboolean否标记为废弃,默认 false
dataSourceNamestring否命名数据源引用,支持不同模型使用不同数据源
autoLoadDimensionsboolean否自动加载维度定义
autoLoadMeasuresboolean否自动加载度量定义
preAggregationsarray否预聚合配置列表,详见第11章

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

DAY vs DATETIME:DAY 仅包含日期部分(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

¹ tableName 和 viewSql 二选一,优先使用 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)

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

3.4 属性字段说明 ​

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

3.5 公式定义 (formulaDef) ​

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

builder 和 value 二选一,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,避免浮点精度问题
  • 主键字段:代理键用 INTEGER 或 BIGINT,业务键用 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 操作
  • 仅支持 similar 和 hybrid 操作符进行检索
  • 查询结果包含 _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) ​

字段类型必填说明
strategystring否FULL(全量)/ INCREMENTAL(增量),默认 FULL
schedulestring否Cron 表达式
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 的优先级高于全局默认数据源。需在应用配置中注册对应的命名数据源。


下一步 ​