ClickHouse系列教程 5. 数据类型介绍 2.复合数据类型


1. 复合类型

除了基础数据类型之外,ClickHouse 还提供了数组、元组、枚举和嵌套等复合类型。这些类型通常
是其他数据库原生不具备的特性。拥有了复合类型之后,ClickHouse 的数据模型有了更强的表达能力。

2. 枚举类型

ClickHouse支持枚举类型,这是一种在定义常量时经常会使用的数据类型。ClickHouse提供了Enum8和Enum16两种枚举类型,它们除了取值范围不同之外,别无二致。

2.1 规则及基本用法

  • 枚举固定使用 ( String : Int ) ( Key / Value) 键值对的形式定义数据

  • Enum8'String'= Int8 描述,value范围 Int8 的取值范围

  • Enum16'String'= Int16 描述 ,value范围 Int16 的取值范围

  • Key 和 Value 是不允许重复的,要保证唯一性

  • Key和Value的值都不能为Null,但Key允许是空字符串

  • 写入枚举数据的时候,只会用到Key字符串部分

  • 数据在写入的时,会对照枚举集合项的内容逐一检查。如果Key字符串不在集合范围内则会抛出异常。

-- 创建一个带有一个枚举 Enum8('hello' = 1, 'world' = 2, 'clickhouse'=3) 类型的列:
CREATE TABLE enum_test
(
    `x` Nullable(Enum8('hello' = 1, 'world' = 2, 'clickhouse' = 3))
)
ENGINE = TinyLog

-- 这个 x 列只能存储类型定义中列出的 'hello' 、 'world' 或 NULL值,保存除此之外任何其他值 ClickHouse 都将抛出异常
-- 插入数据
INSERT INTO enum_test Values('hello'),('world'),(NULL);

-- 查询数据
select * from enum_test;

┌─x─────┐
│ hello │
│ world │
│ ᴺᵁᴸᴸ  │
└───────┘

-- 插入不存在的 key, 则会抛出异常
INSERT INTO enum_test VALUES ('spark');

Exception on client:
Code: 36. DB::Exception: Unknown element 'spark' for enum: data for INSERT was parsed from query

Connecting to localhost:9977 as user default.
Connected to ClickHouse server version 21.4.3 revision 54447.

-- 尝试将k/v 类型调换 

CREATE TABLE enum_test2 (x Enum8(1 = 'a', 2 = 'b' )) ENGINE = TinyLog;

Received exception from server (version 21.4.3):
Code: 223. DB::Exception: Received from localhost:9977. DB::Exception: Elements of Enum data type must be of form: 'name' = number, where name is string literal and number is an integer.

2.2 查询对应行的数值

-- 查询对应行的数值,则必须将 Enum 值转换为整数类型
SELECT 
  CAST(x, 'Int8')
FROM enum_test;

┌─CAST(x, 'Int8')─┐
│               1 │
│               2 │
└─────────────────┘

2.3 查询中创建枚举值

-- 查询中创建枚举值,还需要使用 CAST
SELECT
    CAST('a', 'Enum8(\'a\' = 1, \'b\' = 2)') AS en,
    CAST(en, 'Int8') AS enInt,
    toTypeName(en) AS entype;

┌─en─┬─enInt─┬─entype──────────────────┐
│ a1 │ Enum8('a' = 1, 'b' = 2) │
└────┴───────┴─────────────────────────┘

2.4 总结

  1. Enum 类型以二进制读取和写入的方式与 Int8Int16 类型一样的。

当以文本方式读取的时候,ClickHouse 将 解析成字符串然后去枚举值的集合中搜索对应字符串。如果没有找到,会抛出异常。

当读取文本格式的时候,会根据读取到的字符串去找对应的数值。如果没有找到,会抛出异常。

