【ClickHouse】高级数据类型|面向对象但不多

本文为稀土掘金技术社区首发签约文章,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_1tag_2的文章时

sql 复制代码
select * from t_article where hasAny(tag_ids, ['tag_1','tag_2']);

当我们需要查询有tag_1tag_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_idauthor.author_nameauthor.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就是每一个数组的元素

不得不说,这种lambdasql的写法还挺有意思

多级嵌套

虽然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)这四种组合

执行没有任何问题,也能查出来插入的数据

所以理论上我们是可以用ArrayTuple的组合来存储相对复杂的对象(除了没办法用字段名表示,只能用字段下标来确定数据)

总结

从支持的字段类型来看

ClickHouse像是介于关系型数据库和非关系型数据库之间的感觉

能够插入一些简单的对象

这样就有可能优化一些连表查询变为单表查询,简化逻辑提升性能

相关推荐
customer088 分钟前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
Qspace丨轻空间2 小时前
气膜场馆:推动体育文化旅游创新发展的关键力量—轻空间
大数据·人工智能·安全·生活·娱乐
小码编匠2 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
AskHarries2 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_2 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
Elastic 中国社区官方博客3 小时前
如何将数据从 AWS S3 导入到 Elastic Cloud - 第 3 部分:Elastic S3 连接器
大数据·elasticsearch·搜索引擎·云计算·全文检索·可用性测试·aws
许野平4 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono