ClickHouse读书笔记(三)—数据定义

一、基础类型

1、数值类型

  • 整型:Int8、Int16、Int32、Int64
  • 无符号整型:UInt8、UInt16、UInt32、UInt64
  • 浮点数:Float32(7位精度)、Float64(16位精度)
  • 定点数:Decimal32、Decimal64、Decimal128三种精度,简写方式有Decimal32(S)、Decimal64(S)、Decimal128(S)。原生方式为Decimal(P,S),P代表总位数,范围是1~38;S代表小数位数,范围是0~P。加减乘除运算会导致S发生变化,加减法S=max(S1,S2);乘法S=S1+S2(S1范围>=S2范围);除法S=S1(S1是被除数,S1/S2);

2、字符串类型

  • String:不限长度,不限定字符集,同一套程序应该遵循使用统一的编码,例如“统一保持UTF-8编码”
  • FixedString:定长字符串,类似传统意义的Char类型,定长字符串通过FixedString(N)声明,N表示字符串长度,末尾不足补null字节
  • UUID:数据库常见的主键类型,共32位,格式是8-4-4-4-12,如果写入时没赋值会按照格式用0填充

3、时间类型

支持字符串写入,没有时间戳,最高精度是秒,毫秒微秒需要借助UInt类型。

  • DateTime:年月日时分秒,例如2019-06-22 00:00:00
  • DateTime64:支持亚秒,例如2019-06-22 00:00:00.00
  • Data:年月日,例如2019-06-22

二、复合类型

1、Array

数组,写法有两种:array(T)和[T],例如array(1, 2) 和 [1, 2],通过toTypeName()函数获取类型名称,例如Array(UInt8)。CK的数组拥有类型推断能力,以最小存储代价为原则,使用最小可表达的数据类型。多种数据类型之间可以兼容,例如[1, 1.2]

定义表字段时,需要明确元素类型,c1 Array(String)。

2、Tuple

元组类型,由1~n个元素组成,每个元素之间允许设置不同数据类型,不要求兼容。写法有两种:tuple(T)和(T),tuple(1,'a',now())或者(1,2.0,null),同样支持最小存储代价原则的类型推断。

定义表字段时,需要明确元素类型,c2 Tuple(String, Int8),写入时会进行类型检查。

3、Enum

枚举类型,定义常量时经常会使用到的数据类型。Enum8和Enum16只有取值范围不同。枚举固定使用(String:Int)Key/Value键值对的形式定义数据。Enum8对应(String:Int8),Enum16对应(String:Int16)。

定义枚举集合【c1 Enum8('ready' = 1, 'start' = 2, 'success' = 3, 'error' = 4)】,注意:Key和Value都不允许重复,都不允许为Null,但Key允许为空字符串,写入枚举数据时,只会用到Key字符串部分。例如【INSERT INTO Enum_TEST VALUES('ready');】

为什么不直接用String替代枚举,还要枚举类型?因为后续对枚举的所有操作,包括排序、分组、去重、过滤等都会使用Int类型的Value值。

4、Nested

嵌套类型,一张表可以有多个嵌套类型字段,但只能嵌套一层。嵌套里面的字段期望写入的是Array数组类型。并且同一行数据每个数组字段的长度必须相等。

CREATE TABLE nested_test (
    name String,
    age  UInt8 ,
    dept Nested(
        id UInt8,
        name String 
    )
) ENGINE = Memory;
INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001,10002], ['研发部','技 术支持中心','测试部']); -- 正确,3个id对应3个name
INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研发部','技术支持中心','测试部']); -- 错误,2个id对应3个name
SELECT name, dept.id, dept.name FROM nested_test; -- 访问嵌套类型的数据需要使用点符号
View Code

三、特殊类型

1、Nullable

准确来说,Nullable并不能算是一种独立的数据类型,它更像是 一种辅助的修饰符,需要与基础数据类型一起搭配使用。被修饰的字段可以写入null值。有两个注意点:

  • 只能和基础类型搭配使用,不能用于复合类型,也不能作为索引字段。
  • 慎用,会使查询和写入性能变慢。正常字段的数据会被存储在对应的[Column].bin文件中,如果用Nullable类型修饰,会额外生成一个[Column].null.bin文件专门保存它的Null值,这意味着读取和写入数据时需要一倍的额外文件操作。

2、Domain

域名类型,分为Ipv4和Ipv6,本质是对整型和字符串的进一步封装。有三个注意点

  • 相比String,有格式校验。
  • IPv4使用UInt32存 储,相比String更加紧凑,占用的空间更小,查询性能更快。IPv6类 型是基于FixedString(16)封装的。
  • 看起来与String一样,但不支持隐式的自动类型转换,如果需要返回IP的字符串形式,需要显式调 用IPv4NumToString或IPv6NumToString函数。

四、关于最小存储代价原则的疑问(21.9.4.35版本)

SELECT [1, 2.1, null] as a , toTypeName(a)
-- Array(Nullable(Float64)),2.1用Float32就足够存储了,为什么最小存储代价的类型是Float64?

SELECT [1, 2, null] as a , toTypeName(a)
-- Array(Nullable(UInt8)),这个没有疑问,两个小正数用UInt8

SELECT [1, -2, null] as a , toTypeName(a)
-- Array(Nullable(Int16)),-2用Int8应该是够存了,为什么最小存储代价的类型是Int16?

盲猜:在最小存储代价之上还有一个默认规则,浮点数一律用Float64,正负小整数一律用Int16

 

posted @ 2022-03-08 15:21  守林鸟  阅读(466)  评论(0编辑  收藏  举报