【跨国数仓迁移最佳实践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 中的如下两类特性:

  1. BigQuery 独有特性:这是指在启动业务迁移的时候,MaxCompute 还没有提供的语法特性或者功能。针对这一类的特性,需要对 MaxCompute 进行增强,提供和 BigQuery 类似的业务功能。
  2. 两个平台都提供但是具体行为存在差异的那些特性。对于这种情况,MaxCompute 为了保持行为兼容,不能直接修改自己的行为。MaxCompute 的做法是增加一个 odps.sql.bigquery.compatible 语法开关,打开这个开关之后的语法行为会尽量和 BigQuery 保持一致。

在本次 GoTerra 搬栈项目中,MaxCompute SQL 主要新增了如下功能:

  1. auto partition 表
  2. unnest 语法
  • Delta Table 能力增强
  • Merge INTO 支持多次 update 和 insert
  1. delete from 支持别名
  2. 新增 30+内建函数

在 bigquery 兼容模式下调整如下语法行为以保持和 BigQuery 一致:

  1. 列别名解析

  2. CTE 支持输出同名列

  3. 隐式类型转换规则

  4. 某些类型转换行为

  5. pivot 列名生成规则

下文中我们选取部分重点内容进行进一步描述

MaxCompute 新增功能

一、Auto partition

MaxCompute 和 BigQuery 的分区表概念说明

MaxCompute 和 BigQuery 都支持分区表,但是它们的底层设计思路有很大的差异。

MaxCompute 关于分区表的底层设计思路和 Hive 类似。它允许用户根据一个或多个列对表进行划分,从而将表中的数据分散存储在不同的物理位置上。下面是 MaxCompute 创建分区表的一个例子:

lua 复制代码
create table foo_table(id bigint) partitioned by (pt string);

假如这个 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 表的建表语句如下:

sql 复制代码
create table my_dataset.foo_table (id int64, d date) 
partition by date_trunc(d, MONTH);

从这个建表语句中可以看到,它的底层逻辑是先对时间列按照指定的函数进行运算,上面的例子是使用 date_trunc 函数对时间列进行截取操作,截取的粒度是 MONTH,然后根据截取的结果来作为分区的依据。也就是说,对于同一个分区中的数据,它们的时间列的具体取值可能是不同的,但是都处于相同的某个时间区间内。

BigQuery 在 Time-unit column partitioning 的基础上,又提供了 Ingestion time partitioning 功能。表中有一个名字为_PARTITIONTIME 的伪列,标记每行数据的提取时间,它会按照建表语句被截取至相应的边界(例如每小时或每天)。建表语句如下:

sql 复制代码
create table my_dataset.foo_ingestion_hour (a string) 
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, HOUR);

向 Ingestion time partitioning 表里插入数据的时候,可以指定伪列_PARTITIONTIME 的值,也可以不指定。假如不指定,则系统会根据当前时间来自动填充。

sql 复制代码
-- 插入数据,不指定伪列_PARTITIONTIME的值,由系统自动填充
insert into my_dataset.foo_ingestion_hour(a) values('hi1');

-- 插入数据,指定伪列_PARTITIONTIME的值
insert into my_dataset.foo_ingestion_hour(_PARTITIONTIME, a)
values (timestamp '2024-11-02 14:00:00', 'hi2');

Auto partition 表

MaxCompute 通过 Auto partition 表来实现和 BigQuery Time-unit column partitioning 类似的功能。在建表语句中,通过如下语法来创建 auto partition 表:

vbnet 复制代码
AUTO PARTITIONED BY (trunc_time(<col>, <datePart>) [as alias])

举例如下:

sql 复制代码
CREATE TABLE newtable (id INT64, d DATE) 
AUTO PARTITIONED BY (trunc_time(d, 'month') as ds);

上述建表语句生成的 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 表。建表语句示例如下:

sql 复制代码
-- 指定分区的粒度是hour
create tablefoo_ingestion_hourly(_partitiontime timestamp_ntz, a string) 
auto partitioned by (trunc_time(_partitiontime, 'hour')) 
tblproperties('ingestion_time_partition'='true');

