Clickhouse中的Array类型

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中的值,然后通过相同数组下标,找到对应换乘站的时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容