Article body
正文
导读:在数据分析领域,“留存率”与”窗口函数”是衡量用户行为和进行复杂统计的两大核心武器。然而,传统SQL在处理连续留存、时间序列累计、跨维度计算等场景时,往往需要编写大量嵌套子查询,不仅性能堪忧,更让业务分析师望而却步。HENGSHI SENSE 构建了一套覆盖30+高级分析函数的完整体系,将复杂的窗口计算、留存分析、时间序列运算封装为简洁的函数调用。本文将从工程实现角度,逐一拆解这些函数的设计思想、语法规范与实战技巧。
一、为什么需要一套”高级分析函数”体系?
在企业级BI场景中,分析师面对的数据问题远不止”求和”与”计数”。以下几个典型场景,暴露了传统SQL分析能力的不足:
| 业务场景 | 传统SQL方案 | 痛点 |
|---|---|---|
| 连续7天活跃用户 | 多层嵌套自连接+窗口函数 | SQL复杂度高,性能随天数指数级下降 |
| 周期性累计增长(MTD/YTD) | CASE WHEN配合日期截断 | 代码冗长,维度切换需重写 |
| 同比/环比分析 | LAG函数+日期偏移 | 日期边界处理繁琐,易出错 |
| 非时间维度留存(如按渠道) | 无标准方案,需自定义 | 业务逻辑与SQL深度耦合 |
| 跨表非关联数据引用 | 子查询或LEFT JOIN | 维护成本高,语义不清晰 |
HENGSHI SENSE 的设计哲学是:将高频的分析模式抽象为一等公民(first-class)的函数,让分析师用”声明式”的方式描述”要什么”,而非”怎么算”。这套体系分为四大阵营:
- 时间序列函数——处理与时间相关的连续、累计、对比、移动计算
- 数据处理函数——实现跨上下文计算、维度操控、数据查找
- 聚合函数——从基础统计到高级分布分析的完整工具箱
- 图表高级计算——在可视化组件中直接调用的分析能力
接下来,我们逐一深入。
二、时间序列函数:让时间分析从”手写SQL”到”函数调用”
2.1 留存与活跃计算:retention()
留存分析是用户增长领域最核心的指标之一。HENGSHI SENSE 提供了 retention() 函数,支持在时间维度下计算用户的留存与活跃情况。
函数签名:
retention(
expression, -- 待计算的表达式(通常为用户ID)
date expression, -- 日期字段
lowExpr, -- 留存区间的起始偏移
upperExpr, -- 留存区间的结束偏移
includeStartExpr, -- 是否包含起始日(可选)
includeEndExpr, -- 是否包含结束日(可选)
excludeStartExpr, -- 是否排除起始日(可选)
excludeEndExpr -- 是否排除结束日(可选)
)
实战示例:计算”注册后第1~7天留存”
-- 计算注册日在2024年1月,且在第1到第7天内有活跃行为的用户留存数
retention(
distinct_count(user_id),
activity_date,
1, -- 从第1天开始
7, -- 到第7天结束
true, -- 包含第1天
true -- 包含第7天
)
该函数的底层实现思路值得深究:它将传统的”自连接+条件筛选”模式,转化为一次性的分区扫描计算。在引擎层,HENGSHI SENSE 会自动识别日期分区,生成最优的执行计划,避免了对原始数据的多次扫描。
2.2 连续留存:continuous_retention()
与普通留存不同,连续留存要求用户在指定区间内每天都活跃,这对实现的要求更高。
函数签名:
continuous_retention(
expression, -- 待计算的表达式
date expression, -- 日期字段
lowExpr, -- 连续区间的起始偏移
upperExpr -- 连续区间的结束偏移
)
实战示例:计算连续3天活跃用户数
continuous_retention(
distinct_count(user_id),
activity_date,
1, -- 从第1天开始
3 -- 连续到第3天
)
工程上,连续留存的实现比普通留存复杂得多。传统方案通常使用 ROW_NUMBER() + GROUP BY 的”岛屿与间隙(Islands and Gaps)“算法,而 HENGSHI SENSE 在引擎层对其进行了优化,通过一次分组聚合即可完成连续性判定,大幅降低了计算复杂度。
2.3 累计计算:date_accumulate()
累计计算(YTD/MTD/QTD等)是财务报表和经营分析中的高频需求。date_accumulate() 函数可以计算指定时间范围内的累计值。
函数签名:
date_accumulate(
function1(function2(expression)), -- 嵌套函数表达式
date expression1, -- 日期字段
reset period -- 重置周期(如 'year', 'quarter', 'month')
)
实战示例:年初至今累计销售额
date_accumulate(
sum(sales_amount),
order_date,
'year' -- 按年重置,实现YTD累计
)
当 reset period 设置为 'month' 时,即为MTD(Month to Date)累计;设置为 'quarter' 时,即为QTD累计。这种统一的接口设计,让分析师可以通过一个参数切换不同的累计周期,无需重写任何逻辑。
2.4 同期对比:date_compare()
同比分析(YoY)是衡量业务增长趋势的关键指标。date_compare() 函数直接计算数据相对于指定日期的同期对比值。
函数签名:
date_compare(
function(expression), -- 待计算的指标
date expression1, -- 当前日期
date expression2 -- 对比的基准日期
)
实战示例:今年vs去年同期销售额对比
date_compare(
sum(sales_amount),
current_date,
same_period_last_year --去年同期
)
2.5 移动计算:date_shift()
移动平均(MA)、移动求和等滑动窗口计算在时间序列分析中无处不在。date_shift() 函数专门用于处理这类时间序列移动统计。
函数签名:
date_shift(
function1(function2(expression)), -- 嵌套函数表达式
date expression, -- 日期字段
lowExpr, -- 窗口起始偏移
upperExpr -- 窗口结束偏移
)
实战示例:7日移动平均GMV
date_shift(
avg(gmv),
trade_date,
-6, -- 前6天
0 -- 当天(共7天窗口)
)
该函数的本质是一个时间维度的滑动窗口,但在语义上比原生SQL的 ROWS BETWEEN 子句更加直观。lowExpr 和 upperExpr 的偏移量基于日期(而非行数),避免了因数据稀疏导致的窗口变形问题。
2.6 环比计算:previous()
环比(MoM)用于衡量相邻两个周期之间的变化率。previous() 函数可以直接获取相对于指定日期的环比值。
函数签名:
previous(
function(expression), -- 待计算的指标
period, -- 周期类型
delta -- 偏移量
)
实战示例:上月销售额及环比增长率
-- 获取上月销售额
previous(sum(sales_amount), 'month', 1)
-- 环比增长率可组合使用
(sum(sales_amount) - previous(sum(sales_amount), 'month', 1))
/ previous(sum(sales_amount), 'month', 1)
2.7 非时间维度版本:static_* 系列函数
在许多业务场景中,留存和活跃的计算不一定基于时间维度。例如,按”渠道”、“产品版本”等维度分析留存。HENGSHI SENSE 提供了三个对应的静态版本:
| 函数 | 说明 |
|---|---|
static_retention(expr, dim, low, upper) | 非时间维度的留存计算 |
static_continuous_retention(expr, dim, low, upper) | 非时间维度的连续留存计算 |
static_retention_rate(expr, dim, low, upper) | 非时间维度的留存率计算 |
实战示例:按渠道计算用户留存
static_retention(
distinct_count(user_id),
channel_name, -- 按渠道分组
1,
7
)
这套”时间维度/非时间维度”的对称设计,体现了 HENGSHI SENSE 对分析场景的全面覆盖——无论你的维度是日期、渠道还是产品线,都可以使用统一的函数接口。
三、数据处理函数:突破维度与上下文的计算边界
如果说时间序列函数解决的是”何时”的问题,那么数据处理函数解决的就是”如何”的问题——如何突破当前的维度上下文,如何在不同的数据粒度间切换计算。
3.1 上下文操控:calculate() / calculatep() / calculatex()
这三个函数构成了 HENGSHI SENSE 的上下文计算三件套,它们的功能相似但适用场景各有侧重。
函数签名:
calculate(ARG1, [ARG2], ...) -- 预计算数据并与原始数据一起计算
calculatep(ARG1, [ARG2], ...) -- 不受小计影响
calculatex(ARG1, [ARG2], ...) -- 不受维度控制
三个函数的核心区别:
| 函数 | 小计影响 | 维度控制 | 典型场景 |
|---|---|---|---|
calculate | 受影响 | 受控制 | 通用预计算 |
calculatep | 不受影响 | 受控制 | 需要排除小计干扰的计算 |
calculatex | 受影响 | 不受控制 | 需要忽略当前维度上下文的计算 |
实战示例:计算各品类占全渠道的百分比
-- 使用 calculate 获取全渠道总额
sum(sales_amount) / calculate(sum(sales_amount))
实战示例:跨品类计算(忽略当前品类维度)
-- 使用 calculatex 计算所有品类的总平均值
calculatex(avg(sales_amount))
3.2 跨表数据引用:lookupvalue()
在复杂的数据模型中,有时需要从其他非直接关联的数据表中获取值。lookupvalue() 函数提供了类似Excel VLOOKUP的能力。
函数签名:
lookupvalue(ARG1, ARG2, ARG3)
ARG1:目标表中要返回的表达式ARG2:目标表中用于匹配的字段ARG3:当前表中用于匹配的值
实战示例:从地区维度表获取区域经理姓名
lookupvalue(manager_name, region_id, current_region_id)
3.3 部分维度聚合:rollup_value() OVER()
这是 HENGSHI SENSE 6.1.0 版本新增的重要函数,支持基于部分维度计算指标,是处理层级聚合和多粒度分析的利器。
函数签名:
rollup_value(ARG) OVER(
[ PARTITION BY expr1 [, expr2, ...] ]
)
实战示例:计算各省在其所属大区中的占比
-- 按大区维度进行部分聚合
rollup_value(sum(sales_amount)) OVER(PARTITION BY region)
OVER(PARTITION BY ...) 的语法与标准SQL窗口函数一致,降低了学习成本。在底层实现上,HENGSHI SENSE 的加速引擎会根据数据源类型智能选择执行策略——对于支持窗口函数的数据库(如PostgreSQL、Oracle),直接下推执行;对于不支持的数据库(如MySQL 5.7),则在引擎层进行内存计算。
3.4 Top/Bottom N 过滤
top_n(expression, n) -- 保留前N个值
bottom_n(expression, n) -- 保留后N个值
实战示例:只展示销售额排名前10的产品
top_n(sum(sales_amount), 10)
3.5 自定义过滤计算:custom_filters()
custom_filters() 允许在计算表达式中嵌入自定义的过滤条件,实现”条件聚合”的效果。
实战示例:只计算特定品类的销售额
custom_filters(
sum(sales_amount),
category = '电子产品'
)
四、聚合函数:从基础统计到高级分布分析
HENGSHI SENSE 提供了完整的聚合函数工具箱,覆盖了从基础统计到高级分析的各个层次。
4.1 数学统计函数
这些是日常分析中使用频率最高的聚合函数:
| 函数 | 说明 | 示例 |
|---|---|---|
avg(expr) | 算术平均值 | avg(sales_amount) |
count(expr) | 计数 | count(order_id) |
distinct_count(expr) | 去重计数 | distinct_count(user_id) |
sum(expr) | 求和 | sum(revenue) |
min(expr) | 最小值 | min(price) |
max(expr) | 最大值 | max(price) |
median(expr) | 中位数 | median(income) |
percentile(expr, n) | 百分位数 | percentile(response_time, 95) |
mode(expr) | 众数 | mode(page_view) |
4.2 统计相关函数
对于需要进行深度统计分析的场景,HENGSHI SENSE 提供了完整的相关性分析和方差分析函数:
| 函数 | 说明 |
|---|---|
corr(expr1, expr2) | 皮尔逊相关系数 |
covar_pop(expr1, expr2) | 总体协方差 |
covar_samp(expr1, expr2) | 样本协方差 |
stddev_pop(expr) | 总体标准差 |
stddev_samp(expr) | 样本标准差 |
var_pop(expr) | 总体方差 |
var_samp(expr) | 样本方差 |
regr_slope(expr1, expr2) | 线性回归斜率 |
regr_intercept(expr1, expr2) | 线性回归截距 |
regr_r2(expr1, expr2) | R平方值 |
regr_count(expr1, expr2) | 回归中非空对的数量 |
实战示例:分析广告投放金额与转化率的相关性
corr(ad_spend, conversion_rate)
实战示例:构建用户收入与消费金额的线性回归模型参数
regr_slope(consumption, income) -- 斜率
regr_intercept(consumption, income) -- 截距
regr_r2(consumption, income) -- 拟合优度
4.3 位置获取函数
在排序场景中,经常需要获取特定位置的数据值:
| 函数 | 说明 | 示例 |
|---|---|---|
first(expr) | 获取第一个值 | first(login_time) |
last(expr) | 获取最后一个值 | last(logout_time) |
nth(expr, n) | 获取第N个值 | nth(score, 3) |
max_by(expr1, expr2) | 获取使expr2最大的expr1值 | max_by(product_name, sales) |
min_by(expr1, expr2) | 获取使expr2最小的expr1值 | min_by(product_name, return_rate) |
实战示例:获取每个品类中销量最高的产品名称
max_by(product_name, sales_amount)
这个函数的设计非常巧妙——它将”排序+取值”两个步骤合并为一个原子操作,在语义上更加清晰,在执行上避免了中间结果的物化。
4.4 聚合收集函数
将多个值收集为集合或字符串,适用于标签聚合、列表展示等场景:
| 函数 | 说明 | 示例 |
|---|---|---|
list_collect(expr) | 收集为列表(保留重复) | list_collect(tag) |
list_collect_flatten(expr) | 收集为列表并展平嵌套数组 | list_collect_flatten(tags_array) |
set_collect(expr) | 收集为集合(去重) | set_collect(category) |
set_collect_flatten(expr) | 收集为集合并展平 | set_collect_flatten(categories) |
string_agg(expr, delimiter) | 字符串聚合 | string_agg(name, ', ') |
4.5 FILTER 子句与 WITHIN GROUP 排序子句
HENGSHI SENSE 支持在聚合函数中使用 FILTER 子句进行条件过滤,以及 WITHIN GROUP 子句进行排序控制。
FILTER 子句示例:
-- 只计算已完成订单的平均金额
avg(order_amount) FILTER(status = 'completed')
-- 只统计VIP用户的去重数
distinct_count(user_id) FILTER(user_level = 'VIP')
WITHIN GROUP 排序子句示例:
-- 获取按金额降序排列的第二个订单
nth(order_id, 2) WITHIN GROUP(ORDER BY order_amount DESC)
-- 获取最早的登录时间
first(login_time) WITHIN GROUP(ORDER BY login_time ASC)
FILTER 子句的引入,使得”条件聚合”不再需要 CASE WHEN 包裹,代码更加简洁。在底层,HENGSHI SENSE 会将其优化为对应的数据库原生语法(如PostgreSQL的 FILTER (WHERE ...)),确保执行效率。
五、图表高级计算:可视化中的分析能力
除了在查询层使用函数,HENGSHI SENSE 还将高级分析能力直接集成到图表组件中。用户无需编写代码,通过配置即可实现以下分析:
| 计算类型 | 说明 | 适用场景 |
|---|---|---|
| 百分比 | 计算各项占总体的比例 | 市场份额、构成分析 |
| 百分位数 | P50/P75/P90/P95等分位值 | 性能监控、用户分层 |
| 同比/环比 | 年同比、月环比等 | 趋势分析、增长追踪 |
| 累计 | YTD/MTD/QTD累计 | 财务报表、目标进度 |
| 移动 | 滑动窗口均值/求和 | 数据平滑、趋势识别 |
| 重复 | 重复购买/访问计算 | 用户忠诚度分析 |
| 留存 | N日留存率 | 用户生命周期管理 |
| 活跃 | DAU/MAU/WAU | 产品活跃度监控 |
| 窗口计算 | 灵活的分区聚合 | 复杂多维分析 |
在 6.2 版本中,HENGSHI SENSE 还新增了 KPI条件格式的非色阶方式支持,允许分析师使用自定义的图标、符号等非色阶方式来展示KPI状态,进一步丰富了数据可视化的表达手段。
六、多数据源兼容:一套函数,多种引擎
HENGSHI SENSE 的高级函数体系之所以能在企业级场景中落地,一个关键原因是其广泛的数据源兼容性。以下是官方支持的数据源清单:
| 数据源 | 支持状态 |
|---|---|
| HENGSHI Engine(自研加速引擎) | 完整支持 |
| PostgreSQL | 完整支持 |
| Oracle | 完整支持 |
| MySQL 8.0+ | 完整支持 |
| SQL Server 2017+ | 完整支持 |
| Amazon Redshift | 完整支持 |
| Apache Impala | 完整支持 |
| Presto/Trino | 完整支持 |
| Greenplum | 完整支持 |
| Apache Hive | 完整支持 |
| Spark SQL | 完整支持 |
| IBM DB2 | 完整支持 |
| MaxCompute | 完整支持 |
6.1 智能执行策略
不同数据库对高级分析语法的支持程度差异巨大。HENGSHI SENSE 在此采用了**“能下推就下推,不能下推就内存算”**的策略:
- 下推执行:对于原生支持窗口函数、FILTER子句等特性的数据库(如PostgreSQL、Oracle),直接将分析函数翻译为对应数据库的SQL语法,充分利用数据库的计算能力。
- 引擎层计算:对于不支持某些特性的数据库,HENGSHI SENSE 的加速引擎会在内存中完成计算。例如,MySQL 5.7不支持窗口函数,此时 HENGSHI SENSE 会将数据拉取到加速引擎层,使用自研的计算引擎完成窗口计算。
- 混合执行:部分查询条件下,HENGSHI SENSE 会将能下推的部分下推到数据库执行,将不能下推的部分留在引擎层执行,通过两阶段计算实现最优性能。
6.2 版本演进
HENGSHI SENSE 的高级函数体系在持续演进中:
- 6.1.0 版本:新增
ROLLUP_VALUE()函数和json_array_length()函数,进一步丰富了维度计算和JSON数据处理能力。ROLLUP_VALUE()的引入,填补了”部分维度聚合”场景的功能空白。 - 6.2 版本:KPI条件格式支持非色阶方式,让分析结果的展示更加灵活。
七、最佳实践与性能调优
7.1 函数选择指南
面对众多的分析函数,如何选择最合适的方案?
场景决策树:
需要分析用户留存?
├── 基于时间维度 → retention() / continuous_retention()
└── 基于其他维度 → static_retention() / static_continuous_retention()
需要做时间对比?
├── 同期对比(如去年同期) → date_compare()
├── 环比(如上月) → previous()
└── 滑动窗口 → date_shift()
需要突破当前维度计算?
├── 全局占比 → calculate()
├── 忽略小计影响 → calculatep()
└── 忽略所有维度 → calculatex()
需要获取特定位置的值?
├── 排序后的第一个/最后一个 → first() / last()
├── 排序后取最值对应的字段 → max_by() / min_by()
└── 按特定排名取值 → nth()
7.2 性能优化建议
- 优先使用内置函数而非自定义表达式:HENGSHI SENSE 的内置函数经过引擎层优化,通常比等效的SQL表达式性能更好。
- 合理设置时间窗口:
date_shift()和retention()中的窗口参数直接影响扫描的数据量。在满足业务需求的前提下,尽量缩小窗口范围。 - 利用 FILTER 子句替代 CASE WHEN:
FILTER子句在支持它的数据库上能生成更优的执行计划。 - 注意聚合粒度:
rollup_value() OVER(PARTITION BY ...)中的分区字段不宜过多,否则会导致分区碎片化,影响性能。 - 善用加速引擎:对于复杂的多表关联+窗口计算场景,建议使用 HENGSHI SENSE 的加速引擎,避免在源数据库上执行过重的查询。
7.3 常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 日期格式不一致 | retention() 等函数要求日期字段格式统一 | 在数据接入层进行日期格式标准化 |
| 空值处理 | 部分聚合函数遇到NULL会跳过,部分会返回NULL | 使用 coalesce() 进行空值替换 |
| 大偏移量性能下降 | date_shift() 的窗口过大时性能下降 | 评估是否真的需要如此大的窗口 |
| 维度冲突 | calculatex() 会忽略所有维度,可能导致意外结果 | 仔细确认是否真的需要忽略所有维度 |
八、总结
HENGSHI SENSE 的高级分析函数体系,本质上是对企业级数据分析中高频场景的系统化抽象。从本文的解析可以看出:
- 覆盖全面:30+函数覆盖了留存分析、时间序列、上下文计算、统计分布等核心分析场景,形成了一套完整的分析工具箱。
- 设计对称:时间维度与非时间维度的函数对称设计(如
retentionvsstatic_retention),降低了学习成本,提升了API的一致性。 - 多源兼容:通过智能执行策略,同一套函数可以在15+种数据源上运行,屏蔽了底层的技术差异。
- 持续演进:从6.1.0的
ROLLUP_VALUE()到6.2的KPI条件格式增强,这套体系在持续扩展边界。 - 工程导向:每个函数的设计都考虑了执行效率,通过下推策略、内存计算、混合执行等手段,确保分析性能不会成为瓶颈。
对于数据分析团队而言,掌握这套函数体系意味着:可以用更少的代码、更清晰的逻辑、更好的性能,解决更复杂的业务问题。这不仅是效率的提升,更是分析思维的升级——当你不再被”怎么写SQL”所困扰,就能将更多精力投入到”数据背后的业务洞察”之中。