StarRocks years_diff 函数详解:计算两个日期/时间表达式之间的年份差
2026/9/18 20:27:19 网站建设 项目流程

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_diffmonths_diffquarters_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 类型

参数说明要点:

  • 两个参数均支持DATETIMEDATE类型。当传入DATE时,其隐含的时间部分被视为00:00:00,如years_diff('2010-11-30', '2000-11-1')等价于比较两个零点时刻。
  • 函数计算方向为expr1 − expr2,即返回"结束时间相对开始时间过去了多少整年"。若expr1早于expr2,返回负值。
  • 返回值类型固定为BIGINT,用于承载年份差值(可能为负数)。

返回值语义

函数返回BIGINT类型,语义为:

  • 正向差值expr1晚于expr2时返回正数,表示满的整年数;
  • 负向差值expr1早于expr2时返回负数;
  • 未满一年:若expr1expr2之间的实际间隔不足一个完整年份(即月份或日期尚未到达对应位置),结果会向零取整(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的二元向量化函数。

其计算逻辑可分解为三步:

  1. 拆解时间分量:调用TimestampValue::to_timestamp将左右操作数分别拆解为年、月、日、时、分、秒、微秒(year1/2month1/2day1/2hour1/2等)。
  2. 计算年份粗差值year = year1 - year2,得到仅按年份相减的初始结果。
  3. 按"月-日-时-分-秒-微秒"字典序修正取整:构造一个单调编码函数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 行,覆盖了以下关键场景:

  • 未满整年返回 02001-11-01 00:30:302000-12-01 00:30:30相差不足一年,断言结果为0
  • 正向整年差2002-12-012000-11-01相差两年有余,断言结果为2
  • 反向差值为负2000-11-012001-12-01(前者早于后者),断言结果为-1
  • 同年时间对2021-01-012021-03-022021-01-012021-12-31等成对输入均断言为0,且注释明确"timestamps that share a year are less than one year apart in either direction"(同一自然年内的两个时间点,无论方向差均不足一年);
  • 跨年部分年份向零取整2023-01-012021-03-02断言为1,反向断言为-1

测试通过TimestampValue::create构造时间值、以TimestampColumn组织列数据,直接调用TimeFunctions::years_diff向量化入口,并以ColumnHelper::cast_to<TYPE_BIGINT>校验结果,从执行层印证了本文上述语义描述。

实践注意事项

  1. 参数顺序决定正负:函数语义是expr1 − expr2,统计"距今多少整年"时应写成years_diff(now(), 起始时间),统计"距今负数"(未来时间)同理。
  2. DATE 与 DATETIME 混用DATE参数会按00:00:00参与比较,混用不会报错,但精确到秒的边界判断(如23:59:59)仅在DATETIME下有意义。
  3. 非法日期返回 NULL:如2022-02-29(平年无此日),函数返回NULL,需在业务侧做好空值处理。
  4. 与同类函数的选型:同一差值函数族还包括months_diffquarters_diff以及语义对齐月尾的years_diff_v2,按月、按季度粒度计算时请选用对应函数,避免自行做乘法换算造成语义偏差。
  5. 整年取整特性:结果不会出现小数或四舍五入,任何不满一年的间隔一律截断为零;这与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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询