当以文本形式写入时,ClickHouse 将值解析成字符串写入。如果列数据包含垃圾数据,则抛出异常。

  1. ORDER BYGROUP BYINDISTINCT 等等中,Enum 的行为与相应的数字相同。例如,按数字排序。对于等式运算符和比较运算符,Enum 的工作机制与它们在底层数值上的工作机制相同。

  2. 枚举值不能与数字进行比较。枚举可以与常量字符串进行比较。如果与之比较的字符串不是有效Enum值,则将引发异常。可以使用 IN 运算符来判断一个 Enum 是否存在于某个 Enum 集合中,其中集合中的 Enum 需要用字符串表示。

  3. 大多数具有数字和字符串的运算并不适用于Enums;例如,Enum 类型不能和一个数值相加。但是,Enum有一个原生的 toString 函数,它返回它的字符串值。

  4. Enum 值使用 toT 函数可以转换成数值类型,其中 T 是一个数值类型。若 T 恰好对应 Enum 的底层数值类型,这个转换是零消耗的。

  5. Enum 类型可以被 ALTER 无成本地修改对应集合的值。可以通过 ALTER 操作来增加或删除 Enum 的成员。作为安全保障,改变之前使用过的 Enum 成员将抛出异常。

  6. 通过 ALTER 操作,可以将 Enum8 转成 Enum16,反之亦然,就像 Int8Int16一样。

3. 数组

  • 数组是由 T 类型的0 到多个同元素组成,T 可以是任意类型,包含数组类型,数组内可以包含多个类型,但各个类型间必须兼容

  • 但不推荐使用多维数组,ClickHouse 对多维数组的支持有限。例如,不能存储在 MergeTree 表中存储多维数组。

  • 创建数组可以使用array函数,如array(T)

  • 创建数组也可以使用[],如[1,2,3]

  • 查询时无需指定数组元素类型,可以自己推断数据类型,并使用最小可表达的数据类型

3.1 使用示例

-- 使用 array 创建数组, 推断数据类型,并使用最小可表达数据类型 UInt8
SELECT
    [1, 2] AS x,
    toTypeName(x);

┌─x─────┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8)            │
└───────┴─────────────────────────┘

-- 使用 [] 创建数组
SELECT
    [1, 2] AS x,
    toTypeName(x) AS type;

┌─x─────┬─type─────────┐
│ [1,2] │ Array(UInt8) │
└───────┴──────────────┘

3.2 在数据表中使用 Array

-- 创建表时 Array字段需要明确内部存储的数据类型
CREATE TABLE array_test
(
    `a` Array(Int8)
)
ENGINE = TinyLog

-- 插入数据
insert into table array_test values (array(1,2,3));

-- 查询数据
select * from array_test;

┌─a───────┐
│ [1,2,3] │
└─────────┘

-- 插入空值
insert into table array_test values (array());

-- 查询数据
select * from array_test;

┌─a───────┐
│ [1,2,3] │
│ []      │
└─────────┘

3.3 类型检测

  • ClickHouse 会自动检测数组元素,并根据元素计算出存储这些元素最小的数据类型。
  • 如果在元素中存在 Null 或存在 Nullable 类型元素,那么数组的元素类型将会变成 Nullable 。
  • 如果 ClickHouse 无法确定数据类型,它将产生异常。
SELECT array(1, 2, NULL) AS x, toTypeName(x);

┌─x──────────┬─toTypeName(array(1, 2, NULL))─┐
│ [1,2,NULL] │ Array(Nullable(UInt8))        │
└────────────┴───────────────────────────────┘
  • 如果您尝试创建不兼容的数据类型数组,ClickHouse 将引发异常:
-- 插入异常类型的数据
insert into table array_test values (array('a'));

Exception on client:
Code: 6. DB::Exception: Cannot parse string 'a' as Int8: syntax error at begin of string. Note: there are toInt8OrZero and toInt8OrNull functions, which returns zero/NULL instead of throwing exception.: while executing 'FUNCTION CAST(assumeNotNull(array(_dummy_0)) :: 3, 'Array(Int8)' :: 1) -> CAST(assumeNotNull(array(_dummy_0)), 'Array(Int8)') Array(Int8) : 2': data for INSERT was parsed from query

