StarRocks years_diff 函数详解:计算两个日期/时间表达式之间的年份差
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
本篇技术指南围绕 StarRocks 的years_diff日期时间函数展开,系统讲解其语法、参数约束、返回值语义与精确到月的取整逻辑。读者读完本文后,将掌握在 StarRocks 中使用years_diff计算两时间点年份差的正确写法,理解底层实现原理与边界情况(如年初未满整年、反向时间差、2 月 29 日等场景),并能结合源码定位与测试用例进行验证。
函数概述
years_diff是 StarRocks 提供的日期时间(date-time)函数之一,用于计算两个日期表达式之间的年份差值,结果为expr1 − expr2,精确到年。该函数属于 StarRocks 后端(BE)向量化表达式引擎中的时间函数族,声明位于 time_functions.h 第 533 行(DEFINE_VECTORIZED_FN(years_diff)),其文档位于 years_diff.md。
从源码结构看,years_diff与months_diff、quarters_diff属于同一族"差值"函数,在 time_functions.h 中与years_diff_v2一并注册,注释明确指出years_diff_v2等函数的语义与years_diff保持一致(见第 415-419 行)。years_diff常用于用户画像分析(如统计用户注册至今的年限)、账龄分桶、合约期限计算等按"整年"聚合的业务场景。
语法与参数
BIGINT years_diff(DATETIME expr1, DATETIME expr2);| 参数 | 说明 |
|---|---|
expr1 | 结束时间,必须是 DATETIME 或 DATE 类型 |
expr2 | 开始时间,必须是 DATETIME 或 DATE 类型 |
参数说明要点:
- 两个参数均支持
DATETIME与DATE类型。当传入DATE时,其隐含的时间部分被视为00:00:00,如years_diff('2010-11-30', '2000-11-1')等价于比较两个零点时刻。 - 函数计算方向为
expr1 − expr2,即返回"结束时间相对开始时间过去了多少整年"。若expr1早于expr2,返回负值。 - 返回值类型固定为
BIGINT,用于承载年份差值(可能为负数)。
返回值语义
函数返回BIGINT类型,语义为:
- 正向差值:
expr1晚于expr2时返回正数,表示满的整年数; - 负向差值:
expr1早于expr2时返回负数; - 未满一年:若
expr1与expr2之间的实际间隔不足一个完整年份(即月份或日期尚未到达对应位置),结果会向零取整(truncate toward zero),不产生小数部分; - 非法日期:如果输入的日期本身不存在(例如
2022-02-29,2022 年非闰年),函数返回NULL。
使用示例
带时间部分的 DATETIME 参数
select years_diff('2010-11-30 23:59:59', '2000-11-1 23:59:59'); +---------------------------------------------------------+ | years_diff('2010-11-30 23:59:59', '2000-11-1 23:59:59') | +---------------------------------------------------------+ | 10 | +---------------------------------------------------------+仅日期部分的 DATE 参数
select years_diff('2010-11-30', '2000-11-1'); +---------------------------------------+ | years_diff('2010-11-30', '2000-11-1') | +---------------------------------------+ | 10 | +---------------------------------------+边界与反向差值的直观验证
结合源码实现,可以推演出以下典型边界结果,读者可直接在 StarRocks 中执行验证:
| 查询 | 预期结果 | 说明 |
|---|---|---|
years_diff('2021-01-01', '2021-03-02') | 0 | 同一年内不足一整年,取整为 0 |
years_diff('2021-03-02', '2021-01-01') | 0 | 反向仍不足一年 |
years_diff('2021-12-31', '2021-01-01') | 0 | 同年内即使跨 11 个月仍为 0 |
years_diff('2023-01-01', '2021-03-02') | 1 | 跨年但未满两年,取整为 1 |
years_diff('2021-03-02', '2023-01-01') | -1 | 反向差值返回负数 |
底层实现原理(源码级分析)
years_diff的核心实现位于 time_functions.cpp 的years_diffImpl(第 1211-1233 行),并通过DEFINE_TIME_BINARY_FN(years_diff, TYPE_DATETIME, TYPE_DATETIME, TYPE_BIGINT)(第 1235 行)注册为接收两个DATETIME、返回BIGINT的二元向量化函数。
其计算逻辑可分解为三步:
- 拆解时间分量:调用
TimestampValue::to_timestamp将左右操作数分别拆解为年、月、日、时、分、秒、微秒(year1/2、month1/2、day1/2、hour1/2等)。 - 计算年份粗差值:
year = year1 - year2,得到仅按年份相减的初始结果。 - 按"月-日-时-分-秒-微秒"字典序修正取整:构造一个单调编码函数
func(month, day, hour, minute, second, usec),将年内的时刻整体编码为一个大整数;当year > 0时,若结束时刻的年内编码小于开始时刻的年内编码(说明结束时间在年内尚未到达开始时间的对应位置,即未满整年),则year减 1;当year < 0时做对称处理,若结束时刻的年内编码大于开始时刻则year加 1。这一修正即实现了"向零取整、不足一年不计"的语义。
// be/src/exprs/time_functions.cpp 第 1211-1233 行(核心逻辑摘录) DEFINE_BINARY_FUNCTION_WITH_IMPL(years_diffImpl, l, r) { int year1, month1, day1, hour1, minute1, second1, usec1; int year2, month2, day2, hour2, minute2, second2, usec2; l.to_timestamp(&year1, &month1, &day1, &hour1, &minute1, &second1, &usec1); r.to_timestamp(&year2, &month2, &day2, &hour2, &minute2, &second2, &usec2); int year = (year1 - year2); // 将月/日/时/分/秒/微秒编码为大整数,用于"未满整年"的字典序比较 const auto func = [](int month, int day, int hour, int minute, int second, int usec) -> int64_t { ... }; if (year > 0) { year -= (func(month1, day1, hour1, minute1, second1, usec1) < func(month2, day2, hour2, minute2, second2, usec2)); } else if (year < 0) { year += (func(month1, day1, hour1, minute1, second1, usec1) > func(month2, day2, hour2, minute2, second2, usec2)); } return year; }需要说明的是,上述实现按"年份差 + 年内时刻比较"完成整年取整,未对月份天数差异(如平年 2 月 28 日与闰年 2 月 29 日)做逐月逐日的特殊处理;更精细的"月末对齐"语义由years_diff_v2(第 1238-1291 行)承担,其内部使用DAYS_IN_MONTH数组结合闰年判断,对 2 月在平年/闰年的天数差异进行了大量边界分支处理(见第 1255-1286 行注释中的date_diff('year', '2017-02-28', '2016-02-29')等特殊场景)。当前仓库中years_diff_v2的注册与years_diff并存(见 time_functions.h 第 415-419 行),读者若需要对齐月末的年份差语义,可关注该变体。
单元测试验证
StarRocks 为years_diff提供了专项单元测试TimeFunctionsTest.yearsDiffTest,位于 time_functions_test.cpp 第 751-854 行,覆盖了以下关键场景:
- 未满整年返回 0:
2001-11-01 00:30:30与2000-12-01 00:30:30相差不足一年,断言结果为0; - 正向整年差:
2002-12-01与2000-11-01相差两年有余,断言结果为2; - 反向差值为负:
2000-11-01与2001-12-01(前者早于后者),断言结果为-1; - 同年时间对:
2021-01-01与2021-03-02、2021-01-01与2021-12-31等成对输入均断言为0,且注释明确"timestamps that share a year are less than one year apart in either direction"(同一自然年内的两个时间点,无论方向差均不足一年); - 跨年部分年份向零取整:
2023-01-01与2021-03-02断言为1,反向断言为-1。
测试通过TimestampValue::create构造时间值、以TimestampColumn组织列数据,直接调用TimeFunctions::years_diff向量化入口,并以ColumnHelper::cast_to<TYPE_BIGINT>校验结果,从执行层印证了本文上述语义描述。
实践注意事项
- 参数顺序决定正负:函数语义是
expr1 − expr2,统计"距今多少整年"时应写成years_diff(now(), 起始时间),统计"距今负数"(未来时间)同理。 - DATE 与 DATETIME 混用:
DATE参数会按00:00:00参与比较,混用不会报错,但精确到秒的边界判断(如23:59:59)仅在DATETIME下有意义。 - 非法日期返回 NULL:如
2022-02-29(平年无此日),函数返回NULL,需在业务侧做好空值处理。 - 与同类函数的选型:同一差值函数族还包括
months_diff、quarters_diff以及语义对齐月尾的years_diff_v2,按月、按季度粒度计算时请选用对应函数,避免自行做乘法换算造成语义偏差。 - 整年取整特性:结果不会出现小数或四舍五入,任何不满一年的间隔一律截断为零;这与
datediff按天数取整、timediff返回精确时长的语义均不同,按需选用。
如需查看更多日期时间函数的完整清单与语法说明,可继续浏览 docs/en/sql-reference/sql-functions/date-time-functions 目录下的对应文档。
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考