-- 指定分区的粒度是day
create table foo_ingestion_daily(_partitiontime timestamp_ntz, a bigint) 
auto partitioned by (trunc_time(_partitiontime, 'day')) 
tblproperties('ingestion_time_partition'='true');

使用如下方式插入数据:

sql 复制代码
-- 不指定伪列_PARTITIONTIME,由系统自动生成
insert into foo_ingestion_hourly(a) values('hi1');
insert into foo_ingestion_daily(a) values(100);

-- 指定伪列_PARTITIONTIME的值
insert into foo_ingestion_hourly(_PARTITIONTIME, a) values (timestamp_ntz '2024-11-02 14:00:00', 'hi2');
insert into foo_ingestion_daily(_PARTITIONTIME, a) values (timestamp_ntz '2024-11-02 00:00:00', 200);

auto partition 表支持分区裁剪

分区表的主要优势在于它可以显著减少扫描的数据量。例如,在查询时如果指定了某个分区的条件,则只需扫描该分区的数据而不是整个表的数据,从而大大加快了查询速度。

为了下面的举例中描述方便,首先假设建表语句为:

sql 复制代码
create table table_daily(a bigint, ts timestamp) 
auto partitioned by (trunc_time(ts, 'day') as pt);

它支持在如下一些条件下进行分区裁剪:

  1. 使用 partition 列来进行数据过滤。例如:
csharp 复制代码
select * from table_daily where pt >= '2024-09-14';
  1. 直接使用时间列来进行数据过滤,例如:
sql 复制代码
select * from table_daily where ts between timestamp '2024-09-14 00:00:00' and timestamp '2024-09-15 00:00:00';
  1. 对时间列调用 trunc_time 函数,并且 trunc 的粒度(year/month/day/hour)和建表语句对应,支持分区裁剪。例如:
csharp 复制代码
select * from table_daily where trunc_time(ts, 'day') = '2024-09-14';
  1. 对时间列调用 datetrunc 函数,并且 trunc 的粒度(year/month/day/hour)和建表语句对应,支持分区裁剪
csharp 复制代码
select * from table_daily where datetrunc(ts, 'day') = timestamp '2024-09-14 00:00:00';
  1. 对于其他的时间函数,部分函数支持分区裁剪(具体可以参考后续产品文档的说明),例如:
sql 复制代码
select * from table_daily where to_date(ts, 'Asia/Jakarta') > date '2024-03-14'
  1. 假如分区裁剪条件涉及到 scalar suBigQueryuery,系统会先计算 scalar suBigQueryuery 的值,然后根据 suBigQueryuery 的返回值来进行分区裁剪,例如:
csharp 复制代码
-- 系统会先计算select max(ts) from other_table的值,根据所得到的结果来对table_daily进行分区裁剪
select * from table_daily where ts = (select max(ts) from other_table);

二、内建函数能力增强

我们对 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 支持时区参数
  • 增强时区格式
  1. 时间函数支持指定更多的处理格式
  • 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

vbnet 复制代码
--  MaxCompute默认行为
set odps.sql.bigquery.compatible=false;
with  
  t1 as (select 1 a, 2 b),  
  t2 as (select 1 a, 2 b)
  select t1.a as a from t1 join t2 on t1.a=t2.a group by a;
  
-- 会报错:
Semantic analysis exception - a is ambiguous, can be both t1.a or t2.a

但是同样的 query 在 BigQuery 里可以运行。原因是在 select 语句中有 t1.a as a,也就是说为 t1.a 分配了一个别名 a,导致 group by a 中的 a 被解释成了 t1.a

vbnet 复制代码
-- BigQuery的行为:
with  
  t1 as (select 1 a, 2 b),  
  t2 as (select 1 a, 2 b)
 select t1.a as a from t1 join t2 on t1.a=t2.a group by a;
 
-- 输出结果
1

bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行

sql 复制代码
-- MaxCompute在bigquery兼容模式下的行为
set odps.sql.bigquery.compatible=true;
with
  t1 as (select 1 a, 2 b),  
  t2 as (select 1 a, 2 b)
select t1.a as a from t1 join t2 on t1.a=t2.a group by a;

-- 输出
+------------+
| a          |
+------------+
| 1          |
+------------+

CTE 输出支持同名列

如下 query,MaxCompute 默认会报错。原因是 MaxCompute 检查出 CTE 的输出列中有重名,输出了两个列的列名都是 a

csharp 复制代码
--  MaxCompute默认行为
set odps.sql.bigquery.compatible=false;
with   
  t as (select 1 as a, 2 as a, 3 as b)
select b from t;

-- 会报错:
Semantic analysis exception - column reference xxx is ambiguous

但是同样的 query 在 BigQuery 里可以运行

csharp 复制代码
-- BigQuery的行为:
with   
t as (select 1 as a, 2 as a, 3 as b)
select b from t;

-- 输出结果
3

bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行

csharp 复制代码
-- MaxCompute在bigquery兼容模式下的行为
set odps.sql.bigquery.compatible=true;
with   
  t as (select 1 as a, 2 as a, 3 as b)
select b from t;

-- 输出

隐式类型转换规则

如下 query,MaxCompute 默认会报错。

sql 复制代码
--  MaxCompute默认行为
set odps.sql.type.system.odps2=true;
set odps.sql.bigquery.compatible=false;

select '1970-1-2' + interval 1 day;
-- 报错,原因是类型不匹配,string类型和interval类型之间不能进行相加操作
Semantic analysis exception - invalid operand type(s) STRING,INTERVAL_DAY_TIME for operator '+'

但是上述 query 在 BigQuery 可以运行,它可以把这个 query 中的 string 类型隐式类型转换为 date 类型,相当于 select date '1970-1-2' + interval 1 day;

sql 复制代码
-- BigQuery的行为:
select '1970-1-2' + interval 1 day;

-- 输出结果
1970-01-03T00:00:00

bigquery 兼容模式下,上述 query 也能在 MaxCompute 运行,并且行为和 BigQuery 保持一致

sql 复制代码
-- MaxCompute在bigquery兼容模式下的行为
set odps.sql.type.system.odps2=true;
set odps.sql.bigquery.compatible=true;

select '1970-1-2' + interval 1 day

-- 输出结果
1970-01-03 00:00:00

业务价值

经过前文所描述的 SQL 语法功能增强之后,在 bigquery 兼容模式下,MaxCompute 的语法特性已经能够非常好的兼容 BigQuery。配合转换工具,GoTerra 项目组顺利地完成了客户 SQL 的转写工作,有力地支撑客户的业务从 GCP 平台迁移到 MaxCompute 平台。整个业务切换中用户的业务运行平稳,用户体验良好。

迁移到 MaxCompute 之后,配合 MaxCompute 平台的其他核心业务特性和性能优化措施,SQL 的整体查询性能和效率也有了很大的提高,这进一步体现了 MaxCompute 平台在语法兼容性、高性能和稳定性等诸多方面的整体优势。

相关推荐
深蓝海拓9 分钟前
PySide6从0开始学习的笔记(十) 样式表(QSS)
笔记·python·qt·学习·pyqt
小鸡吃米…16 分钟前
Python - 类属性
java·前端·python
宁大小白23 分钟前
pythonstudy Day40
python·机器学习
深蓝海拓41 分钟前
PySide6从0开始学习的笔记(十三) IDE的选择
笔记·python·qt·学习·pyqt
智算菩萨1 小时前
实战:用 Python + 传统NLP 自动总结长文章
开发语言·人工智能·python
子夜江寒1 小时前
基于 Python 库使用贝叶斯算法与逻辑森林
开发语言·python·算法
BBB努力学习程序设计1 小时前
掌握Python中不可变对象与可变对象的深度解析
python·pycharm
whitelbwwww1 小时前
Pytorch--张量表示实际数据
人工智能·pytorch·python
写文章的大米1 小时前
10分钟用Python搭个接口,还能自动生成文档?
python·fastapi
Blossom.1181 小时前
大模型推理优化实战:连续批处理与PagedAttention性能提升300%
大数据·人工智能·python·神经网络·算法·机器学习·php