Connecting to localhost:9977 as user default.
Connected to ClickHouse server version 21.4.3 revision 54447.

4. 元组

  • 元组类型由1~n个元素组成,每个元素之间允许设置不同的数据类型,且彼此之间不要求兼容。

  • 元组同样支持类型推断,其推断依据仍然以最小存储代价为原则。

  • 元组也可以使用两种方式定义,常规方式tuple(T)

    • 方式一:tuple(T1,T2,…),常规方式
    • 方式二:(T1,T2,…)
  • 元素类型和泛型的作用类似,可以进一步保障数据质量。在数据写入的过程中会进行类型检查。

4.1 使用示例

-- 定义元组类型数据 
SELECT tuple(1,'a') AS x, toTypeName(x) as type;

┌─x───────┬─type─────────────────┐
│ (1,'a') │ Tuple(UInt8, String) │
└─────────┴──────────────────────┘

-- 获取元组内的数据
SELECT tuple(1,'a') AS x, toTypeName(x) as type, x.1 as v1, x.2 as v2;

┌─x───────┬─type─────────────────┬─v1─┬─v2─┐
│ (1,'a') │ Tuple(UInt8, String)1a  │
└─────────┴──────────────────────┴────┴────┘

4.2 在数据表中使用元组

-- 创建一张带tuple字段的表

CREATE TABLE tuple_table
(
    `t` Tuple(Int8, String, Array(String), Array(Int8))
)
ENGINE = TinyLog

-- 插入数据
INSERT INTO tuple_table VALUES ((1, 'a', ['a', 'b', 'c'], [1, 2, 3]));
INSERT INTO tuple_table VALUES (tuple(11, 'A', ['A', 'B', 'C'], [11, 22, 33]));

-- 查询数据
SELECT t, t.2, t.3, t.4 FROM tuple_table;

┌─t─────────────────────────────────┬─tupleElement(t, 2)─┬─tupleElement(t, 3)─┬─tupleElement(t, 4)─┐
│ (1,'a',['a','b','c'],[1,2,3])a['a','b','c'][1,2,3]            │
│ (11,'A',['A','B','C'],[11,22,33]) │ A                  │ ['A','B','C'][11,22,33]         │
└───────────────────────────────────┴────────────────────┴────────────────────┴────────────────────┘

-- 定义好 tuple字段 之后,插入的数据需要严格按照定义的类型写入
-- 未按照指定数据类型写入,则会报错
INSERT INTO tuple_table VALUES ((101, 'hello', 'world', [1, 2, 3]));

5. 嵌套类型

  • 嵌套类型,顾名思义是一种嵌套表结构
  • 数据表,可以定义任意多个嵌套类型字段
  • 每个字段的嵌套层级只支持一级,即嵌套表内不能继续使用嵌套类型
  • 简单场景的层级关系或关联关系,使用嵌套类型能够非常方便的存储对应的数据
  • 嵌套的数据定义方式为:Nested(Name1 Type1,Name2 Type2,…)就像一个嵌套的表。
  • 嵌套数据结构的参数 - 列名和类型 - 与在CREATE查询中的指定方式相同

5.1 使用示例

-- 创建带有 嵌套结构字段的表
CREATE TABLE nest_table
(
    `name` String,
    `age` Int8,
    `dept` Nested(id UInt8, name String)
)
ENGINE = TinyLog

-- 插入数据
insert into nest_table values 
  ('xiaohong', 18, [1], ['财务部']),
  ('codes', 18, [1,2,3],['hadoop','spark','flink']);

-- 查询数据
select * from nest_table;

┌─name─────┬─age─┬─dept.id─┬─dept.name──────────────────┐
│ xiaohong │  18[1]['财务部']                 │
│ codes    │  18[1,2,3]['hadoop','spark','flink'] │
└──────────┴─────┴─────────┴────────────────────────────┘

