在本指南中,你将了解如何在 ClickHouse 中使用数组,以及一些最常用的数组函数。
数组简介
array 函数构造:
[]:
创建不同类型的数组
创建不同类型的数组
你可以使用 然后,你还可以按类型名称从数组中读取对应类型的值:
use_variant_as_common_type 设置来更改上文所述的默认行为。
这样一来,当参数类型没有公共类型时,你可以将 Variant 类型用作 if/multiIf/array/map 函数的结果类型。例如:[] 索引是一种便捷的数组元素访问方式。
在 ClickHouse 中,需要特别注意的是,数组索引始终从 1 开始。
这可能与你熟悉的其他编程语言不同,因为那些语言中的数组通常是从零开始索引的。
例如,给定一个数组,你可以这样写来选取数组的第一个元素:
数组函数
length、arrayEnumerate、indexOf、has* 函数
length 函数用于返回数组中的元素个数:
arrayEnumerate 函数,返回由各元素索引组成的数组:
indexOf 函数:
indexOfAssumeSorted 函数。
函数 has、hasAll 和 hasAny 可用于判断数组是否包含给定值。
请看下面的示例:
使用数组函数探索航班数据
groupArray
'2024-01-01') 最繁忙的 10 个机场。
我们想了解每个机场有多少架航班起飞。
数据中每个航班对应一行,但如果能按始发机场对数据进行分组,并将目的地机场汇总到一个数组中,会更方便。
为此,我们可以使用 groupArray 聚合函数。它会从每一行中提取指定列的值,并将这些值分组到一个数组中。
运行下面的查询,看看它是如何工作的:
上述查询中的 toStringCutToZero 用于去除某些机场三字码后面出现的空字符。
在这种数据格式下,我们可以通过查看汇总后 “Destinations” 数组的长度,轻松找出最繁忙机场的顺序:
arrayMap 和 arrayZip
arrayMap 函数就是这样一种高阶函数:它对原数组中的每个元素应用 lambda 函数,并基于给定数组返回一个新数组。
运行下面的查询。该查询使用 arrayMap 函数来查看哪些航班延误、哪些航班准点。
对于每组起点/目的地组合,它会显示每个航班的机尾编号和状态:
在上面的查询中,arrayMap 函数接收一个单元素数组 [DepDelayMinutes],并应用 lambda 函数 d -> if(d >= 30, 'DELAYED', if(d >= 15, 'WARNING', 'ON-TIME' 对其进行分类。
然后再用 [DepDelayMinutes][1] 提取结果数组中的第一个元素。
arrayZip 函数会将 Tail_Number 数组和 statuses 数组合并成一个数组。
arrayFilter
DEN、ATL 和 DFW 这几个机场中延误 30 分钟或以上的航班数量:
在上面的查询中,我们将一个 Lambda 函数作为 arrayFilter 函数的第一个参数传入。
这个 Lambda 函数以延误分钟数 (d) 作为输入,满足条件时返回 1,否则返回 0。
arraySort 和 arrayIntersect
arraySort 和 arrayIntersect 函数,找出哪些美国主要机场组合拥有最多的共同目的地。
arraySort 接收一个数组,默认按升序对元素进行排序,不过你也可以向它传入一个 lambda 函数 来定义排序顺序。
arrayIntersect 接收多个数组,并返回一个数组,其中包含所有这些数组中都存在的元素。
运行下面的查询,看看这两个数组函数的实际效果:
这个查询主要分为两个阶段。
首先,它使用公用表表达式 (CTE) 创建一个名为 airport_routes 的临时数据集,查看 2024 年 1 月 1 日的所有航班,并为每个始发机场构建一个排好序的唯一目的地列表,也就是该机场所服务的每一个不同目的地。
例如,在 airport_routes 结果集中,DEN 可能有一个数组,包含它飞往的所有城市,比如 ['ATL', 'BOS', 'LAX', 'MIA', ...] 等等。
在第二阶段,查询选取五个美国主要枢纽机场 (DEN、ATL、DFW、ORD 和 LAS) ,并比较它们所有可能的两两组合。
它通过 cross join 来实现这一点,这会生成这些机场的所有组合。
然后,对于每一对机场,它使用 arrayIntersect 函数找出同时出现在两个机场列表中的目的地。
length 函数则用于统计它们共有多少个目的地。
条件 a1.Origin < a2.Origin 可确保每一对机场只出现一次。
如果没有这个条件,你会同时得到 JFK-LAX 和 LAX-JFK 这两条单独结果,而这是冗余的,因为它们表示的是同一次比较。
最后,查询会对结果进行排序,显示哪些机场组合拥有最多的共同目的地,并只返回前 10 条。
这揭示了哪些主要枢纽的航线网络重叠最多,这可能意味着多家航空公司正在同一城市对之间竞争,也可能意味着这些枢纽服务于相似的地理区域,并可能作为旅客中转时的替代连接点。
arrayReduce
arrayReduce,找出从丹佛国际机场出发的每条航线的平均延误和最大延误:
在上面的示例中,我们使用 arrayReduce 计算了从 DEN 出发的各条航线的平均延误和最大延误。
arrayReduce 会将函数第一个参数中指定的聚合函数应用到提供的数组元素上,而该数组由函数的第二个参数指定。
arrayJoin
arrayJoin 函数。
arrayJoin 会将数组“展开”,为数组中的每个元素生成单独的一行。
这类似于其他数据库中的 UNNEST 或 EXPLODE SQL 函数。
与大多数返回数组或标量值的 数组函数 不同,arrayJoin 会通过增加行数从根本上改变结果集。
看下面这个查询,它返回一个从 0 到 100、step 为 10 的值数组。
我们可以把这个数组看作不同的延误时长:0 分钟、10 分钟、20 分钟,依此类推。
我们可以编写一个使用 arrayJoin 的查询,计算两个机场之间延误达到某个分钟数及以上的航班有多少。
下面的查询使用累计延误桶,创建一个直方图,展示 2024 年 1 月 1 日从 Denver (DEN) 到 Miami (MIA) 的航班延误分布:
在上面的查询中,我们通过 CTE 子句 (WITH 子句) 返回一个延误数组。
Destination 会将目的地代码转换为字符串。
我们使用 arrayJoin 将延误数组展开成多行。
delay 数组中的每个值都会变成单独的一行,并使用别名 del,
因此会得到 10 行:一行对应 del=0,一行对应 del=10,一行对应 del=20,等等。
对于每个延误阈值 (del) ,查询会使用 countIf(DepDelayMinutes >= del),
统计延误大于或等于该阈值的航班数量。
arrayJoin 还有一个等价的 SQL 命令:ARRAY JOIN。
下面用这个等价的 SQL 命令形式重写上述查询,便于对比:
后续步骤
groupArray、arrayFilter、arrayMap、arrayReduce 和 arrayJoin 等强大函数。
要继续深入学习,请查阅完整的数组函数参考,了解更多函数,例如 arrayFlatten、arrayReverse 和 arrayDistinct。
你可能还想了解一些可与数组配合使用的相关数据结构,例如 元组、JSON 和 Map 类型。
请练习将这些概念应用到你自己的数据集中,并在 SQL playground 或其他示例数据集上尝试不同的查询。
数组是 ClickHouse 的一项基础特性,可实现高效的分析查询——随着你越来越熟悉数组函数,你会发现它们能够显著简化复杂的聚合和时间序列分析。
如果你想进一步了解数组的更多用法,我们推荐观看下面这段由我们的常驻数据专家 Mark 带来的 YouTube 视频: