4、数据类型
ClickHouse 提供了许多数据类型,它们可以划分为基础类型、复合类型和特殊类型。
可以在 system.data_type_families
表中检查数据类型名称以及是否区分大小写。这个表中存储了 ClickHouse 支持的所有数据类型。
ClickHouse 与 Mysql、Hive 中常用数据类型的对比如下:
MySQL | Hive | ClickHouse |
---|---|---|
byte | TINYINT | Int8 |
short | SMALLINT | Int16 |
int | INT | Int32 |
long | BIGINT | Int64 |
varchar | STRING | String |
timestamp | TIMESTAMP | DateTime |
float | FLOAT | Float32 |
double | DOUBLE | Float64 |
boolean | BOOLEAN | -- |
可通过 toTypeName(field)
函数获取字段数据类型。
4.1、整型
ClickHouse中 整形分为 Int8、Int16、Int32、Int64 来表示整数不同的取值范围,整形又包含有符号整形和无符号整形。
类型 | 字节空间 | 取值范围 |
---|---|---|
Int8 | 1 | [-128 : 127] |
Int16 | 2 | [-32768 : 32767] |
In32 | 4 | [-2147483648 : 2147483647] |
Int64 | 8 | [-9223372036854775808 : 9223372036854775807] |
UInt8 | 1 | [0 : 255] |
UInt16 | 2 | [0 : 65535] |
UInt32 | 4 | [0 : 4294967295] |
UInt64 | 8 | [0 : 18446744073709551615] |
4.2、浮点型
建议使用整数方式来存储数据,因为浮点类型数据计算可能导致精度误差。浮点类型包含单精度浮点数和双精度浮点数。
类型 | 字节空间 | 有效精度位数 | 说明 |
---|---|---|---|
Float32 | 4 | 7 | 小数点后第8位起会发生数据溢出。 |
Float64 | 8 | 16 | 小数点后第17位起会发生数据溢出。 |
相关函数
toFloat32(value)
用来将字符串转换成 Float32 类型的函数。toFloat64(value)
用来将字符串转换成 Float64 类型的函数。
4.3、Decimal
Decimal 有符号的定点数,可在加、减和乘法运算过程中保持精度。
ClickHouse提供了 Decimal32、Decimal64、Decimal128、Decimal256 几种精度的定点数,支持几种写法:
-
Decimal(P,S)
:原生写法 -
Decimal32(S)
:数据范围: -
Decimal64(S)
:数据范围: -
Decimal128(S)
:数据范围: -
Decimal256(S)
:数据范围:
其中,P 代表精度,决定总位数(整数部分+小数部分),取值范围是 [ 1 - 76 ]。S 代表规模,决定小数位数,取值范围是 [ 0 - P ]。
根据 P 值的范围可以有如下对等写法,这里以小数点后2位举例:
P取值 | 原生写法示例 | 等同于 |
---|---|---|
[1 : 9] | Decimal(9, 2) | Decimal32(2) |
[10 : 18] | Decimal(18, 2) | Decimal64(2) |
[19 : 38] | Decimal(38, 2) | Decimal128(2) |
[39 : 76] | Decimal(76, 2) | Decimal256(2) |
Decimal 数据在进行四则运算时,精度(总位数)和规模(小数点位数)会发生变化,具体规则如下:
精度(总位数)对应规则:两个不同精度的数据进行四则运算时,结果数据的精度以最大精度为准。
-
规模(小数点位数)对应规则:
加法|减法:
S = max(S1, S2)
,以两个数据中小数点位数最多的为准。乘法:
S = S1 + S2
,以两个数据的小数位相加为准。-
除法:规模以被除数的小数位为准。两数相除,被除数的小数位数不能小于除数的小数位数,也就是触发的规模可以理解为与两个数据中小数点位数大的为准。
举例:A/B ,A 是被除数,与 A 的规模保持一致。
相关函数
-
toDecimal32(value, S)
函数可将字符串value
转换为Decimal32
类型,小数点后有 S 位。 -
toDecimal64(value, S)
函数可将字符串value
转换为Decimal64
类型,小数点后有 S 位。 -
toDecimal128(value, S)
函数可将字符串value
转换为Decimal128
类型,小数点后有 S 位。 -
toDecimal256(value, S)
函数可将字符串value
转换为Decimal256
类型,小数点后有 S 位。
4.4、String
字符串 String 可以是任意长度的。它可以包含任意的字节集,包含空字节。因此,字符串类型可以代替其他 DBMS 中的 VARCHAR、BLOB、CLOB 等类型。
相关函数
-
toString(value)
:将数据转换为字符串类型。
4.5、FixedString
FixedString 固定长度 N 的字符串,N 必须是严格的正自然数,一般在明确字符串长度的场景下使用,可以使用下面的语法对列声明为 FixedString 类型:
<column_name> FixedString(N)
当向 ClickHouse 中插入数据时,如果字符串包含的字节数少于 N ,将对字符串末尾进行空字节填充。如果字符串包含的字节数大于 N,将抛出 Too large value for FixedString(N)
异常。
当做数据查询时,ClickHouse 不会删除字符串末尾的空字节。 如果使用 WHERE 子句,则须要手动添加空字节以匹配 FixedString 的值,新版本后期不需要手动添加。
相关函数
-
toFixedString(value, N)
:将数据转换为 N 位长度,N 不能小于value
字符串实际长度。 -
length(field)
:查询字符串字节数,中文所占用字节数与编码相关。
4.6、UUID
UUID 是一种数据库常见的主键类型,在 ClickHouse 中直接把它作为一种数据类型。UUID 共有32位,它的格式为 8-4-4-4-12,如果在插入新记录时未指定 UUID 列值,则UUID 值将用0来填充:00000000-0000-0000-0000-000000000000。
UUID 类型不支持算术运算及聚合函数。
相关函数
-
generateUUIDv4()
:随机生成32位 UUID。
4.7、日期 /时间类型
Date
Date 只能精确到天,用2字节存储,表示从 1970-01-01(无符号)到当前的日期值。日期中没有存储时区信息,不能指定时区。
:) CREATE TABLE t_date (x Date) ENGINE = Memory;
:) INSERT INTO t_date (x) VALUES ('2021-06-01'), ('2021-07-01');
:) SELECT x, toTypeName(x) FROM t_date;
:) SELECT now(), toDate(now()) as d, toTypeName(d);
DateTime
DateTime 精确到秒,可以指定时区。用4字节(无符号)存储 Unix 时间戳。允许存储与日期类型相同的范围内的值。最小值为0000-00-00 00:00:00,时间戳类型值精确到秒。时区使用启动客户端或服务器时的系统时区。默认情况下,客户端连接到服务的时候会使用服务端时区,可以通过启用客户端命令行选项 --use_client_time_zone
来设置使用客户端时区。
:) CREATE TABLE t_datetime(ts DateTime) ENGINE = Memory;
:) INSERT INTO ts VALUES ('2021-06-01 08:00:00');
:) SELECT ts, toTypeName(ts) AS t FROM t_datetime;
:) SELECT toDateTime(ts, 'Asia/Shanghai') AS tz, toTypeName(tz) AS tzt FROM t_datetime;
DateTime64
DateTime64 精确到毫秒和微秒,可以指定时区。在内部,此类型以 Int64 类型将数据存储。时间刻度的分辨率由 precision
参数确定。此外,DateTime64 类型可以像存储其他数据列一样存储时区信息,时区会影响 DateTime64 类型的值如何以文本格式显示,以及如何解析以字符串形式指定的时间数据 (2020-01-01 05:00:01.000
)。时区信息不存储在表的行中,而是存储在列的元数据中。
:) CREATE TABLE t_datetime64(dt64 DateTime64(3, 'Europe/Moscow'), event_id UInt8) ENGINE = Memory;
:) INSERT INTO t_datetime64 Values (1546300800000, 1), ('2019-01-01 00:00:00', 2), (1546300812345, 3);
:) SELECT toDateTime64(dt64, 4) as t1, toDateTime64(dt64, 4, 'Europe/London') as t2, event_id from t_datetime64;
相关函数
-
now()
:获取当前时间,返回格式:yyyy-MM-dd HH:mm:ss
。 -
toDate(value)
:将字符串转成 Date 类型,只支持yyyy-MM-dd
格式。 -
toDateTime(value, [TimeZone])
:将字符串转成 DateTime,支持将数据转换为对应时区时间,只支持yyyy-MM-dd HH:mm:ss
格式。 -
toDateTime64(value, precision, [TimeZone])
:将字符串转成 DateTime64,精度为precision
。支持yyyy-MM-dd HH:mm:ss.SSS
时间格式。
4.8、枚举型
枚举类型通常在定义常量时使用,ClickHouse 提供 Enum8 和 Enum16 两种枚举类型。Enum 保存 'string' = integer
的对应关系。在 ClickHouse 中,尽管用户使用的是字符串常量,但所有含有 Enum 数据类型的操作都是按照包含整数的值来执行。这在性能方面比使用 String 数据类型更有效。
Enum8 和 Enum16 分别对应 'String' = Int8
和 'String' = Int16
,Enum8 类型的每个值范围是 -128 ~ 127
,Enum16 类型的每个值范围是 -32768 ~ 32767
,所有的字符串或者数字都必须是不一样的,允许存在空字符串,Enum 类型中数字可以是任意顺序,顺序并不重要。
向 Enum 字段中插入值时,可以插入枚举的字符串值也可以插入枚举对应的 Integer 值,建议插入对应的字符串值,这样避免插入对应的 Integer 值不在 Enum 枚举集合中再次查询表时报错。定义了枚举类型值之后,不能写入其他值的数据,写入的值不在枚举集合中就会抛出异常。
:) CREATE TABLE t_enum(x Enum8('hello' = 1, 'world' = 2)) ENGINE = Memory;
:) INSERT INTO t_enum VALUES ('hello'), (2);
4.9、Boolean
ClickHouse 中没有单独的类型来存储布尔值。可以使用 Enum8 类型,取值限制为 0 或 1。
:) CREATE TABLE t_boolean(bl Enum8('true' = 1, 'false' = 0)) ENGINE = Memory;
:) INSERT INTO t_boolean VALUES (0), (1);
4.10、Nullable
Nullable 类型只能与基础数据类型搭配使用,表示某个类型的值可以为 NULL,Nullable(Int8) 表示可以存储 Int8 类型的值,没有值时存 NULL。
使用 Nullable 需要注意:Nullable 类型的字段不能作为索引字段,尽量避免使用 Nullable 类型,因为字段被定义为 Nullable 类型后会额外生成 [Column].null.bin
文件保存 NULL 值,增加开销,比普通列消耗更多的存储空间。
:) CREATE TABLE t_nullable(x Int8, y Nullable(Int8)) ENGINE Memory;
:) INSERT INTO t_nullable VALUES (1, NULL), (2, 3);
:) SELECT * FROM t_nullable;
┌─x─┬────y─┐
│ 1 │ NULL │
│ 2 │ 3 │
└───┴──────┘
:) SELECT x + y FROM t_nullable;
┌─plus(x, y)─┐
│ NULL │
│ 5 │
└────────────┘
4.11、Array 数组
Array(T) 由 T 类型元素组成的数组。T 可以是任意类型,包含数组类型。但不推荐使用多维数组,ClickHouse 对多维数组的支持有限。例如,不能在 MergeTree 表中存储多维数组。
数组的定义方式有两种:Array(T)
或 [1,2,3,...]
,数组类型里面的元素必须具有相同的数据类型,否则将引发异常。另外,需要注意的是,数组元素中如果存在 NULL 值,则元素类型将变为 Nullable。
从数组中查询获取值使用 field[1|2|...]
,直接使用中括号获取值,下标从1开始。
# array 必须为小写
:) SELECT array(1, 2) AS x, toTypeName(x) AS xt, ['a','b','c'] as y, toTypeName(y) AS yt;
┌─x─────┬─xt───────────┬─y─────────────┬─yt────────────┐
│ [1,2] │ Array(UInt8) │ ['a','b','c'] │ Array(String) │
└───────┴──────────────┴───────────────┴───────────────┘
:) SELECT array(1, 2, NULL) AS x, toTypeName(x) AS xt;
┌─x──────────┬─xt─────────────────────┐
│ [1,2,NULL] │ Array(Nullable(UInt8)) │
└────────────┴────────────────────────┘
:) CREATE TABLE t_array(id UInt32,name String, score Array(UInt32)) ENGINE = Memory;
:) INSERT INTO t_array VALUES (1, 'zs', array(10,20,30)), (2, 'ls', [100,200,300]);
:) SELECT id, name, score[1] AS score_1 FROM t_array;
┌─id─┬─name─┬─score_1─┐
│ 1 │ zs │ 10 │
│ 2 │ ls │ 100 │
└────┴──────┴─────────┘
4.12、Tuple 元组
元组类型有 1-N 个元素组成,每个元素允许设置不同的数据类型,且彼此之间不要求兼容。
元组支持两种定义方式:tuple(1, 'hello', 12.34)
或 (1, 'hello', 45.67)
。
元组中可以存储多种数据类型,但是要注意数据类型顺序。
:) SELECT tuple(1, 'a') AS x, toTypeName(x) AS xt, (1,'b','hello') AS y, toTypeName(y) AS yt;
┌─x───────┬─xt───────────────────┬─y───────────────┬─yt───────────────────────────┐
│ (1,'a') │ Tuple(UInt8, String) │ (1,'b','hello') │ Tuple(UInt8, String, String) │
└─────────┴──────────────────────┴─────────────────┴──────────────────────────────┘
:) CREATE TABLE t_tuple(id UInt8, name String, info Tuple(String,UInt8)) engine = Memory;
:) INSERT INTO t_tuple VALUES (1, 'zs', tuple('cls1', 100)), (2, 'ls', ('cls2', 200));
:) SELECT * FROM t_tuple;
┌─id─┬─name─┬─info─────────┐
│ 1 │ zs │ ('cls1',100) │
│ 2 │ ls │ ('cls2',200) │
└────┴──────┴──────────────┘
4.13、Nested 嵌套类型
ClickHouse 支持嵌套数据类型 Nested,可以为表定义一个或多个嵌套数据类型字段,但是每个嵌套字段只支持一级嵌套,即嵌套字段内不能继续使用嵌套类型。嵌套一般用来表示简单的级联关系。嵌套本质上是多维数组,嵌套类型中的每个数组的长度必须相同。目前,Nested 类型支持很局限,MergeTree 引擎中不支持 Nested 类型。
:) CREATE TABLE t_nested(
:-] id UInt8,
:-] name String,
:-] dept Nested(
:-] id UInt8,
:-] name String
:-] )
:-] ) engine = Memory;
:) DESC t_nested;
┌─name──────┬─type──────────┬─default_type─┬─default_expression─┬─comment─┬─codec_expression─┬─ttl_expression─┐
│ id │ UInt8 │ │ │ │ │ │
│ name │ String │ │ │ │ │ │
│ dept.id │ Array(UInt8) │ │ │ │ │ │
│ dept.name │ Array(String) │ │ │ │ │ │
└───────────┴───────────────┴──────────────┴────────────────────┴─────────┴──────────────────┴────────────────┘
:) INSERT INTO t_nested VALUES (1, 'zs', [10, 11, 12], ['dp1', 'dp2', 'dp3']), (2, 'ls', [100, 101], ['dp4', 'dp5']);
:) SELECT * FROM t_nested;
┌─id─┬─name─┬─dept.id────┬─dept.name───────────┐
│ 1 │ zs │ [10,11,12] │ ['dp1','dp2','dp3'] │
│ 2 │ ls │ [100,101] │ ['dp4','dp5'] │
└────┴──────┴────────────┴─────────────────────┘
:) SELECT id, name, dept.name[1] AS first_dept FROM t_nested;
┌─id─┬─name─┬─first_dept─┐
│ 1 │ zs │ dp1 │
│ 2 │ ls │ dp4 │
└────┴──────┴────────────┘
4.14、Domain
Domain 类型是特定实现的类型,目前支持 IPv4 和 IPv6,本质上是对整形和字符串的进一步封装,IPv4 基于 UInt32 封装,IPv6 基于 FixedString(16) 封装。
出于便捷性的考量,IPv4 支持格式检查,格式错误的 IP 无法被写入。出于性能的考量,IPv4 和 IPv6 相对于 String 更加紧凑,占用的空间更小,查询性能更快。
在使用 Domain 时需要注意,虽然表面看来与 String 一样,但 Domain 类型并不是字符串,也不支持隐式自动转换成字符串,如果需要返回 IP 的字符串形式,需要调用函数显式实现。
:) CREATE TABLE t_domain(url String, `from` IPv4) ENGINE = Memory;
:) INSERT INTO t_domain(url, `from`) VALUES ('https://wikipedia.org', '116.253.40.133') ('https://clickhouse.tech', '183.247.232.58') ('https://clickhouse.tech/docs/en/', '116.106.34.242');
:) SELECT * FROM t_domain;
┌─url──────────────────────────────┬───────────from─┐
│ https://wikipedia.org │ 116.253.40.133 │
│ https://clickhouse.tech │ 183.247.232.58 │
│ https://clickhouse.tech/docs/en/ │ 116.106.34.242 │
└──────────────────────────────────┴────────────────┘
:) SELECT `from`, toTypeName(`from`) as ft, IPv4NumToString(`from`) as s, toTypeName(s) as st FROM t_domain;
┌───────────from─┬─ft───┬─s──────────────┬─st─────┐
│ 116.253.40.133 │ IPv4 │ 116.253.40.133 │ String │
│ 183.247.232.58 │ IPv4 │ 183.247.232.58 │ String │
│ 116.106.34.242 │ IPv4 │ 116.106.34.242 │ String │
└────────────────┴──────┴────────────────┴────────┘
相关函数
-
IPv4NumToString(field)
:IPv4 类型字段转化为 String。 -
IPv6NumToString(field)
:IPv6 类型字段转化为 String。