通过插入数据信息显示,异常显示需要 Array ,而不是单纯的 Int 。

可以看出嵌套类型本质是一个多维数组的结构。

嵌套类型的一个字段对应一个数组。字段对应的数组内的数量没有限制,但是字段之间需要数组内的数量对齐。

在Hive中,有一种复杂类型叫做 Struct,跟当前这种情况很类似,但是根据经验,推荐尽量少使用 Nested 类型

6 Nullable(TypeName)

  • Nullable 并不能算是一种独立的数据类型,更像是一种修饰符,需要与基础数据类型一起搭配使用
  • Nullable 类型与 Java8 的 Optional 对象有些相似,它表示某个基础数据类型可以是Null 值
  • 特殊标记 (NULL) 表示 “缺失值”,可以与 TypeName 的正常值存放一起。例如,Nullable(Int8) 类型的列可以存储 Int8 类型值,而没有值的行将存储 NULL。
  • Nullable 只能和基本类型搭配使用
  • 不能使用在 Array/Tuple 这种复合类型上
  • 不能作为索引字段【Order by()】
  • 慎用 Nullable ,写入写出性能不好。
    • 在正常情况下,每个列字段的数据会被存储在对应的 [Column].bin文件中。当列字段被Nullable 类型修饰后,会额外生成一个 [Column].null.bin 文件专门保存它的 Null 值。这意味着在读取和写入数据时,需要一倍的额外文件操作。
-- 建表
CREATE TABLE null_test
(
    `c1` String,
    `c2` Nullable(UInt8)
)
ENGINE = TinyLog


-- 插入数据
insert into null_test values ('aa', null),('bb', 99);

-- 查询数据

select c1, c2 from null_test;

┌─c1─┬───c2─┐
│ aa │ ᴺᵁᴸᴸ │
│ bb99 │
└────┴──────┘

7. Nothing

此数据类型的唯一目的是表示不是期望值的情况。 所以不能创建一个 Nothing 类型的值。

例如,文本 NULL 的类型为 Nullable(Nothing)。详情请见 Nullable

Nothing 类型也可以用来表示空数组:

SELECT toTypeName(array()) as type;

┌─type───────────┐
│ Array(Nothing) │
└────────────────┘

8. Domain

  • Domain类型是特定实现的类型,它总是与某个现存的基础类型保持二进制兼容的同时添加一些额外的特性,以能够在维持磁盘数据不变的情况下使用这些额外的特性。

  • 目前ClickHouse暂不支持自定义domain类型。

  • 域名类型分为 IPv4 和 IPv6 两类,本质上它们是对整型和字符串的进一步封装。

    • IPv4 类型是基于UInt32 封装的
    • IPv6 类型是基于FixedString(16) 封装的

如果你可以在一个地方使用与Domain类型二进制兼容的基础类型,那么在相同的地方您也可以使用Domain类型,例如:

  • 使用Domain类型作为表中列的类型
  • 对Domain类型的列进行读/写数据
  • 如果与Domain二进制兼容的基础类型可以作为索引,那么Domain类型也可以作为索引
  • 将Domain类型作为参数传递给函数使用

8.1 Domains的额外特性

  • 在执行SHOW CREATE TABLE 或 DESCRIBE TABLE时,其对应的列总是展示为Domain类型的名称
  • 在INSERT INTO domain_table(domain_column) VALUES(…)中输入数据总是以更人性化的格式进行输入
  • 在SELECT domain_column FROM domain_table中数据总是以更人性化的格式输出
  • 在INSERT INTO domain_table FORMAT CSV …中,实现外部源数据以更人性化的格式载入

8.2 Domains类型的限制

  • 无法通过ALTER TABLE将基础类型的索引转换为Domain类型的索引。
  • 当从其他列或表插入数据时,无法将string类型的值隐式地转换为Domain类型的值。
  • 无法对存储为Domain类型的值添加约束。

