CK中有个强大的数据类型:Array,借助其和相关arrayJoin()、indexof()等函数,可以完成很多关系型数据库无法高效实现的关联查询和统计。
Array相关函数介绍
*函数arrayJoin(arr)
与其他函数最大的不同就是,它可以将单行数据展开到多行(普通函数不改变行数,聚合函数将多行压缩到一行),展开规则也很简单:基于参数列的数组的元素数量,展开相同数量的行,其他列的值会被简单复制。
*函数indexOf(arr, x)
返回x元素在数组中的位置,注意序号是从1开始,0代表元素不存在。
*函数arrayMap(func,arr1,...)
一个很灵活强大的函数
func是一个lambda表达式,通过该方法作用于原始数组中每一个数据,得到最终结果。
SELECT arrayMap(x -> (x + 2), [1, 2, 3]) as res;
┌─res─────┐
│ [3,4,5] │
└─────────┘
SELECT arrayMap((x, y) -> (x, y), [1, 2, 3], [4, 5, 6]) AS res
┌─res─────────────────┐
│ [(1,4),(2,5),(3,6)] │
└─────────────────────┘
*函数arrayFilter(func, arr1, …)
func是一个lambda表达式,通过作用于数组中每一个元素后,只留下结果为非0的部分。
SELECT arrayFilter(x -> x LIKE '%World%', ['Hello', 'abc World']) AS res
┌─res───────────┐
│ ['abc World'] │
└───────────────┘
SELECT
arrayFilter(
(i, x) -> x LIKE '%World%',
arrayEnumerate(arr),
['Hello', 'abc World'] AS arr)
AS res
┌─res─┐
│ [2] │
└─────┘
注意:高级函数中,都支持多个数组参数,但是必须保证长度一致。多个数组在过滤时会组成多维数组,共享数组指针,但最终过滤后显示的只有第一个参数数组。如果不理解,可以看最下面的例子。
函数arrayMin,arrayMax,arraySum,arrayAvg([func,] arr)
数组计算
函数均支持可选函数参数(lambda表达式),用法与arrayMap相同。
SELECT arraySum(x -> x*x, [2, 3]) AS res;
┌─res─┐
│ 13 │
└─────┘
*函数arrayCumSum([func,] arr1, …), arrayCumSumNonNegative(arr)
均为累加求和函数
arrayCumSumNonNegative当返回值包含小于零的值时,该值将被替换为零,并以零参数执行后续计算
SELECT arrayCumSum([1, 1, 1, 1]) AS res
┌─res──────────┐
│ [1, 2, 3, 4] │
└──────────────┘
SELECT arrayCumSumNonNegative([1, 1, -4, 1]) AS res
┌─res───────┐
│ [1,2,0,1] │
└───────────┘
函数arrayProduct(arr)
将数据元素相乘
SELECT arrayProduct([1,2,3,4,5,6]) as res;
┌─res───┐
│ 720 │
└───────┘
返回的结果,始终为Float64
函数arrayCompact(arr)
从数组中删除连续的重复元素。结果值的顺序由源数组中的顺序决定。
SELECT arrayCompact([1, 1, nan, nan, 2, 3, 3, 3]);
┌─arrayCompact([1, 1, nan, nan, 2, 3, 3, 3])─┐
│ [1,nan,nan,2,3] │
└────────────────────────────────────────────┘
函数arrayZip(arr)
合并多个数组,合并后长度不变,元素变为多个数组相同位置元素组成的元组(tuples)
SELECT arrayZip(['a', 'b', 'c'], [5, 2, 1]);
┌─arrayZip(['a', 'b', 'c'], [5, 2, 1])─┐
│ [('a',5),('b',2),('c',1)] │
└──────────────────────────────────────┘
函数empty(arr), notEmpty(arr)
判断数组是否为空,
函数length(arr)
返回数组长度
函数range(end),range([start,]end[,step])
返回一个由UInt数字组成的数组,可以指定长度
函数arrayConcat(arrays)函数
将多个数组进行组合
函数has(arr, elem), hasAll(arr1,arr2),hasAny(arr1,arr2),hasSubstr(arr1,arr2)
检查数组中元素包含关系
has(): arr是否有特定元素
hasAll(): arr1是否有特定arr2数组
hasAny():是否有任意相同元素
hasSubstr():arr1= prefix+arr2+suffix时,才为1,否则均为0。
SELECT hasAll([1.0, 2, 3, 4], [1, 3]) returns 1.
SELECT hasAll(['a', 'b'], ['a']) returns 1.
SELECT hasAny([[1, 2], [3, 4]], [[1, 2], [1, 3]]) returns 1.
SELECT hasAll([[1, 2], [3, 4]], [[1, 2], [1, 3]]) returns 0.
SELECT hasSubstr([1, 2, 3, 4], [2, 3]) returns 1.
SELECT hasSubstr([1, 2, 3, 4], [1, 3]) returns 0.
函数arrayCount([func,] arr)
统计数组中,符合func函数的数量,func可以是一个lambda表达式
SELECT arrayCount(e-> e>=3,[1,2,3,4]);
returns 2
函数countEqual(arr, x)
返回数组中x元素的数量,等同于arrayCount (elem -> elem = x, arr)
函数arrayEnumerate(arr)
返回array(1,2,3,...,length(arr))
该函数通常跟ARRAY JOIN关键字一起试用,在应用ARRAY JOIN后为每个数组进行计算一次
函数arrayEnumerateUniq(arr)
返回与源数组大小相同的数组,其中每个元素表示与其下标对应的源数组元素在源数组中出现的次数。
例如:arrayEnumerateUniq( [10,20,10,30 ])= [1,1,2,1 ]。
官网例子:
SELECT
Goals.ID AS GoalID,
sum(Sign) AS Reaches,
sumIf(Sign, num = 1) AS Visits
FROM test.visits
ARRAY JOIN
Goals,
arrayEnumerateUniq(Goals.ID) AS num
WHERE CounterID = 160656
GROUP BY GoalID
ORDER BY Reaches DESC
LIMIT 10
┌──GoalID─┬─Reaches─┬─Visits─┐
│ 53225 │ 3214 │ 1097 │
│ 2825062 │ 3188 │ 1097 │
│ 56600 │ 2803 │ 488 │
│ 1989037 │ 2401 │ 365 │
│ 2830064 │ 2396 │ 910 │
│ 1113562 │ 2372 │ 373 │
│ 3270895 │ 2262 │ 812 │
│ 1084657 │ 2262 │ 345 │
│ 56599 │ 2260 │ 799 │
│ 3271094 │ 2256 │ 812 │
└─────────┴─────────┴────────┘
函数arrayPopBack(), arrayPopFront(), arrayPushBack(), arrayPushFront()
数据元素进出操作
函数arrayResize(array, size[, extender])
改变数组长度
如果size大于数组的初始大小,则使用extender值或数组项的数据类型的默认值将数组扩展到右侧,extender可以是NULL
函数arraySlice(array, offset[, length])
返回一个子数组,包含从指定位置的指定长度的元素。
函数arraySort([func,] arr, …),arrayReverseSort([func,] arr, …)
对arr数组的元素进行排序。如果指定了func函数,则排序顺序由func函数的调用结果决定。如果func接受多个参数,那么arraySort函数也将解析与func函数参数相同数量的数组参数。func支持lambda表达式
SELECT arraySort((x) -> -x, [1, 2, 3]) as res;
┌─res─────┐
│ [3,2,1] │
└─────────┘
SELECT arraySort((x, y) -> y, ['hello', 'world'], [2, 1]) as res;
┌─res────────────────┐
│ ['world', 'hello'] │
└────────────────────┘
函数arrayDifference(arr)
返回一个数组,其中包含所有相邻元素对之间的差值。
SELECT arrayDifference([1, 2, 3, 4])
┌─arrayDifference([1, 2, 3, 4])─┐
│ [0,1,1,1] │
└───────────────────────────────┘
函数arrayUniq(arr)
如果传递一个参数,则计算数组中不同元素的数量。
如果传递了多个参数,则它计算多个数组中相应位置的不同元素元组的数量。
函数arrayDistinct(arr)
返回一个包含所有数组中不同元素的数组。
函数arrayReduce(agg_func,arr1)
将聚合函数应用于数组并返回其结果。如果聚合函数具有多个参数,则此函数可应用于相同大小的多个数组。
SELECT arrayReduce('sum',[1,2,3]);
6
函数arrayEnumerateDense(arr)
返回与源数组大小相同的数组,指示每个元素首次出现在源数组中的位置。例如:arrayEnumerateDense([10,20,10,30])= [1,2,1,3]。
函数arrayIntersect(arr)
返回所有数组元素的交集
函数arrayReverse(arr)
反转函数元素
模拟场景:基于地铁客流OD,统计线路客流
局部表结构
in_time | card_id | line_code_list |
---|---|---|
2020-10-09 15:12:06 | 0221584005371930 | ["101","102","103"] |
2020-10-09 15:13:42 | 0221584005153444 | ["101","102","103","105"] |
2020-10-09 15:15:07 | 0221584005371932 | ["101","104","106"] |
... |
经过算法匹配得到OD数据,包含乘客进站、出站、经过站点轨迹、经过线路轨迹等数据,由于经过站点和线路都是不定长度的数组,拆分到多行会造成数据量增大数十倍,影响效率也不利于数据统计的灵活性。这里选取Array结构可以很方便的解决问题。
统计SQL:
select
toDate(in_time) as days,
arrayJoin(line_code_list) as line_code,
count() as num
from ticket_od_trajectory
group by days,line_code
order by days,line_code;
┌─────days─────┬─line_code─┬──num─┐
│ 2020-10-09 │ 101 │ 3 │
│ 2020-10-09 │ 102 │ 2 │
│ 2020-10-09 │ 103 │ 2 │
│ 2020-10-09 │ 104 │ 1 │
│ 2020-10-09 │ 105 │ 1 │
│ 2020-10-09 │ 106 │ 1 │
└──────────────┴───────────┴──────┘
模拟场景:统计线路分时客流
数据说明:
in_time:进站时间
station_time_list:经过站点时的秒数,从进站开始计时
station_code_list:经过站点列表,与station_time_list长度一致
line_code_list:经过的线路列表
transfer_station_code_list:站点中的换乘站列表,包含进站站点,作为第一条线路的换乘站使用,与line_code_list长度一致
注意:数组长度保持一致很重要
由于原始数据中,没有记录用户换乘到某条线路后的时间,所以想统计线路的分时客流,无法通过传统SQL完成。
数据实例:
station_time_list | station_code_list | line_code_list | transfer_station_code_list |
---|---|---|---|
['0','400','650','1300','1500','2100'] | ['站点1','站点2','站点3','站点5','站点7','站点10'] | ['线路1','线路2','线路3'] | ['站点1','站点3','站点7'] |
... |
统计SQL:
select days, line_code, toStartOfFiveMinute(tran_time) as five_minute, count() as num
from (
select arrayMap(x->addSeconds(in_time, toInt32OrZero(x)),
arrayFilter((x, y)->has(transfer_station_code_list, y), station_time_list, station_code_list)) line_time_list,
arrayJoin(line_code_list) as line_code,
line_time_list[indexOf(line_code_list, line_code)] as tran_time,
toDate(in_time) as days,
line_code_list,
station_time_list
from tbl_ticket_od_trajectory
)
group by line_code, days, five_minute
order by line_code, days,five_minute;
该逻辑的重点是需要找到,进入换乘站时的时间(即为换乘到新线路的时间)
此处arrayFilter的作用,就是通过station_code_list中过滤出transfer_station_code_list中的值,然后通过相同数组下标,找到对应换乘站的时间。