【跨国数仓迁移最佳实践 6】MaxCompute SQL 语法及函数功能增强,10 万条 SQL 转写顺利迁移

本系列文章将围绕东南亚头部科技集团的真实迁移历程展开,逐步拆解 BigQuery 迁移至 MaxCompute 过程中的关键挑战与技术创新。本篇为第六篇,MaxCompute SQL 语法及函数功能增强。
注:客户背景为东南亚头部科技集团,文中用 GoTerra 表示。
业务背景和痛点
MaxCompute 和 BigQuery 都是业界领先的大数据处理平台,而 SQL 又是用户进行数据分析的主要工具。虽然大数据环境下的 SQL 语言通常都是基于 ANSI SQL 标准扩展而来的,但是每一个系统都有自己的方言特色,在一些语言细节和行为上存在着微妙的差别。
GoTerra 业务迁移面临着多方面全方位的挑战,其中最大的挑战之一是 10 万条 SQL 的转写问题。这些 SQL 有一部分复杂度非常高,有一两万行 SQL 代码,而且其中使用了非常多的比较高级的 SQL 特性。为了支持 SQL 转写,我们从多个团队调集了精兵强将,成立了专门的团队进行转换工具的开发工作。但是转换工具并不能解决所有的问题,有一些业务特性的差异必须通过对 MaxCompute 平台自身能力的增强和调整来实现。
方案概述
为了使用户业务能够平滑地从 BigQuery 迁移到 MaxCompute,首先需要对两者之间的 SQL 语法差异进行了详细地分析,并基于分析结果进行方案设计。迁移过程中,我们需要重点关注 BigQuery 中的如下两类特性:
BigQuery 独有特性:这是指在启动业务迁移的时候,MaxCompute 还没有提供的语法特性或者功能。针对这一类的特性,需要对 MaxCompute 进行增强,提供和 BigQuery 类似的业务功能。
两个平台都提供但是具体行为存在差异的那些特性。对于这种情况,MaxCompute 为了保持行为兼容,不能直接修改自己的行为。MaxCompute 的做法是增加一个 odps.sql.bigquery.compatible 语法开关,打开这个开关之后的语法行为会尽量和 BigQuery 保持一致。

在本次 GoTerra 搬栈项目中,MaxCompute SQL 主要新增了如下功能:
auto partition 表
unnest 语法
Delta Table 能力增强
Merge INTO 支持多次 update 和 insert
delete from 支持别名
新增 30+内建函数
在 bigquery 兼容模式下调整如下语法行为以保持和 BigQuery 一致:
列别名解析
CTE 支持输出同名列
隐式类型转换规则
某些类型转换行为
pivot 列名生成规则
下文中我们选取部分重点内容进行进一步描述
MaxCompute 新增功能
一、Auto partition
MaxCompute 和 BigQuery 的分区表概念说明
MaxCompute 和 BigQuery 都支持分区表,但是它们的底层设计思路有很大的差异。
MaxCompute 关于分区表的底层设计思路和 Hive 类似。它允许用户根据一个或多个列对表进行划分,从而将表中的数据分散存储在不同的物理位置上。下面是 MaxCompute 创建分区表的一个例子:
假如这个 table 有两个 partition pt='2025-06-29'和 pt='2025-06-30',那么这两个 partition 中的数据,它们的 pt 列的取值分别为'2025-06-29'和'2025-06-30'。
BigQuery 支持时间单位列分区(Time-unit column partitioning)和提取时间分区(Ingestion time partitioning),分区列的数据类型只能是时间类型,包括 DATE、TIMESTAMP 或 DATETIME。Time-unit column partitioning 表的建表语句如下:
从这个建表语句中可以看到,它的底层逻辑是先对时间列按照指定的函数进行运算,上面的例子是使用 date_trunc 函数对时间列进行截取操作,截取的粒度是 MONTH,然后根据截取的结果来作为分区的依据。也就是说,对于同一个分区中的数据,它们的时间列的具体取值可能是不同的,但是都处于相同的某个时间区间内。