8.3 IPv4

IPv4是与UInt32类型保持二进制兼容的Domain类型,其用于存储IPv4地址的值。它提供了更为紧凑的二进制存储的同时支持识别可读性更加友好的输入输出格式。

-- 建表
CREATE TABLE ip4_test
(
    `url` String,
    `ip` IPv4
)
ENGINE = Memory

-- 查看表结构
describe ip4_test;

┌─name─┬─type───┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐
│ url  │ String │              │                    │         │                  │                │
│ ip   │ IPv4   │              │                    │         │                  │                │
└──────┴────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘

-- 插入数据
insert into ip4_test values ('www.baidu.com', '192.168.22.55');

-- 查询数据
select url, ip, toTypeName(ip) from ip4_test;

┌─url───────────┬─ip────────────┬─toTypeName(ip)─┐
│ www.baidu.com │ 192.168.22.55 │ IPv4           │
└───────────────┴───────────────┴────────────────┘

-- 转为更紧凑的二进制存储格式
SELECT url,toTypeName(ip) as type, hex(ip) FROM ip4_test;

┌─url───────────┬─type─┬─hex(ip)──┐
│ www.baidu.com │ IPv4 │ C0A81637 │
└───────────────┴──────┴──────────┘

-- 将 IP 转换为 UInt32 类型
SELECT toTypeName(ip) as type, CAST(ip as UInt32) as i FROM ip4_test;

┌─type─┬──────────i─┐
│ IPv4 │ 3232241207 │
└──────┴────────────┘

-- 将 IP 转换为 String 类型
SELECT IPv4NumToString(ip) as s, toTypeName(s) as type  FROM ip4_test;

┌─s─────────────┬─type───┐
│ 192.168.22.55 │ String │
└───────────────┴────────┘

为什么不直接使用字符串来代替 IPv4 类型呢 ?
1、出于便捷性的考量,例如IPv4类型支持格式检查,格式错误的IP数据是无法被写入的
2、出于性能的考量,同样以IPv4为例,IPv4使用UInt32存储,相比String更加紧凑,占用的空间更小,
查询性能更快。IPv6类型是基于FixedString(16)封装的,它的使用方法与IPv4基本相同。

在使用Domain类型的时候还有一点需要注意,虽然它从表象上看起来与String一样,但Domain类型并
不是字符串,所以它不支持隐式的自动类型转换。如果需要返回IP的字符串形式,则需要显式调用
IPv4NumToString或IPv6NumToString函数进行转换。

8.4 IPv6

-- 创建表
CREATE TABLE ip6_test
(
    `url` String,
    `ip` IPv6
)
ENGINE = Memory;


-- 查看表结构
describe ip6_test;

┌─name─┬─type───┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐
│ url  │ String │              │                    │         │                  │                │
│ ip   │ IPv6   │              │                    │         │                  │                │
└──────┴────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘

-- 插入数据
INSERT INTO ip6_test (url, ip) VALUES ('https://clickhouse.tech', '2001:44c8:129:2632:33:0:252:2');

--查询数据
SELECT * FROM ip6_test;

┌─url─────────────────────┬─ip────────────────────────────┐
│ https://clickhouse.tech │ 2001:44c8:129:2632:33:0:252:2 │
└─────────────────────────┴───────────────────────────────┘

-- 转换更为紧凑的二进制存储格式
SELECT toTypeName(ip) as type, hex(ip) FROM ip6_test;

┌─type─┬─hex(ip)──────────────────────────┐
│ IPv6 │ 200144C8012926320033000002520002 │
└──────┴──────────────────────────────────┘

-- 将IPv6类型的值转换成字符串
SELECT IPv6NumToString(ip) as s, toTypeName(s) as type FROM ip6_test;

┌─s─────────────────────────────┬─type───┐
│ 2001:44c8:129:2632:33:0:252:2 │ String │
└───────────────────────────────┴────────┘

