引言
在BI领域,有一个长期被忽视但至关重要的技术问题:指标的计算逻辑应该用什么语言来定义?
SQL?太底层——SQL直接操作数据库表和字段,业务人员无法理解,且每次口径变更都需要逐个报表修改SQL脚本。Excel公式?太轻量——无法处理多维度交叉聚合、时间序列计算、复杂条件分支。自然语言?太模糊——"同比增长率"这种描述无法精确到计算逻辑的每个细节。
衡石科技自研了HQL(Hengshi Query Language)——一种面向业务指标的声明式定义语言。HQL的设计目标是在"业务可理解"和"计算精确性"之间找到平衡点,让指标的计算逻辑既能被业务人员读懂,又能被引擎精确执行。这不是一个简单的技术选择,而是企业级BI数据建模的基础设施设计。
本文将从设计原理、语法体系、工程实践、与SQL的关系四个维度,深度解析HQL指标定义语言的技术内涵。
一、HQL的设计哲学
1.1 三大设计原则
原则一:业务优先
HQL使用业务术语而非数据库字段名。一个HQL表达式中的sales_amount代表的是业务概念"销售额",而非数据库中的t_order.total_price字段。这一设计使得指标定义可以被业务人员直接理解,而不需要理解底层数据表结构。
业务优先原则的深层含义是:指标的定义应该脱离物理数据层的实现细节,在语义层面描述"计算什么"而非"怎么计算"。当底层的数据表结构变更时(如字段重命名、表拆分合并),HQL定义不需要修改——只需要更新数据集的字段映射。
原则二:声明式定义
HQL采用声明式而非命令式的定义方式。用户声明"我想要什么"(如SUM(order_net_price)),HQL引擎负责将其转化为"怎么做"的执行计划(如对应的SQL查询语句、聚合路径、Join策略)。
声明式定义的核心优势是"关注点分离"——指标的定义者关注业务逻辑,执行优化的复杂度由引擎处理。当数据引擎的性能优化策略变化时(如从全表扫描优化为索引扫描),HQL定义不需要修改。
原则三:维度无关
HQL的指标定义不绑定特定维度组合。一个原子指标SUM(order_net_price)的定义中不包含任何维度过滤条件——它定义的是"销售额的计算逻辑",而非"华东区的销售额"或"月度销售额"。
维度无关原则的核心价值是"最大复用性"——一个原子指标可以被无数个业务指标引用,每个业务指标绑定不同的维度组合。当新增一个分析维度时,只需要创建新的业务指标引用现有原子指标,不需要修改原子指标的定义。
1.2 HQL与传统指标定义方式的对比
维度 | SQL片段 | Excel公式 | HQL |
表达对象 | 数据库表和字段 | Excel单元格引用 | 业务指标和维度 |
使用角色 | 数据工程师 | 业务人员 | 业务分析师/业务运营专家 |
维度处理 | WHERE条件硬编码 | 手动筛选 | 维度参数化、动态组合 |
计算口径 | 每次查询手动定义 | 每个表格手动定义 | 一次定义、全局复用 |
可维护性 | 散落在各报表中 | 散落在各Excel中 | 集中管理、版本控制 |
多引擎适配 | 需要为每种引擎编写不同SQL | N/A | 方言适配器自动转换 |
二、HQL的语法体系
2.1 基础表达式
HQL的基础表达式由数据集字段和聚合函数组成:
简单聚合
// 销售额:订单净价的求和 measure sales_amount = SUM(order_net_price) // 订单量:订单ID的计数 measure order_count = COUNT(order_id) // 客单价:销售额除以订单量 measure avg_order_value = sales_amount / order_count
简单聚合表达式是HQL最基础的形态——定义在单一数据集上的聚合计算,结果随用户选择的维度动态变化。
条件聚合
// 退款订单量:满足退款条件的订单计数 measure refund_order_count = COUNT_IF(order_status = 'refunded', order_id) // 高价值客户数:累计消费超过10万的客户计数 measure high_value_customer_count = COUNT_IF(SUM(order_amount) > 100000, customer_id)
条件聚合通过IF和COUNT_IF等条件函数实现——根据业务条件动态筛选聚合范围。
2.2 时间序列表达式
时间序列计算是BI分析的核心需求,HQL提供了丰富的时序函数:
同比与环比
// 同比增长率:本期销售额相对去年同期销售额的增长率 metric yoy_growth = (sales_amount - lag(sales_amount, 12)) / lag(sales_amount, 12) // 环比增长率:本期销售额相对上期销售额的增长率 metric mom_growth = (sales_amount - lag(sales_amount, 1)) / lag(sales_amount, 1)
lag函数用于获取历史时间点的指标值——lag(sales_amount, 12)表示12个周期前的销售额值。lag函数的"周期"由指标的粒度声明决定:如果粒度是月度,则lag(x, 12)表示12个月前;如果粒度是日度,则表示12天前。
累计与移动平均
// 年度累计销售额 metric ytd_sales = running_sum(sales_amount) // 7日移动平均销售额 metric ma7_sales = avg(sales_amount, 7)
running_sum实现从周期开始到当前的累计计算,avg函数配合窗口参数实现移动平均。
时间偏移
// 上月销售额 measure last_month_sales = offset(sales_amount, -1, 'month') // 去年同期销售额 measure last_year_same_period = offset(sales_amount, -12, 'month')
offset函数用于获取相对于当前时间点的历史或未来值——正数表示未来,负数表示历史。
2.3 复合表达式
HQL支持将多个指标组合为更复杂的计算逻辑:
比率指标
// 毛利率 measure gross_margin = gross_profit / sales_amount // 库存周转率 measure inventory_turnover = cost_of_goods_sold / avg(inventory_value)
比率指标通过两个指标的除法实现——分子和分母各自独立聚合,最后计算比率。这一设计确保了:即使分子和分母的维度组合不同,比率的计算结果仍然正确(如"华东区毛利率"中,毛利润和销售额都按华东区维度聚合后再相除)。
排名表达式
// 区域销售额排名 measure region_sales_rank = rank() over(partition by region order by sales_amount desc) // 品类销售额占比 measure category_sales_pct = sales_amount / sum(sales_amount) over(partition by category)
排名表达式通过窗口函数实现——rank()计算排名,sum() over(partition by)计算分组内占比。窗口函数的引入使得HQL可以表达"排名"、"占比"、"累计占比"等常见业务分析需求。
2.4 粒度声明
HQL的粒度声明是指标管理精确化的重要工具——它定义了指标计算的聚合层次:
// 华东区月度销售额——粒度:按区域、按月 metric east_monthly_sales = sales_amount[region='华东'] [granularity='region,month'] // 全国日度GMV——粒度:按日 metric national_daily_gmv = sales_amount[granularity='day']
粒度声明的核心价值是"避免聚合冲突"——当同一仪表盘中同时展示"按门店按日"和"按区域按月"的销售额时,粒度声明确保两个指标各自使用正确的聚合路径,不会产生计算冲突。
2.5 自定义函数
对于行业特有的计算需求,HQL支持用户注册自定义函数:
// 零售行业的"坪效"指标——单位面积销售额 measure sales_per_sqm = sales_amount / store_area // store_area是自定义函数返回的门店面积 // 金融行业的"夏普比率"——风险调整后收益 measure sharpe_ratio = (return_rate - risk_free_rate) / stddev(return_rate, 252)
自定义函数的注册通过HQL的扩展机制实现——数据团队编写自定义函数的计算逻辑(通常为Python或Java),注册到HQL引擎后即可在HQL表达式中使用。自定义函数的使用方式与内置函数完全一致,业务人员无需关心底层实现。
三、HQL引擎的执行机制
3.1 从HQL到SQL的转化流程
HQL引擎在运行时将HQL表达式转化为对应数据引擎的SQL查询。这一转化流程分为四个步骤:
Step 1:表达式解析
HQL引擎解析表达式,构建抽象语法树(AST)。AST中包含了指标引用、函数调用、维度条件、粒度声明等所有语义信息。
Step 2:语义验证
引擎验证表达式的语义正确性:
指标引用是否指向已定义的指标
函数调用的参数类型是否匹配
维度条件是否在数据集中存在
粒度声明是否与指标的维度兼容
语义验证未通过时,引擎返回明确的错误信息(如"指标'sales_amount'未定义"或"维度'region'在数据集中不存在"),帮助数据团队快速定位问题。
Step 3:查询计划生成
引擎根据AST和当前查询的维度上下文,生成查询执行计划:
确定需要Join的数据集和Join条件
确定聚合层次和分组字段
确定过滤条件的注入位置
选择执行优化策略(如预计算命中、索引使用)
Step 4:SQL生成与方言适配
引擎将查询计划转化为目标数据引擎的SQL语句,通过方言适配器处理引擎间的语法差异。
3.2 动态聚合的执行逻辑
HQL引擎的核心能力是"动态聚合"——同一指标在不同维度组合下的聚合计算:
场景:用户查询"按区域查看销售额"
引擎解析维度上下文:维度=region
引擎查询指标的HQL定义:
measure sales_amount = SUM(order_net_price)引擎生成聚合SQL:
SELECT region, SUM(order_net_price) FROM orders GROUP BY region方言适配器将SQL转换为目标引擎的语法
数据引擎执行SQL,返回各区域的销售额
场景:用户下钻至"按区域、按门店查看销售额"
引擎解析新的维度上下文:维度=region, store
引擎查询同一指标的HQL定义(定义未变)
引擎生成新的聚合SQL:
SELECT region, store, SUM(order_net_price) FROM orders GROUP BY region, store方言适配和执行如前
整个过程的核心是:HQL定义不变,维度组合在查询时动态决定。这就是"维度无关"原则在执行层面的体现——指标的定义与维度解耦,维度组合由用户的查询行为动态确定。
四、HQL的工程实践
4.1 指标定义的命名规范
良好的命名规范是HQL工程实践的基础:
原子指标命名
采用"业务概念_度量类型"的命名模式:
sales_amount(销售额_金额)order_count(订单_数量)customer_count(客户_数量)avg_order_value(平均_订单_价值)
业务指标命名
采用"维度条件_指标_粒度"的命名模式:
east_monthly_sales(华东_月度_销售额)national_daily_gmv(全国_日度_GMV)top10_store_sales(TOP10_门店_销售额)
语义标注规范
每个指标的语义标注应包含:
标准名称:正式的业务术语
别名列表:常见的同义词和缩写
业务描述:1-2句话说明指标的业务含义
使用场景标注:该指标常用于哪些分析场景
4.2 指标复用与组合模式
HQL的指标复用模式是工程效率的关键:
基础指标层
定义最底层的原子指标——通常10-20个核心指标,覆盖企业的主要业务度量。这些指标是所有业务指标的基础,定义质量要求最高。
派生指标层
基于基础指标的简单变换——如同比、环比、累计、移动平均。派生指标引用基础指标,不直接操作数据集字段。
组合指标层
基于多个指标的复杂计算——如毛利率、库存周转率、夏普比率。组合指标引用基础指标和派生指标,形成指标的层级结构。
这种三层结构的核心价值是"变更传播"——当基础指标的口径变更时,所有引用它的派生指标和组合指标自动更新,不需要逐个修改。
4.3 指标定义的质量管理
口径一致性检查
当新指标的定义与已有指标存在口径重叠时,系统自动检测并提示。如新定义net_sales = SUM(order_net_price)时,如果已有gross_sales = SUM(order_gross_price),系统提示两个指标的命名相似但计算口径不同,建议在语义标注中明确区分。
维度覆盖度检查
系统检查每个指标的维度覆盖度——该指标在哪些维度上的聚合是有效的,哪些维度上的聚合可能产生误导。如"库存周转率"按"门店"维度聚合是有效的,但按"客户"维度聚合可能没有业务意义。
性能影响评估
对于复杂指标(特别是多层嵌套的窗口函数和自定义函数),系统评估其查询性能影响。如果某指标的典型查询响应时间超过阈值,系统建议优化——如增加预计算、简化表达式、拆分为多个简单指标。
五、HQL与Agentic BI的协同
5.1 HQL是Text2Metrics的技术基石
Text2Metrics架构的核心是"将自然语言映射至指标语义层"——而指标语义层的内容就是HQL定义的指标。HQL的定义质量直接决定了Text2Metrics的推理准确率:
口径精确性:HQL表达式的计算逻辑必须精确无误——一个错误的HQL定义会导致所有引用它的ChatBI查询返回错误结果
维度完整性:HQL定义中的维度覆盖度决定了ChatBI可以支持哪些维度组合的查询——缺失的维度组合无法通过ChatBI查询
语义标注质量:HQL指标附带的语义标注(别名、描述、使用场景)决定了ChatBI语义匹配的准确率
5.2 HQL支持ChatBI的下钻推理
ChatBI Agent在下钻推理时,需要理解维度的层级关系——而维度层级关系在HQL的粒度声明和维度定义中:
// 维度层级定义 dimension store_hierarchy = { level1: store, // 门店 level2: city, // 城市 level3: region, // 区域 level4: country // 全国 }
当用户查询"华东区销售额"后追问"哪个门店最高"时,Agent通过维度层级定义理解"华东区→门店"是有效的下钻路径,自动生成按门店维度下钻的查询。
六、HQL的演进方向
6.1 AI辅助HQL生成
随着大模型能力的增强,HQL的生成方式正在从"人工编写"向"AI辅助"演进:
自然语言转HQL:业务人员用自然语言描述指标需求(如"华东区月度销售额同比增长率"),AI自动生成HQL表达式,经人工审核后发布
指标建议:AI基于数据集的字段和已有指标,自动推荐可能需要的新指标定义
口径检测:AI自动检测新指标与已有指标之间是否存在口径冲突
6.2 HQL的跨引擎一致性保障
随着企业使用多种数据引擎的趋势增强,HQL的跨引擎一致性变得尤为重要。衡石正在持续扩展方言适配器的覆盖范围,确保同一HQL定义在不同引擎上的计算结果完全一致——这包括数据类型转换、空值处理、舍入规则等细节层面的对齐。
结语
HQL不是一个"又一个查询语言"——它是企业级BI数据建模的基础设施。它的核心价值不在于"语法有多强大",而在于"让指标的定义从分散走向集中、从一次性走向可复用、从IT专属走向业务可理解"。
当企业从"每个报表手动写SQL"进化为"语义层集中定义HQL",从"口径变更逐报表修改"进化为"修改原子指标、自动传播",从"ChatBI回答不确定"进化为"基于HQL语义层的精确推理"——数据建模就真正从"技术工作"进化为"业务资产"。
好的指标定义语言不是让数据工程师更方便的工具,而是让业务人员可以理解和参与的数据资产建设基础设施。这正是HQL设计的终极目标。