BigQuery 在 Time-unit column partitioning 的基础上,又提供了 Ingestion time partitioning 功能。表中有一个名字为_PARTITIONTIME 的伪列,标记每行数据的提取时间,它会按照建表语句被截取至相应的边界(例如每小时或每天)。建表语句如下:
向 Ingestion time partitioning 表里插入数据的时候,可以指定伪列_PARTITIONTIME 的值,也可以不指定。假如不指定,则系统会根据当前时间来自动填充。
Auto partition 表
MaxCompute 通过 Auto partition 表来实现和 BigQuery Time-unit column partitioning 类似的功能。在建表语句中,通过如下语法来创建 auto partition 表:
举例如下:
上述建表语句生成的 table 有 3 列,分别是 id, d 和 ds。ds 是一个 string 类型的伪列(pseduo-column),它对应于对于列 d 的取值进行 trunc_time 运算后的取值。
Ingestion time partition 表
MaxCompute 在 auto partition table 的基础上来构建 ingestion time partition 表的能力。建表的时候通过指定 tblproperties 来标识 ingestion time partition 表。建表语句示例如下:
使用如下方式插入数据:
auto partition 表支持分区裁剪
分区表的主要优势在于它可以显著减少扫描的数据量。例如,在查询时如果指定了某个分区的条件,则只需扫描该分区的数据而不是整个表的数据,从而大大加快了查询速度。
为了下面的举例中描述方便,首先假设建表语句为:
它支持在如下一些条件下进行分区裁剪:
使用 partition 列来进行数据过滤。例如:
直接使用时间列来进行数据过滤,例如:
对时间列调用 trunc_time 函数,并且 trunc 的粒度(year/month/day/hour)和建表语句对应,支持分区裁剪。例如:
对时间列调用 datetrunc 函数,并且 trunc 的粒度(year/month/day/hour)和建表语句对应,支持分区裁剪
对于其他的时间函数,部分函数支持分区裁剪(具体可以参考后续产品文档的说明),例如:
假如分区裁剪条件涉及到 scalar suBigQueryuery,系统会先计算 scalar suBigQueryuery 的值,然后根据 suBigQueryuery 的返回值来进行分区裁剪,例如:
二、内建函数能力增强
我们对 MaxCompute 的内置函数能力进行了扩展,增加新的内建函数,并对已有内建函数的功能进行增强。
时间/时期函数
1. 日期时间构造能力增强,增加新的 format,构造的时候允许指定时区信息
新增 TO_TIME/TO_TIMESTAMP/TO_TIMESTAMP_NTZ/TIMESTAMP 函数
新增 TIME_ADD/TIME_SUB/TIME_DIFF/TIME_TRUNC/FORMAT_TIME 函数
新增 CURRENT_DATE/CURRENT_TIMESTAMP_NTZ/CURRENT_MICROS 函数
增强 TO_DATE/TO_CHAR 函数功能
DATETRUNC/TO_CHAR/TO_DATE/TO_TIMESTAMP_NTZ/TO_TIME/TIMESTAMP 支持时区参数
增强时区格式
2. 时间函数支持指定更多的处理格式
DATETRUNC 支持 quarter/week(weekday)/isoweek 参数
DATEDIFF 支持 week/week(weekday)/isoweek/ff6 参数
DATEADD 支持 quarter/week/ff6 参数
LAST_DAY 支持 year/isoyear/quarter/month/week/week(weekday)/isoweek 参数
WEEKOFYEAR 支持 week(weekday)参数
新增 ISOYEAR 函数
网络 IP 数据处理相关函数
新增 NET_IP_NET_MASK 函数
新增 NET_IP_FROM_STRING/NET_SAFE_IP_FROM_STRING 函数
新增 NET_IP_TO_STRING/NET_IPV4_TO_INT64 函数
新增 NET_HOST/NET_PUBLIC_SUFFIX/NET_REG_DOMAIN 函数
字符串及二进制转换
新增 BASE32 函数
新增 CODEPOINT_ARRAY 函数
新增 SAFE_CONVERT_BYTES_TO_STRING 函数
新增 FORMAT_STRING 函数
增强 REGEXP_EXTRACT 函数功能
REVERSE 函数支持输入 binary
正则表达式相关函数
新增 REGEXP_CONTAINS 函数
增强 REGEXP_EXTRACT/REGEXP_EXTRACT_ALL 函数功能
Json 类型相关函数
新增 JSON_STRIP_NULLS 函数
增强 JSON_EXTRACT 函数功能
TO_JSON 函数对于 struct value 为 NULL 时做兼容性处理
聚合函数
新增基于近似聚合算法的 HLL++函数 HLL_COUNT_INIT/HLL_COUNT_MERGE/HLL_COUNT_MERGE_PARTIAL/HLL_COUNT_EXTRACT
新增 PERCENTILE_CONT/PERCENTILE_DISC 函数
新增 STRING_AGG/ARRAY_AGG 函数
新增 APPROX_QUANTILES 函数
地理函数
新增 ST_S2CELLIDFROMPOINT/ST_S2CELLIDNUMFROMPOINT 函数
ST_ARRAY 函数支持输入 array
除了新增函数和增强已有函数来提供和 BigQuery 相同的计算能力,对于部分具有相近功能的函数,设计了精准的函数转换规则,将两个平台的内建函数进行了一对一映射,确保搬迁后函数行为一致。
三、bigquery 兼容模式
通过设置 odps.sql.bigquery.compatible 的取值,可以调整 MaxCompute 的行为和 BigQuery 尽量保持一致,下面举几个例子
对列别名(alias)解析的影响
如下 query,MaxCompute 默认会报错,因为用户的 query 中 group by a,这里的 a 是 ambiguous,可能是 t1.a,也可能是 t2.a
但是同样的 query 在 BigQuery 里可以运行。原因是在 select 语句中有 t1.a as a,也就是说为 t1.a 分配了一个别名 a,导致 group by a 中的 a 被解释成了 t1.a
bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行
CTE 输出支持同名列
如下 query,MaxCompute 默认会报错。原因是 MaxCompute 检查出 CTE 的输出列中有重名,输出了两个列的列名都是 a
但是同样的 query 在 BigQuery 里可以运行
bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行
隐式类型转换规则
如下 query,MaxCompute 默认会报错。
但是上述 query 在 BigQuery 可以运行,它可以把这个 query 中的 string 类型隐式类型转换为 date 类型,相当于 select date '1970-1-2' + interval 1 day;
bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行,并且行为和 BigQuery 保持一致
业务价值
经过前文所描述的 SQL 语法功能增强之后,在 bigquery 兼容模式下,MaxCompute 的语法特性已经能够非常好的兼容 BigQuery。配合转换工具,GoTerra 项目组顺利地完成了客户 SQL 的转写工作,有力地支撑客户的业务从 GCP 平台迁移到 MaxCompute 平台。整个业务切换中用户的业务运行平稳,用户体验良好。
迁移到 MaxCompute 之后,配合 MaxCompute 平台的其他核心业务特性和性能优化措施,SQL 的整体查询性能和效率也有了很大的提高,这进一步体现了 MaxCompute 平台在语法兼容性、高性能和稳定性等诸多方面的整体优势。
评论