本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
虽然ClickHouse
是列式存储
但是从平时的操作来说,和关系型数据库也没有太大差别(也是以表展示)
所以我之前一直以为ClickHouse
的字段类型也就那么一些基础类型
后来才发现,ClickHouse
支持的类型比我想象的要丰富的多
数组
一般情况下当我们有多对多的业务场景的时候
都会键一张中间表用于关联数据
比如说一篇文章有多个标签,一个标签可以对应多篇文章
我们建的中间表就像这样
article_id | tag_id |
---|---|
article_1 | tag_1 |
article_1 | tag_2 |
article_2 | tag_2 |
... | ... |
当需要根据标签来查询文章的时候
一般的逻辑就是先查出中间表中指定标签对应的文章ID,再根据文章ID查询文章数据(分两次查询或进行多表关联)
而如果字段支持数组类型就不需要中间表了
直接把文章的标签属性设置成数组就行了
article_id | tag_ids |
---|---|
article_1 | [tag_1,tag_2] |
article_2 | [tag_2] |
article_3 | [tag_3,tag_4] |
... | ... |
表定义
用ClickHouse
可以这样指定列类型
sql
CREATE TABLE t_article
(
article_id String,
tag_ids Array(String),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
Array
表示数组类型,括号里面的String
表示数组的元素类型
插入
sql
insert into t_article(article_id, tag_ids, create_time)
values ('article_1', ['tag_1','tag_2'], now()),
('article_2', ['tag_2'], now()),
('article_3', array('tag_3','tag_4'), now());
可以使用[]
或是array()
函数来表示数组
查询
当我们需要查询有tag_1
的文章时
sql
select * from t_article where has(tag_ids, 'tag_1');
当我们需要查询有tag_1
或tag_2
的文章时
sql
select * from t_article where hasAny(tag_ids, ['tag_1','tag_2']);
当我们需要查询有tag_1
和tag_2
的文章时
sql
select * from t_article where hasAll(tag_ids, ['tag_1','tag_2']);
元组
元组有点类似没有字段名称的对象
我们可以指定每一个字段的类型
根据字段的顺序来确定数据的含义(如第一个值代表A,第二个值代表B)
文章有作者信息,按照顺序分别定义
-
第一个字段是作者ID
-
第二个字段是作者名称
-
第三个字段是作者等级
表定义
所以可以这样创建表
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author Tuple(String, String, UInt8),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
Tuple
表示元组类型,括号里面可以定义1-n
个数据类型
插入
sql
insert into t_article(article_id, author, create_time)
values ('article_1', ('author_1', 'author_name1', 1), now()),
('article_1', tuple('author_2', 'author_name2', 2), now());
使用()
或是tuple()
函数来表示元组
查询
当我们需要使用元组中的某一个属性来作为条件的时候
sql
select * from t_article where author.1 = 'author_1'
and tupleElement(author, 2) like 'author%';
直接使用字段名称.字段下标(从1开始)
或者使用tupleElement
函数来获得某个属性
嵌套
嵌套有点类似于数组和元组的简单结合
表定义
还是以文章作者为例
使用嵌套类型的建表语句是这样的
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author Nested(author_id String,
author_name String,
author_level UInt8),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
从表定义来看
相比于元组类型,嵌套类型可以指定每个字段的名称了
Nested
表示嵌套类型,括号里面可以定义1-n
个字段,很接近我们平时定义类的方式
插入
根据前两个类型的经验
我非常自信的写下了插入数据的脚本
sql
insert into t_article(article_id, author, create_time)
values ('article_1', ('author_1', 'author_name1', 1), now()),
('article_1', nested('author_2', 'author_name2', 2), now());
因为Array
类型有array()
函数,Tuple
类型有tuple()
函数
所以Nested
类型有nested()
函数,没毛病
怎么样,我的举一反三还不错吧
很可惜,聪明过头了(bushi
当执行插入语句的时候,ClickHouse
报了这样一个错
arduino
Code: 16. DB::Exception: No such column author in table default.t_article
没有author
字段?这报错怎么牛头不对马嘴的,我们明明定义了一个嵌套类型的author
字段
查询一下看看怎么个事
sql
select * from t_article;
你猜怎么着?
article_id | author.author_id | author.author_name | author.author_level | create_time |
---|
原来ClickHouse
直接把字段拆开来了,怪不得没有author
这个字段
修改了一下插入语句
sql
insert into t_article(article_id, author.author_id, author.author_name, author.author_level, create_time)
values ('article_1', 'author_1', 'author_name1', 1, now()),
('article_1', 'author_2', 'author_name2', 2, now());
这样总没问题了吧
这回倒是没有字段不存在的报错了,但是
css
Code: 53. DB::Exception: Type mismatch in IN or VALUES section. Expected: Array(UInt8). Got: UInt64
期望是数组?我们也没有数组类型的字段啊
既然报错说要数组,那就用数组试试?
sql
insert into t_article(article_id, author.author_id, author.author_name, author.author_level, create_time)
values ('article_1', ['author_1'], ['author_name1'], [1], now()),
('article_1', ['author_2'], ['author_name2'], [2], now());
成功了,所以嵌套类型其实默认就是带数组的
嵌套类型更准确的表达其实是(伪代码)
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author Array(对象(author_id String,
author_name String,
author_level UInt8)),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
当然,实际结构更像是这样
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author.author_id Array(String),
author.author_name Array(String),
author.author_level Array(UInt8),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
不过嵌套属性对于数组的长度是会有校验的
如果我们的author.author_id
,author.author_name
,author.author_level
的数组长度不一样,则会报错
sql
insert into t_article(article_id, author.author_id, author.author_name, author.author_level, create_time)
values ('article_3', ['author_3'], ['author_name3'], [1,2], now());
在插入数据的时候,作者ID和名称都写1个值,等级写2个值
c
Code: 190. DB::Exception: Elements 'author.author_id' and 'author.author_level' of Nested data structure 'author' (Array columns) have different array sizes.
ClickHouse
会告诉我们数组的长度不同
另外嵌套类型只支持一级,没办法在嵌套类型中再定义嵌套类型
关于为什么只支持一级嵌套,假设支持二级嵌套(伪代码)
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author.author_id Array(String),
author.author_name Array(String),
author.author_level Array(Nested(level_id UInt8, level_name String)),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
而Nested
本身就是一个数组,所以author.author_level
就变成了
sql
CREATE TABLE t_article
(
article_id String,
/*作者ID,作者名称,作者等级*/
author.author_id Array(String),
author.author_name Array(String),
author.author_level Array(Array(对象(level_id UInt8, level_name String))),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
这还能再展开吗。。。展不开,根本展不开
查询
既然嵌套本身是数组
那么用标签举例更合适一点
sql
CREATE TABLE t_article
(
article_id String,
tags Nested(tag_id String,
tag_name String),
create_time DateTime
) ENGINE = MergeTree
ORDER BY create_time;
sql
/* article_1 有 tag_1(tag_name1) 和 tag_2(tag_name2) 两个标签 */
/* article_2 有 tag_2(tag_name2) 一个标签 */
insert into t_article(article_id, tags.tag_id, tags.tag_name, create_time)
values ('article_1', ['tag_1','tag_2'], ['tag_name1','tag_name2'], now()),
('article_2', ['tag_2'], ['tag_name2'], now());
查询标签名称模糊匹配name1
sql
select *
from t_article
where arrayExists(x -> x like '%name1%', tags.tag_name);
arrayExists()
函数会遍历数组,x
就是每一个数组的元素
不得不说,这种lambda
加sql
的写法还挺有意思
多级嵌套
虽然ClickHouse
自带的嵌套类型只能支持一级
但是如果Array
能和Tuple
组合不就类似于多级嵌套的对象了吗
sql
CREATE TABLE t_test
(
test Array(Array(Tuple(Array(String), Tuple(String))))
) ENGINE = MergeTree
ORDER BY test;
sql
insert into t_test(test)
values ([[(['test'], ('test'))]]);
test
字段包含了Array(Array)
,Array(Tuple)
,Tuple(Array)
,Tuple(Tuple)
这四种组合
执行没有任何问题,也能查出来插入的数据
所以理论上我们是可以用Array
和Tuple
的组合来存储相对复杂的对象(除了没办法用字段名表示,只能用字段下标来确定数据)
总结
从支持的字段类型来看
ClickHouse
像是介于关系型数据库和非关系型数据库之间的感觉
能够插入一些简单的对象
这样就有可能优化一些连表查询变为单表查询,简化逻辑提升性能