-- 使用CAST将其转换为FixedString(16):
SELECT CAST(ip as FixedString(16)) as i,toTypeName(i) as type  FROM ip6_test;

┌─i───────┬─type────────────┐
│  D�)&23R │ FixedString(16) │
└─────────┴─────────────────┘

9. LowCardinality

  • LowCardinality 是一种改变数据存储和数据处理方法的概念。
  • ClickHouse会把 LowCardinality 所在的列转为字典编码类型,在大多数情况下处理字典编码的数据可以显著的增加SELECT查询速度。
  • 如果一个字典包含少于10000个不同的值,那么ClickHouse可以进行更高效的数据存储和处理。反之如果字典多于10000,效率会表现的更差。
  • 当使用字符类型的时候,可以考虑使用 LowCardinality 代替 Enum
  • 用法:LowCardinality(data_type)
    • data_typeString, FixedString, Date, DateTime,包括数字类型,但是Decimal除外。
    • 对一些数据类型来说,LowCardinality 并不高效,详查 allow_suspicious_low_cardinality_types 设置描述。

9.1 代码示例

-- 建表
CREATE TABLE low_cardinality_test
(
    `id` UInt16,
    `strings` LowCardinality(String)
)
ENGINE = Memory

-- 查看表结构
describe low_cardinality_test;

┌─name────┬─type───────────────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐
│ id      │ UInt16                 │              │                    │         │                  │                │
│ strings │ LowCardinality(String) │              │                    │         │                  │                │
└─────────┴────────────────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘

-- 插入数据
insert into low_cardinality_test values (1,'hello'),(2,'clickhouse');

-- 查询数据
select * from low_cardinality_test;
┌─id─┬─strings────┐
│  1 │ hello      │
│  2 │ clickhouse │
└────┴────────────┘

9.2 相关的设置

9.2.1 allow_suspicious_low_cardinality_types

允许或限制将 LowCardinality 用于固定大小为8个字节或更少的数据类型:数字数据类型和 FixedString(8_bytes_or_less)。

对于小的固定值,LowCardinality 通常使用of效率低下,因为ClickHouse为每行存储一个数字索引。因此:

  • 磁盘空间使用量可能会增加。
  • RAM消耗可能更高,具体取决于字典的大小。
  • 由于额外的编码/编码操作,某些功能的工作速度可能较慢。

由于上述所有原因,MergeTree -engine表中的合并时间可能会增加。

可能的值:

  • 1-的使用LowCardinality不受限制。
  • 0-的使用LowCardinality受到限制。

默认值:0。

9.2.2 low_cardinality_max_dictionary_size

在可写入存储文件系统的 LowCardinality 数据类型的共享全局词典的行中设置最大大小。在无限制的字典增长的情况下,此设置可以防止RAM出现问题。由于最大字典大小限制,所有无法编码的数据都是ClickHouse用普通方法写入的。

可能的值:

  • 任何正整数。

预设值:8192。

9.2.3 low_cardinality_use_single_dictionary_for_part

使用单个字典打开或关闭数据部分。

默认情况下,ClickHouse服务器监视词典的大小,如果词典溢出,则服务器将开始编写下一个词典。禁止创建多个字典集low_cardinality_use_single_dictionary_for_part = 1

可能的值:

  • 1-禁止为数据部分创建多个词典。
  • 0-禁止为数据部分创建多个词典。

默认值:0。

9.2.4 low_cardinality_allow_in_native_format

允许或限制将 LowCardinality 数据类型与本机格式一起使用。

如果对的使用 LowCardinality 受到限制,则ClickHouse服务器会将-列转换为普通列以 LowCardinality 进行 SELECT 查询,并将普通列转换为-列 LowCardinality 进行 INSERT 查询。

主要对于不支持 LowCardinality 数据类型的第三方客户端,此设置是必需的。

