首页 / 工具 / 迁移清单 / MaxCompute → Flink SQL
MaxCompute 迁移到 Flink SQL 的函数改写清单
从 MaxCompute 迁移到 Flink SQL 时,在 MaxCompute 有对应写法的 45 个常用函数中,29 个需要改写(其中 2 个在 Flink SQL 没有对应函数)。逐个列出两边写法和注意事项。
需要改写的函数 · 迁移注意事项 · 可以直接用的函数 · 反向:Flink SQL 迁移到 MaxCompute
需要改写的函数
| 函数 | MaxCompute 写法 | Flink SQL 写法 | Flink SQL 说明 |
|---|---|---|---|
| ADD_MONTHS | ADD_MONTHS(startdate, num_months) | 需改写 TIMESTAMPADD(MONTH, n, timepoint) | 内置函数中没有 ADD_MONTHS,用 TIMESTAMPADD 的 MONTH 单位。 |
| ANY_VALUE | ANY_VALUE(colname) | 需改写 FIRST_VALUE(expression) | 内置函数中没有 ANY_VALUE,可以用聚合函数 FIRST_VALUE 代替。 |
| APPROX_COUNT_DISTINCT | APPROX_DISTINCT(colname) | 需改写 COUNT(DISTINCT expr) | 内置函数文档中没有近似去重函数,只能用精确的 COUNT(DISTINCT)。 |
| COLLECT_LIST | COLLECT_LIST(colname) | 需改写 ARRAY_AGG(expression) | 没有输入行时返回 NULL,可以用 RESPECT NULLS 或 IGNORE NULLS 控制是否保留 NULL。 |
| COLLECT_SET | COLLECT_SET(colname) | 需改写 ARRAY_AGG(DISTINCT expression) | 也可以用 COLLECT(DISTINCT expression),返回的是 MULTISET 而不是数组。 |
| DATE_ADD | DATE_ADD(startdate, delta) | 需改写 TIMESTAMPADD(DAY, n, timepoint) | 内置函数中没有 DATE_ADD,改用 TIMESTAMPADD。 |
| DATE_SUB | DATE_SUB(startdate, delta) | 需改写 TIMESTAMPADD(DAY, -n, timepoint) | 内置函数中没有 DATE_SUB,用 TIMESTAMPADD 传负数。 |
| DATEADD | DATEADD(date, delta, datepart) | 需改写 TIMESTAMPADD(unit, n, timepoint) | 单位可以是 SECOND、MINUTE、HOUR、DAY、WEEK、MONTH、QUARTER 或 YEAR。 |
| DATEDIFF | DATEDIFF(end_date, start_date[, date_part]) | 需改写 TIMESTAMPDIFF(DAY, timepoint1, timepoint2) | 内置函数中没有 DATEDIFF。TIMESTAMPDIFF 返回 timepoint1 与 timepoint2 之间带符号的单位数,先写单位,再写开始时间和结束时间。 |
| DATETRUNC | DATETRUNC(date, date_part) | 需改写 FLOOR(timepoint TO MONTH) | 内置函数中没有 DATE_TRUNC,用 FLOOR ... TO 向下取整到指定单位。 |
| DECODE | DECODE(expression, search, result[, search, result]...[, default]) | 需改写 CASE expr WHEN search THEN result ... ELSE default END | DECODE(binary, string) 是按字符集解码二进制,不能做多值匹配,需要用 CASE 改写。 |
| EXPLODE | EXPLODE(var) | 需改写 CROSS JOIN UNNEST(arr) AS t(x) | 内置函数中没有 EXPLODE,数组展开写在 JOIN 中:CROSS JOIN UNNEST,也支持 LEFT JOIN。需要元素位置时加 WITH ORDINALITY,位置从 1 开始,目前只支持 CROSS JOIN。也可以展开 map 和 multiset。 |
| FROM_JSON | from_json(jsonStr, schema) | 需改写 JSON_VALUE(json, path) / JSON_QUERY(json, path) | 没有 FROM_JSON。JSON_VALUE 取标量值,JSON_QUERY 取对象或数组,返回 JSON 字符串。 |
| GET_JSON_OBJECT | GET_JSON_OBJECT(json, json_path) | 需改写 JSON_VALUE(json, path) | 提取 JSON 中的标量值,可以用 RETURNING 子句指定返回类型。 |
| GETDATE | GETDATE() | 别名 CURRENT_TIMESTAMP 或 NOW() | 返回本地时区的 TIMESTAMP_LTZ(3)。流模式下每条记录都会重新求值,批模式下只在查询开始时求值一次。 |
| LAST_DAY | LAST_DAY(date[, date_part]) | 需改写 TIMESTAMPADD(DAY, -1, TIMESTAMPADD(MONTH, 1, FLOOR(ts TO MONTH))) | 内置函数中没有 LAST_DAY。先用 FLOOR 取当月第一天,加一个月再减一天。 |
| LENGTH | LENGTH(str) | 别名 CHAR_LENGTH(string) | 返回字符数,别名 CHARACTER_LENGTH。内置函数中没有 LENGTH。 |
| MAX_BY | MAX_BY(valueToReturn, valueToMaximize) | 需改写 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY y DESC) = 1 | 内置函数中没有 MAX_BY 或 ARG_MAX,用 ROW_NUMBER 窗口函数改写。 |
| MAX_PT | MAX_PT(table_full_name) | 不支持 | 内置函数中没有 MAX_PT。流式作业没有“最新分区”的概念,批模式下可以用子查询改写。 |
| MONTHS_BETWEEN | MONTHS_BETWEEN(end_date, start_date) | 需改写 TIMESTAMPDIFF(MONTH, start, end) | 没有 MONTHS_BETWEEN。TIMESTAMPDIFF 返回 timepoint2 减 timepoint1 的整数,参数顺序与 MONTHS_BETWEEN 相反。 |
| NVL | NVL(value, default_value) | 需改写 IFNULL(input, null_replacement) | 没有 NVL,可以用 IFNULL 或 COALESCE。 |
| PERCENTILE_APPROX | PERCENTILE_APPROX(col[, weight], p[, B]) | 需改写 PERCENTILE(expr, percentage[, frequency]) | PERCENTILE 是精确分位数,按线性插值计算。2.0 起的文档中有这个函数,1.20 的文档中没有,使用前请按所用版本确认。文档提示在普通分组聚合中开销较大,建议用在窗口场景。 |
| SIZE | SIZE(array_or_map) | 别名 CARDINALITY(array_or_map) | 数组返回元素个数,Map 返回键值对个数。 |
| SORT_ARRAY | SORT_ARRAY(a[, isasc]) | 需改写 ARRAY_SORT(array[, ascending_order[, null_first]]) | 默认升序且 NULL 在开头。可以分别指定升降序和 NULL 的位置。任一参数为 NULL 时返回 NULL。 |
| SPLIT_PART | SPLIT_PART(str, delimiter, start[, end]) | 需改写 SPLIT_INDEX(str, delimiter, n - 1) | 下标从 0 开始。下标为负数或任一参数为 NULL 时返回 NULL。 |
| SUBSTRING_INDEX | SUBSTRING_INDEX(str, separator, count) | 不支持 | 内置函数中没有 SUBSTRING_INDEX。只取某一段时可以用 SPLIT_INDEX,下标从 0 开始。 |
| TO_CHAR | TO_CHAR(date, format) | 需改写 DATE_FORMAT(timestamp, 'yyyy-MM-dd HH:mm:ss') | 没有 TO_CHAR。DATE_FORMAT 的格式符兼容 Java SimpleDateFormat。 |
| WEEKOFYEAR | WEEKOFYEAR(date[, date_part]) | 别名 WEEK(date) | 返回 1~53,等价于 EXTRACT(WEEK FROM date)。 |
| WM_CONCAT | WM_CONCAT(separator, colname) | 需改写 LISTAGG(expression[, separator]) | 分隔符写在后面。 |
迁移注意事项
ADD_MONTHS 按月加减
- MaxCompute 返回字符串,Hive 4.0.0 之前也会丢掉时间部分。结果和 DATE 或 DATETIME 字段比较时,注意类型转换。
- Hologres 和 Flink 的月末行为文档没有写明,迁移涉及月末日期时建议用已知数据核对。
ANY_VALUE 取组内任意值
- NULL 的处理不同:MaxCompute 和 Doris 会优先返回非 NULL 值,Spark 默认可能返回 NULL,需要传 true 才会跳过 NULL。
APPROX_COUNT_DISTINCT 近似去重计数
- 各引擎的误差不同:MaxCompute 文档写的是 5%,Hologres 默认是 0.1%~1%。迁移后与原报表对数时,小的差异属于正常现象,不能直接当作数据问题。
- 改写成 Hive 或 Flink 的 COUNT(DISTINCT) 后结果是精确值,但资源消耗会明显变大,大数据量下要评估内存和运行时间。
COLLECT_LIST 聚合为数组
- 不要依赖数组元素的顺序。MaxCompute 文档建议用 SORT_ARRAY 排序;StarRocks 可以直接写 ARRAY_AGG(x ORDER BY y)。
- 用 COLLECT_LIST(x)[0] 取每组的一个值时,MaxCompute 官方建议改用 ANY_VALUE,性能更好,语义也更清楚。
- StarRocks 的 ARRAY_AGG 保留 NULL,MaxCompute 和 Doris 的 COLLECT_LIST 丢弃 NULL,数组长度可能不一致。
COLLECT_SET 去重聚合为数组
- NULL 的处理不同:MaxCompute、Doris 会丢弃 NULL,StarRocks 的 ARRAY_AGG 会保留 NULL。迁移到 StarRocks 后数组里可能多出 NULL 元素。
DATE_ADD 日期加减
- MaxCompute 的 DATE_ADD 返回 DATE,DATE_ADD(DATETIME '2005-02-28 10:00:00', 1) 的结果不带时间。需要保留时间时改用 DATEADD(d, 1, 'dd'),它的返回类型与输入相同。
- MaxCompute 的 DATEADD 按月加时,目标月份没有对应日期会返回当月最后一天,例如 1 月 29 日加 1 个月得到 2 月 28 日。StarRocks 的 DATE_ADD 遇到不存在的日期返回 NULL。
DATE_SUB 日期减天数
- MaxCompute 的 DATE_SUB 返回 DATE,会丢掉时间部分。需要保留时间时改用 DATEADD(d, -1, 'dd')。
- MaxCompute 的 DATE_SUB 遇到 startdate 为 NULL 会报错,而不是返回 NULL。
DATEADD 按单位加减日期
- MaxCompute 和 Hologres 中 mm 表示月,mi 表示分钟。把 DATEADD(d, 1, 'mm') 当成加 1 分钟,是迁移时最常见的错误。
- Spark 3.4.0 起的 dateadd(d, 1) 表示加 1 天,参数个数和含义都与 MaxCompute 不同,不能直接照搬。
- MaxCompute 的 DATEADD 遇到 date 为 NULL 会报错,而不是返回 NULL。字段可能为空时,先过滤或用 IF 包一层。
- MaxCompute 按月加减时,如果目标月份没有对应日期,结果取当月最后一天。StarRocks 的 DATE_ADD 遇到不存在的日期返回 NULL。
DATEDIFF 日期差
- MaxCompute、StarRocks、Doris、Hive 和 Spark 都是结束日期在前。Flink 的 TIMESTAMPDIFF 先写单位,再写开始时间和结束时间。Flink 文档没有写明正负号的方向,按标准 SQL 的约定结果为 timepoint2 减 timepoint1,迁移后建议用一条已知数据核对符号。
- MaxCompute 按分钟等单位计算时,默认先截掉更小的单位再相减:10:05:05 减 10:00:10 返回 5 分钟。开启 BigQuery 兼容模式后返回 4。
- MaxCompute 2.0 类型系统下,yyyy-mm-dd 格式的字符串不能指定 date_part,否则报错。
DATETRUNC 日期截断
- 参数顺序不同:MaxCompute 是日期在前,Hologres、StarRocks 和 Spark 是单位在前,Doris 两种都接受。
- 返回类型不同:MaxCompute 对 DATE 输入返回 DATE,Hologres 和 Spark 的 date_trunc 返回时间戳。结果用于关联 DATE 字段时要先转换。
- 周的起点:MaxCompute 的 week 默认从周一开始,Doris 和 Spark 文档的示例也截断到周一。按周汇总前确认业务定义的周起点。
DECODE 多值匹配
- MaxCompute 的 DECODE 把 NULL 和 NULL 视为相等,DECODE(x, NULL, 0, 1) 在 x 为 NULL 时返回 0。改写成 CASE x WHEN NULL THEN 0 ... 永远不会匹配,要写成 CASE WHEN x IS NULL THEN 0 ...。
- Hive 和 Flink 也有 DECODE,但含义是字符集解码。把 MaxCompute 的多参数 DECODE 原样搬过去会报参数错误,必须改写成 CASE。
- MaxCompute 要求所有 result 类型一致,例如 DECODE(x, 1, 1, 2, 'b') 会报错。改写成 CASE 后各引擎的隐式转换规则不同,建议显式 CAST。
EXPLODE 数组展开为多行
- 空数组和 NULL 数组的行会被直接丢掉:MaxCompute、Doris 的 EXPLODE 和 StarRocks 的 UNNEST 都是这样。需要保留时,Doris 和 Spark 用 EXPLODE_OUTER,StarRocks 3.2.7 起用 LEFT JOIN UNNEST(...) ON TRUE。
- MaxCompute 的 EXPLODE 不能和其他列写在同一个 SELECT 里。需要同时取其他列时,要改成 LATERAL VIEW 写法。
- 需要元素位置时,Hive 和 Spark 的 posexplode 位置从 0 开始,Flink 的 WITH ORDINALITY 从 1 开始,迁移时要调整下标。
FROM_JSON 按 schema 解析 JSON
- MaxCompute 的 from_json 解析成 FLOAT、DOUBLE 或 DECIMAL 时不保证精度。金额类字段建议先按 STRING 取出,再 CAST 成 DECIMAL。
- MaxCompute 的 STRUCT key 区分大小写。JSON 里是 userId、schema 里写成 userid 时,取到的是 NULL,并且不会报错。
GET_JSON_OBJECT 提取 JSON 字段
- MaxCompute 中,2021 年 1 月 21 日之前创建的项目默认会转义 JSON 保留字符,之后创建的项目保留原始字符串。新旧项目的结果可能不同,官方推荐把 odps.sql.udf.getjsonobj.new 设为 true。
- MaxCompute 每次调用都会重新解析整个 JSON。同一个字段取多个值时,频繁调用会明显变慢,可以考虑 UDTF。
GETDATE 获取当前时间
- Flink 流模式下 CURRENT_TIMESTAMP 每条记录都重新求值,同一批数据的结果并不相同。离线 SQL 里依赖“整条 SQL 共用一个时间”的写法,在 Flink 流作业里会出现不同的值。
- MaxCompute 的 GETDATE 固定使用东八区,其他引擎的结果取决于会话或集群时区。迁移后如果时间差了 8 小时,先检查时区设置。
- 返回类型不同:MaxCompute、StarRocks 和 Doris 返回 DATETIME,Hologres 的 NOW 带时区(文档把不带时区的版本单独列为 LOCALTIMESTAMP),Flink 返回 TIMESTAMP_LTZ(3)。直接写入字符串或 DATETIME 字段时,格式可能变化。
- 精度不同:MaxCompute 默认到秒,StarRocks 和 Doris 默认到秒、可传参数到微秒,Flink 到毫秒。用当前时间做去重或比较时要注意小数部分。
LAST_DAY 当月最后一天
- 返回类型不同:MaxCompute(不传 date_part)和 Hive 返回字符串,StarRocks 和 Doris 返回 DATE。结果用于关联 DATE 字段时要先转换。
- MaxCompute 的 LAST_DAY 遇到 NULL 输入会报错,而不是返回 NULL。字段可能为空时,先用 WHERE 过滤或用 IF 包一层。
LENGTH 字符串长度
- 按字符计数统一写 CHAR_LENGTH 最稳妥:Hologres、StarRocks、Doris、Flink 都支持,Hive 2.2.0 起也支持(char_length 是 character_length 的简写)。
- MaxCompute 的 LENGTH 遇到非 UTF-8 编码的字符串返回 -1,不是 NULL。
MAX_BY 取最大值对应的列
- 并列最大时结果不确定:MaxCompute 随机返回一行,StarRocks 返回最先遇到的一行。需要稳定结果时,改用 ROW_NUMBER 并在 ORDER BY 中加上唯一的次排序列。
MAX_PT 取最新分区
- MaxCompute 的 MAX_PT 只看一级分区。多级分区表官方建议直接用子查询。
- MaxCompute 中用 ALTER TABLE 建出的空分区不参与计算;所有分区都为空时会报错。MaxCompute 没有 MIN_PT,取最小分区用 MIN(pt) 子查询。
- MaxCompute 官方给出的等价写法是 WHERE pt = (SELECT MAX(pt) FROM tbl),可以直接用于其他引擎。改写后的子查询需要扫描分区列,表很大时确认目标引擎能做分区裁剪。
MONTHS_BETWEEN 相差月数
- Flink 的 TIMESTAMPDIFF(MONTH, t1, t2) 计算的是 t2 减 t1,开始时间在前;MONTHS_BETWEEN 是结束时间在前。照搬参数顺序会得到相反的符号。
- StarRocks 的 months_diff、Flink 的 TIMESTAMPDIFF 和 Hologres 的 EXTRACT 写法都只返回整数月,原 SQL 中依赖小数部分的计算(例如按 0.5 个月判断)需要重新设计。
- 舍入不同:Hive 和 Spark 默认保留 8 位小数,MaxCompute 返回完整的 DOUBLE,Doris 由 round_type 控制。和原结果做等值比较前,先统一 ROUND。
NVL 空值替换
- MaxCompute 文档要求两个参数类型一致,例如 NVL(bigint_col, 0.0) 应先做 CAST。文档自带的示例里又有 datetime 列配字符串默认值的写法,两处说法不一致,建议实测。
- 迁移到 Hologres、StarRocks 或 Flink SQL 时,统一改写为 COALESCE 最省事,因为 COALESCE 在七个引擎中都可用。
- Flink 的 IFNULL 在第一个参数来自 JSON_VALUE 时可能返回错误结果,问题编号 FLINK-35832。受影响版本包括 1.15~1.20 和 2.0.0,问题已在 2024 年 7 月标记为修复,但没有写明修复版本,使用前请确认所用版本是否包含修复。
PERCENTILE_APPROX 近似分位数
- 第三个参数含义不同:MaxCompute 和 Hive 的 B、Spark 的 accuracy 都默认 10000;StarRocks 和 Doris 的 compression 只接受 2048~10000。从 Spark 迁移时,如果写了大于 10000 的 accuracy,要调整到这个范围内。
- MaxCompute 有带 weight 的四参数写法,例如 PERCENTILE_APPROX(col, weight, 0.5),其他引擎没有对应参数。迁移时要先确认第二个参数是权重还是分位点。
- 各引擎的算法不同,结果不会完全一致。MaxCompute 文档写明 p 落在两个值中间时取两者均值,Spark 返回满足条件的最小值,Flink 的 PERCENTILE 用线性插值。P50、P99 这类指标迁移后要实测对比。
- Flink 的 PERCENTILE 是精确计算,每条记录都可能触发全量排序。文档建议用在窗口聚合中,流式分组聚合中慎用。
SIZE 数组和 Map 元素个数
- StarRocks 和 Doris 区分数组和 Map 函数。从 MaxCompute 或 Hive 迁移时,要按字段类型分别改成 array_length、map_size 或 ARRAY_SIZE、MAP_SIZE。
SORT_ARRAY 数组排序
- Spark 有两个排序函数:sort_array 升序时把 NULL 放在开头,array_sort 把 NULL 放在末尾。从 MaxCompute 或 Hive 迁移时应使用 sort_array,结果才一致。
- 降序的写法不同:MaxCompute 和 Spark 传第二个参数,Flink 传 ascending_order,Doris 用 ARRAY_REVERSE_SORT,Hive 和 Hologres 的函数只能升序。
SPLIT_PART 按分隔符取第 N 段
- 下标越界时结果不同:MaxCompute、StarRocks 和 Spark 返回空字符串,Hologres 和 Doris 返回 NULL。下游用 IS NULL 或 = '' 判断时,迁移后结果可能变化。
- Hive 的数组下标和 Flink 的 SPLIT_INDEX 都从 0 开始。从 SPLIT_PART 改写时要把下标减 1。
- 取最后一段时,StarRocks 和 Spark 可以直接传 -1。MaxCompute 要求下标大于 0,不能这样写。
- MaxCompute 的四参数写法 SPLIT_PART(s, ',', 1, 2) 在其他引擎中没有对应写法,需要改成多段拼接。
SUBSTRING_INDEX 按分隔符截取
- Hologres 和 Flink 没有这个函数。只取某一段可以改用 Hologres 的 SPLIT_PART 或 Flink 的 SPLIT_INDEX;要取“前两段”这种跨分隔符的结果,需要拼接或用正则改写。
- 从 URL 中取域名这类场景常写成 substring_index(substring_index(url, '/', 3), '/', -1),嵌套写法在 Hologres 和 Flink 中要整体改写。
TO_CHAR 日期转字符串
- MaxCompute 的 'yyyy-mm-dd' 原样搬到 Hive、Spark 或 Flink,mm 会被当成分钟,结果类似 2026-30-14,而且不会报错。Java 风格要写 'yyyy-MM-dd'。
- MaxCompute 的 TO_CHAR 遇到 date 为 NULL 会报错,而不是返回 NULL。
WEEKOFYEAR 一年中的第几周
- MaxCompute 可以通过第二个参数改成周日开始,其他引擎的 WEEKOFYEAR 没有这个参数。
WM_CONCAT 分组拼接字符串
- 不写排序时,各引擎拼接的顺序都不固定。需要稳定结果时,MaxCompute 写 WITHIN GROUP (ORDER BY ...),StarRocks 和 Doris 在 GROUP_CONCAT 内写 ORDER BY。
可以直接用的函数
下面 16 个函数在两个引擎中写法相同。参数、返回值和边界情况仍可能不同,点开函数页查看细节。