ClickHouse - 03

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):数据范围:(-1 \times 10^{(9-S)}, 1 \times 10^{(9-S)})
  • Decimal64(S):数据范围:(-1 \times 10^{(18-S)}, 1 \times 10^{(18-S)})
  • Decimal128(S):数据范围:(-1 \times 10^{(38-S)}, 1 \times 10^{(38-S)})
  • Decimal256(S):数据范围:(-1 \times 10^{(76-S)}, 1 \times 10^{(76-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。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容