可能的值:

  • 1-的使用 LowCardinality 不受限制。
  • 0-的使用 LowCardinality 受到限制。

默认值:1。

9.3 函数

10. 聚合函数类型

10.1 AggregateFunction

  • AggregateFunction — 参数化的数据类型。

  • 定义方式:AggregateFunction(name, types_of_arguments…)

  • 定义参数:

  • 写入数据时,需要调用相应的State函数,可以通过聚合函数名称加-State后缀的形式得到它。

  • 查询数据时,需要调用相应的Merge函数,相同的聚合函数名加-Merge后缀的形式来得到。

  • 不使用Merge函数查询数据时将会是无法显示的二进制。

  • 使用示例
-- 建表语句
CREATE TABLE aggregate_function_test( 
    id String,
    code AggregateFunction(uniq,String), 
    value AggregateFunction(sum,UInt32)
)
ENGINE = Memory;

-- 写入数据使用uniqState,sumState 函数
-- 写入测试数据id = 001, code相同
INSERT INTO TABLE aggregate_function_test SELECT '001', uniqState('code1'), sumState(toUInt32(100));
INSERT INTO TABLE aggregate_function_test SELECT '001', uniqState('code1'), sumState(toUInt32(150));

-- 写入测试数据id = 002, code不同

INSERT INTO TABLE aggregate_function_test SELECT '002', uniqState('code1'), sumState(toUInt32(100));
INSERT INTO TABLE aggregate_function_test SELECT '002', uniqState('code2'), sumState(toUInt32(50));


 -- 查询数据使用uniqMerge,sumMerge 函数
 SELECT id,uniqMerge(code),sumMerge(value) FROM aggregate_function_test GROUP BY id;

┌─id──┬─uniqMerge(code)─┬─sumMerge(value)─┐
│ 0022150 │
│ 0011250 │
└─────┴─────────────────┴─────────────────┘


-- 查询数据时,不使用Merge 函数
SELECT
    id,
    code,
    value
FROM aggregate_function_test;

┌─id──┬─code─┬─value─┐
│ 001 │ n�    │ d     │
└─────┴──────┴───────┘
┌─id──┬─code─┬─value─┐
│ 001 │ n�    │ �      │
└─────┴──────┴───────┘
┌─id──┬─code─┬─value─┐
│ 002 │ ߙc�   │ 2     │
└─────┴──────┴───────┘
┌─id──┬─code─┬─value─┐
│ 002 │ n�    │ d     │
└─────┴──────┴───────┘

10.2 SimpleAggregateFunction

SimpleAggregateFunction(name, types_of_arguments…) 数据类型存储聚合函数的当前值, 并不像 AggregateFunction 那样存储其全部状态。这种优化可以应用于具有以下属性函数: 将函数 f 应用于行集合 S1 UNION ALL S2 的结果,可以通过将 f 分别应用于行集合的部分, 然后再将 f 应用于结果来获得: f(S1 UNION ALL S2) = f(f(S1) UNION ALL f(S2))。 这个属性保证了部分聚合结果足以计算出合并的结果,所以我们不必存储和处理任何额外的数据。

  • SimpleAggregateFunction(func, Type) 的值外观和存储方式于 Type 相同, 所以你不需要应用带有 -Merge/-State 后缀的函数。

  • SimpleAggregateFunction 的性能优于具有相同聚合函数的 AggregateFunction

  • 参数

    • 聚合函数的名称
    • 聚合函数参数的类型

10.2.1 测试代码


-- 建表语句
drop table simpleAggregate_function_test;
CREATE TABLE simpleAggregate_function_test( 
    id String,
    code SimpleAggregateFunction(max,String), 
    value SimpleAggregateFunction(sum,UInt64)
) ENGINE = Memory;

-- 写入测试数据id = 001, code相同
INSERT INTO TABLE simpleAggregate_function_test values('001','code1',100),('001','code1',150);

-- 写入测试数据id = 002, code不同

INSERT INTO TABLE simpleAggregate_function_test values('002','code1',100),('002','code2',50);

 -- 查询数据
SELECT id,max(code),sum(value) FROM simpleAggregate_function_test GROUP BY id;

┌─id──┬─uniqMerge(code)─┬─sumMerge(value)─┐
│ 0022150 │
│ 0011250 │
└─────┴─────────────────┴─────────────────┘


-- 不使用聚合函数查询数据
SELECT
    id,
    code,
    value
FROM simpleAggregate_function_test

┌─id──┬─code──┬─value─┐
│ 002 │ code1 │   100 │
│ 002 │ code2 │    50 │
└─────┴───────┴───────┘
┌─id──┬─code──┬─value─┐
│ 001 │ code1 │   100 │
│ 001 │ code1 │   150 │
└─────┴───────┴───────┘

10.2.2 支持的聚合函数

any anyLast min max sum sumWithOverflow
groupBitAnd groupBitOr groupBitXor groupArrayArray groupUniqArrayArray sumMap
minMap maxMap argMin argMax

11.Map

  • Map(key, value)数据类型存储key:value

  • Map数据类型仍在开发阶段。要使用它,必须进行设置 allow_experimental_map_type = 1

  • 参数
    -—key对中的关键部分。字符串或整数
    -—value对的值部分。字符串,整数或数组

11.1 测试代码

-- 开启 map
set allow_experimental_map_type = 1;

-- 创建包含 map 类型字段的表
CREATE TABLE map_test (a Map(String, UInt64)) ENGINE=Memory;

-- 插入数据
INSERT INTO map_test VALUES ({'key1':1, 'key2':10}), ({'key1':2,'key2':20}), ({'key1':3,'key2':30});


-- 查询数据
select * from map_test;

┌─a────────────────────┐
│ {'key1':1,'key2':10} │
│ {'key1':2,'key2':20} │
│ {'key1':3,'key2':30} │
└──────────────────────┘

-- 选择所有 key2 值:
SELECT a['key2'] as value FROM map_test;

┌─value─┐
│    10 │
│    20 │
│    30 │
└───────┘
  • 如果keyMap()列中没有此类内容,则查询将为数字值,空字符串或空数组返回零。
-- 插入空的 map
INSERT INTO map_test VALUES ({'key3':100}), ({});

-- 查询数据
SELECT a['key3'] FROM map_test;

┌─arrayElement(a, 'key3')─┐
│                       0 │
│                       0 │
│                       0 │
└─────────────────────────┘
┌─arrayElement(a, 'key3')─┐
│                     100 │
│                       0 │
└─────────────────────────┘

11.2 将元组转换为地图类型

你可以投Tuple()作为Map()使用CAST功能:

SELECT CAST(([1, 2, 3], ['Ready', 'Steady', 'Go']), 'Map(UInt8, String)') AS map , toTypeName(map) as type;

┌─map───────────────────────────┬─type──────────────┐
│ {1:'Ready',2:'Steady',3:'Go'} │ Map(UInt8,String) │
└───────────────────────────────┴───────────────────┘

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
ClickHouse系列教程 6. DDL 相关操作介绍 ClickHouse系列教程 6. DDL 相关操作介绍
1. 数据库 数据库起到了命名空间的作用,可以有效规避命名冲突的问题,也为后续的数据隔离提供了支撑。 任何一张数据表,都必须归属在某个数据库之下 1.1 创建数据库 语法 CREATE DATABASE -- 创建数据库 IF NO
2021-04-26
下一篇 
ClickHouse系列教程 4. 数据类型介绍 1. 基本数据类型 ClickHouse系列教程 4. 数据类型介绍 1. 基本数据类型
1. 数据类型介绍ClickHouse 可以在数据表中存储多种数据类型。本节描述 ClickHouse 支持的数据类型,以及使用或者实现它们时(如果有的话)的注意事项。你可以在系统表 system.data_type_families 中检
2021-04-20
  目录