前提#
前边一篇文章详细分析了如何在Windows10系统下搭建ClickHouse的开发环境,接着需要详细学习一下此数据库的数据定义,包括数据类型、DDL和DML。ClickHouse作为一款完备的DBMS,提供了类似于MySQL(其实有部分语法差别还是比较大的)的DDL与DML功能,并且实现了大部分标准SQL规范中的内容。系统学习ClickHouse的数据定义能够帮助开发者更深刻地理解和使用ClickHouse。本文大纲(右侧分支)👇👇
本文会详细分析ClickHouse目前最新版本(20.10.3.30)支持的所有数据类型。
数据类型#
ClickHouse的数据类型从大体的来看主要包括:
- 数值类型
- 字符串类型
- 日期时间类型
- 复合类型
- 特殊类型
这里做一份汇总的表格👇
| 大类 | 类型 | 类型名称 | 一般概念 | JavaType | 备注 |
|---|---|---|---|---|---|
| 数值类型 | Int8 | 8bit整型 | TINYINT | Byte|Integer | - |
| 数值类型 | Int16 | 16bit整型 | SMALLINT | Short|Integer | - |
| 数值类型 | Int32 | 32bit整型 | INT | Integer | - |
| 数值类型 | Int64 | 64bit整型 | BIGINT | Long | - |
| 数值类型 | Int128 | 128bit整型 | `- | - | - |
| 数值类型 | Int256 | 256bit整型 | - | - | - |
| 数值类型 | UInt8 | 无符号8bit整型 | TINYINT UNSIGNED | - | Java中不存在无符号整数类型,选择类型时只要不溢出就行 |
| 数值类型 | UInt16 | 无符号16bit整型 | SMALLINT UNSIGNED | - | Java中不存在无符号整数类型,选择类型时只要不溢出就行 |
| 数值类型 | UInt32 | 无符号32bit整型 | INT UNSIGNED | - | Java中不存在无符号整数类型,选择类型时只要不溢出就行 |
| 数值类型 | UInt64 | 无符号64bit整型 | BIGINT UNSIGNED | - | Java中不存在无符号整数类型,选择类型时只要不溢出就行 |
| 数值类型 | Float32 | 32bit单精度浮点数 | FLOAT | Float | - |
| 数值类型 | Float64 | 64bit双精度浮点数 | DOUBLE | Double | - |
| 数值类型 | Decimal(P,S) | 高精度数值,P为总位长,S为小数位长 | DECIMAL | BigDecimal | - |
| 数值类型 | Decimal32(S) | 高精度数值,P总位长属于[1,9],S为小数位长 | DECIMAL | BigDecimal | Decimal(P,S)特化类型 |
| 数值类型 | Decimal64(S) | 高精度数值,P总位长属于[10,18],S为小数位长 | DECIMAL | BigDecimal | Decimal(P,S)特化类型 |
| 数值类型 | Decimal128(S) | 高精度数值,P总位长属于[19,38],S为小数位长 | DECIMAL | BigDecimal | Decimal(P,S)特化类型 |
| 字符串类型 | String | 不定长字符串,长度随意不限 | 广义上类似LONGTEXT | String | 替代了传统DBMS中的VARCHAR、BLOB、CLOB、TEXT等类型 |
| 字符串类型 | FixedString(N) | 定长字符串,使用null字节填充末尾字符 | 有点类似VARCHAR | String | - |
| 字符串类型 | UUID | 特殊字符串,32位长度,格式为:8-4-4-4-4-12 | - | String | 一般使用内置函数生成 |
| 日期时间类型 | Date | 日期 | DATE | LocalDate | - |
| 日期时间类型 | DateTime | 日期时间 | 类似DATE_TIME | LocalDateTime | OffsetDateTime | 只精确到秒,不包含毫秒 |
| 日期时间类型 | DateTime64 | 日期时间 | 类似DATE_TIME | LocalDateTime | OffsetDateTime | 只精确到秒,不包含毫秒,但是包含亚秒,即10 ^ (-n)秒 |
| 复合类型 | Array(T) | 数组 | - | 类似T[] | - |
| 复合类型 | Tuple(S,T…R) | 元组 | - | - | - |
| 复合类型 | Enum | 枚举 | - | - | - |
| 复合类型 | Nested | 嵌套 | - | - | - |
| 特殊类型 | Nullable | NULL修饰类型,不是独立的数据类型 | - | - | - |
| 特殊类型 | Domain | 域名 | - | - | 存储IPV4和IPV6格式的域名 |
ClickHouse中类型严格区分大小写,一般为驼峰表示,例如DateTime不能写成DATETIME或者DATE_TIME,同理,UUID不能写成uuid
下面就每种类型再详细分析其用法。
数值类型#
数值类型主要包括整型数值、浮点数值、高精度数值和特殊的布尔值。
整型#
整型数值指固定长度(bit数)的整数,可以使用带符号和无符号的表示方式。先看整型数值的表示范围👇👇
带符号整型数值:
| 类型 | 字节(byte)数 | 范围 |
|---|---|---|
| Int8 | 1 | [-128, 127] |
| Int16 | 2 | [-32768, 32767] |
| Int32 | 4 | [-2147483648, 2147483647] |
| Int64 | 8 | [-9223372036854775808, 9223372036854775807] |
| Int128 | 16 | [-170141183460469231731687303715884105728, 170141183460469231731687303715884105727] |
| Int256 | 32 | [-57896044618658097711785492504343953926634992332820282019728792003956564819968,57896044618658097711785492504343953926634992332820282019728792003956564819967] |
Int128和Int256能表示的整数范围十分巨大,占用的字节大小也随之增大,一般很少使用。
无符号整型数值:
| 类型 | 字节(byte)数 | 范围 |
|---|---|---|
| UInt8 | 1 | [0, 255] |
| UInt16 | 2 | [0, 65535] |
| UInt32 | 4 | [0, 4294967295] |
| UInt64 | 8 | [0, 18446744073709551615] |
| UInt256 | 32 | [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] |
值得注意的是,UInt128类型并不支持,因此不存在UInt128。UInt256能表示的整数范围十分巨大,占用的字节大小也随之增大,一般很少使用。
一般在使用MySQL的时候会定义一个BIGINT UNSIGNED类型的自增趋势的主键,在ClickHouse中对标UInt64类型。做一个小测试,在ClickHouse命令行客户端中执行:
SELECT \toInt8(127) AS a,toTypeName(a) AS aType, \toInt16(32767) AS b,toTypeName(b) AS bType, \toInt32(2147483647) AS c,toTypeName(c) AS cType, \toInt64(9223372036854775807) AS d,toTypeName(d) AS dType, \toInt128(170141183460469231731687303715884105727) AS e,toTypeName(e) AS eType, \toInt256(57896044618658097711785492504343953926634992332820282019728792003956564819967) AS f,toTypeName(f) AS fType, \toUInt8(255) AS g,toTypeName(g) AS gType, \toUInt16(65535) AS h,toTypeName(h) AS hType, \toUInt32(4294967295) AS i,toTypeName(i) AS iType, \toUInt64(18446744073709551615) AS j,toTypeName(j) AS jType, \toUInt256(115792089237316195423570985008687907853269984665640564039457584007913129639935) AS k,toTypeName(k) AS kType;
输出结果:
SELECTtoInt8(127) AS a,toTypeName(a) AS aType,toInt16(32767) AS b,toTypeName(b) AS bType,toInt32(2147483647) AS c,toTypeName(c) AS cType,toInt64(9223372036854775807) AS d,toTypeName(d) AS dType,toInt128(1.7014118346046923e38) AS e,toTypeName(e) AS eType,toInt256(5.78960446186581e76) AS f,toTypeName(f) AS fType,toUInt8(255) AS g,toTypeName(g) AS gType,toUInt16(65535) AS h,toTypeName(h) AS hType,toUInt32(4294967295) AS i,toTypeName(i) AS iType,toUInt64(18446744073709551615) AS j,toTypeName(j) AS jType,toUInt256(1.157920892373162e77) AS k,toTypeName(k) AS kType┌───a─┬─aType─┬─────b─┬─bType─┬──────────c─┬─cType─┬───────────────────d─┬─dType─┬────────────────────────────────────────e─┬─eType──┬────────────────────f─┬─fType──┬───g─┬─gType─┬─────h─┬─hType──┬──────────i─┬─iType──┬────────────────────j─┬─jType──┬──────────────────────────────────────────────────────────────────────────────k─┬─kType───┐│ 127 │ Int8 │ 32767 │ Int16 │ 2147483647 │ Int32 │ 9223372036854775807 │ Int64 │ -170141183460469231731687303715884105728 │ Int128 │ -9223372036854775808 │ Int256 │ 255 │ UInt8 │ 65535 │ UInt16 │ 4294967295 │ UInt32 │ 18446744073709551615 │ UInt64 │ 115792089237316195423570985008687907853269984665640564039448360635876274864128 │ UInt256 │└─────┴───────┴───────┴───────┴────────────┴───────┴─────────────────────┴───────┴──────────────────────────────────────────┴────────┴──────────────────────┴────────┴─────┴───────┴───────┴────────┴────────────┴────────┴──────────────────────┴────────┴────────────────────────────────────────────────────────────────────────────────┴─────────┘1 rows in set. Elapsed: 0.009 sec.
浮点数#
浮点数包括单精度浮点数Float32和双精度浮点数Float64👇👇
| 类型 | 字节(byte)大小 | 有效精度(排除最左边的零小数位数) | 备注 |
|---|---|---|---|
| Float32 | 4 | 7 | 小数点后除去左边的零后第8位起会产生数据溢出 |
| Float64 | 8 | 16 | 小数点后除去左边的零后第17位起会产生数据溢出 |
可以做一个小测试:
f5abc88ff7e4 :) SELECT toFloat32('0.1234567890') AS a,toTypeName(a);SELECTtoFloat32('0.1234567890') AS a,toTypeName(a)┌──────────a─┬─toTypeName(toFloat32('0.1234567890'))─┐│ 0.12345679 │ Float32 │└────────────┴───────────────────────────────────────┘1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT toFloat32('0.0123456789') AS a,toTypeName(a);SELECTtoFloat32('0.0123456789') AS a,toTypeName(a)┌───────────a─┬─toTypeName(toFloat32('0.0123456789'))─┐│ 0.012345679 │ Float32 │└─────────────┴───────────────────────────────────────┘1 rows in set. Elapsed: 0.036 sec.f5abc88ff7e4 :) SELECT toFloat64('0.12345678901234567890') AS a,toTypeName(a);SELECTtoFloat64('0.12345678901234567890') AS a,toTypeName(a)┌───────────────────a─┬─toTypeName(toFloat64('0.12345678901234567890'))─┐│ 0.12345678901234568 │ Float64 │└─────────────────────┴─────────────────────────────────────────────────┘1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT toFloat64('0.01234567890123456789') AS a,toTypeName(a);SELECTtoFloat64('0.01234567890123456789') AS a,toTypeName(a)┌────────────────────a─┬─toTypeName(toFloat64('0.01234567890123456789'))─┐│ 0.012345678901234568 │ Float64 │└──────────────────────┴─────────────────────────────────────────────────┘1 rows in set. Elapsed: 0.005 sec.
特别地,与标准的SQL相比,ClickHouse支持如下特殊的浮点数类别:
- Inf - 表示正无穷
- -Inf - 表示负无穷
- NaN - 表示不是数字
验证一下:
f5abc88ff7e4 :) SELECT divide(0.5,0);SELECT 0.5 / 0┌─divide(0.5, 0)─┐│ inf │└────────────────┘1 rows in set. Elapsed: 0.007 sec.f5abc88ff7e4 :) SELECT divide(-0.5,0);SELECT -0.5 / 0┌─divide(-0.5, 0)─┐│ -inf │└─────────────────┘1 rows in set. Elapsed: 0.004 sec.f5abc88ff7e4 :) SELECT divide(0.0,0.0);SELECT 0. / 0.┌─divide(0., 0.)─┐│ nan │└────────────────┘1 rows in set. Elapsed: 0.005 sec.
高精度数值#
高精度数值类型Decimal一般又称为为定点数,可以指定总位数和固定位数小数点,表示一定范围内的精确数值。Decimal的原生表示形式为Decimal(P,S),两个参数的意义是:
- P:代表精度,决定总位数(也就是决定整数部分加上小数部分一共有多少位数字),取值范围是[1,76]
- S:代表规模(scale),决定小数位数,取值范围是[0,P]
Decimal(P,S)衍生出的简单表示形式有:Decimal32(S)、Decimal64(S)、Decimal128(S)和Decimal256(S)。见下表:
| 类型 | P的取值范围 | S的取值范围 | 数值范围 |
|---|---|---|---|
| Decimal(P,S) | [1,76] | [0,P] | (-110^(P - S), 110^(P - S)) |
| Decimal32(S) | [1,9] | [0,P] | (-110^(9 - S), 110^(9 - S)) |
| Decimal64(S) | [10,18] | [0,P] | (-110^(18 - S), 110^(18 - S)) |
| Decimal128(S) | [19,38] | [0,P] | (-110^(38 - S), 110^(38 - S)) |
| Decimal256(S) | [39,76] | [0,P] | (-110^(76 - S), 110^(76 - S)) |
如果觉得衍生类型不好理解,还是直接使用Decimal(P,S)就行。它的定义格式如下:
column_name Decimal(P,S)# 如amount Decimal(10,2)
对于四则运算,使用两个不同精度的Decimal数值进行(内置函数)运算,运算结果小数位的规则如下(假设S1为左值的小数位,S2为右值的小数位,S为结果小数位):
- 对于加法和减法,S = max(S1,S2)
- 对于乘法,S = S1 + S2
- 对于除法,S = S1(结果小数位和被除数小数位一致) ```sql f5abc88ff7e4 :) SELECT toDecimal32(2,4) AS x, toDecimal32(2,2) AS y,x+y;
SELECT toDecimal32(2, 4) AS x, toDecimal32(2, 2) AS y, x + y
┌──────x─┬────y─┬─plus(toDecimal32(2, 4), toDecimal32(2, 2))─┐ │ 2.0000 │ 2.00 │ 4.0000 │ └────────┴──────┴────────────────────────────────────────────┘
1 rows in set. Elapsed: 0.019 sec.
f5abc88ff7e4 :) SELECT toDecimal32(2,4) AS x, toDecimal32(2,5) AS y,y/x
SELECT toDecimal32(2, 4) AS x, toDecimal32(2, 5) AS y, y / x
┌──────x─┬───────y─┬─divide(toDecimal32(2, 5), toDecimal32(2, 4))─┐ │ 2.0000 │ 2.00000 │ 1.00000 │ └────────┴─────────┴──────────────────────────────────────────────┘
1 rows in set. Elapsed: 0.004 sec.
f5abc88ff7e4 :) SELECT toDecimal32(2,4) AS x, toDecimal32(2,4) AS y,y*x
SELECT toDecimal32(2, 4) AS x, toDecimal32(2, 4) AS y, y * x
┌──────x─┬──────y─┬─multiply(toDecimal32(2, 4), toDecimal32(2, 4))─┐ │ 2.0000 │ 2.0000 │ 4.00000000 │ └────────┴────────┴────────────────────────────────────────────────┘
1 rows in set. Elapsed: 0.004 sec.
重点注意:如果从事的是金融领域等追求准确精度的数值存储,不能使用浮点数,而应该考虑使用整型或者定点数,舍入尽可能交由程序规则处理,毕竟数据库是存储数据的工具,不应该承担太多处理数据计算的职能。<a name="BcFr5"></a>### 布尔值[#](https://www.cnblogs.com/throwable/p/14018954.html#%E5%B8%83%E5%B0%94%E5%80%BC)ClickHouse中不存在布尔值类型,官方建议使用UInt8类型,通过值0或1表示false或true。<a name="sqCm8"></a>## 字符串类型[#](https://www.cnblogs.com/throwable/p/14018954.html#%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%B1%BB%E5%9E%8B)字符串类型主要包括:- 不定长(动态长度)字符串String- 固定长度字符串FixedString(N),这里的N是最大字节数,而不是长度,例如UTF-8字符占用3个字节,GBK字符占用2个字节- 特殊字符串UUID(存储的是数值,只是形式是字符串)ClickHouse中没有编码的概念,字符串可以包含一组任意字节,这些字节按原样存储和输出。这个编码和解码操作推测完全移交给客户端完成。一般情况下,推荐使用UTF-8编码存储文本类型内容,这样就能在不进行转换的前提下读取和写入数据。<a name="kqNe3"></a>### String[#](https://www.cnblogs.com/throwable/p/14018954.html#string)String类型不限制字符串的长度,可以直接替代其他DBMS的VARCHAR、BLOB、CLOB等字符串类型,相比VARCHAR这类要考虑预测数据最大长度,显然String无比便捷。使用Java语言开发,直接使用String类型承接即可。String类型的数据列的定义如下:```sqlcolumn_name String
FixedString#
FixedString类型的数据列的定义如下:
column_name FixedString(N)
FixedString表示固定长度N的字符串,这里的N代表N个字节(N bytes),而不是N个字符或者N个码点(code point)。一些使用FixedString类型的典型场景:
- 二进制表示存储IP地址,如使用FixedString(16)存储IPV6地址
- 哈希值的二进制表示形式,如FixedString(16)存储MD5的二进制值,FixedString(32)存储SHA256的二进制值
当写入FixedString类型数据的时候:
- 如果数据字节数大于N,则会返回一个Too large value for FixedString(N)的异常
- 如果数据字节数小于N,则会使用null字节填补剩下的部分
官方文档提示查询条件WHERE中如果需要匹配FixedString类型的列,传入的查询参数要自行补尾部的\0,否则有可能导致查询条件失效。也就是更加建议写入数据和查询条件都是固定字节数的参数。
内置函数length()会直接返回N,而内置函数empty()在全为null字节的前提下会返回1,其他情况返回0。
UUID#
UUID这个概念很常见,Java中也有静态方法java.util.UUID#randomUUID()直接生成UUID,因为其独特的唯一性有时候可以选择生成UUID作为数据库的主键类型。ClickHouse直接定义了一种UUID类型,严格来说这种类型不是字符串,但是因为在文档上它的位置顺序排在字符串类型之下,日期时间类型之上,形式上看起来也像字符串,并且它仅仅支持字符串类型的内置函数,所以笔者也把它归类为字符串类型。ClickHouse中的UUID实际上是一个16字节的数字,它的通用格式如下:
8-4-4-4-4-12## 例子61f0c404-5cb3-11e7-907b-a6006ad3dba0## 零值00000000-0000-0000-0000-000000000000
UUID类型列定义格式如下:
column_name UUID
可以通过内置函数generateUUIDv4()直接生成UUID数据,测试一下:
f5abc88ff7e4 :) CREATE TABLE test_u(id UInt64,u UUID) ENGINE = Memory;CREATE TABLE test_u(`id` UInt64,`u` UUID)ENGINE = MemoryOk.0 rows in set. Elapsed: 0.018 sec.f5abc88ff7e4 :) INSERT INTO test_u VALUES (1,generateUUIDv4());INSERT INTO test_u VALUESOk.1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT * FROM test_u;SELECT *FROM test_u┌─id─┬────────────────────────────────────u─┐│ 1 │ fc379d2c-0753-45a3-8589-1ef95ee0d8c9 │└────┴──────────────────────────────────────┘1 rows in set. Elapsed: 0.004 sec.
日期时间类型#
日期时间类型包括Date(表示年月日)、DateTime(表示年月日时分秒)和DateTime64(表示年月日时分秒亚秒)。
Date#
Date表示年月日,但是这种类型在ClickHouse中使用2字节(2 byte -> 16 bit)无符号整数去存储距离Unix纪元(1970-01-01)的天数,不支持时区,能够表示的最大年份为2105年。基于这个特性,在插入Date类型数据的时候可以采用yyyy-MM-dd格式或者无符号整数。见下面的测试:
f5abc88ff7e4 :) CREATE TABLE test_dt(date Date) ENGINE = Memory;CREATE TABLE test_dt(`date` Date)ENGINE = MemoryOk.0 rows in set. Elapsed: 0.025 sec.f5abc88ff7e4 :) INSERT INTO dt VALUES(1),(2),('0000-00-00'),('2020-11-11');INSERT INTO dt VALUESReceived exception from server (version 20.10.3):Code: 60. DB::Exception: Received from clickhouse-server:9000. DB::Exception: Table default.dt doesn't exist..0 rows in set. Elapsed: 0.007 sec.f5abc88ff7e4 :) INSERT INTO test_dt VALUES(1),(2),('0000-00-00'),('2020-11-11');INSERT INTO test_dt VALUESOk.4 rows in set. Elapsed: 0.025 sec.f5abc88ff7e4 :) SELECT * FROM test_dt;SELECT *FROM test_dt┌───────date─┐│ 1970-01-02 ││ 1970-01-03 ││ 1970-01-01 ││ 2020-11-11 │└────────────┘4 rows in set. Elapsed: 0.005 sec.
Date类型中的0或者’0000-00-00’代表1970-01-01
DateTime#
DateTime是通常概念中的年月日时分秒,支持时区,但是不支持毫秒表示,也就是此类型精确到秒。它的定义格式为:
column_name DateTime[(time_zone)]
可以表示的范围:[1970-01-01 00:00:00, 2105-12-31 23:59:59]。使用DateTime的时候需要注意几点:
- DateTime时间点实际上保存为Unix时间戳(笔者探究过这里的单位应该是秒),与时区或者夏时制无关
- DateTime的时区并不存储在列数据或者结果集中,而是存储在列元数据中
- 创建表定义DateTime类型的列的时候如果不指定时区,则使用服务器或者操作系统中设置的默认时区
- 创建表定义DateTime类型的列的时候如果不指定时区,ClickHouse客户端会使用ClickHouse服务端的时区,也可以通过参数—use_client_time_zone指定
- 可以通过配置值date_time_input_format或date_time_output_format分别指定DateTime类型数据的输入和输出格式
- DateTime类型数据插入的时候,整数会被视为Unix时间戳,并且会使用UTC作为时区(零时区),字符串会被视为使用了时区的日期时间(取决于服务或者系统),再基于时区转化为对应的Unix时间戳进行存储
可以测试一下:
f5abc88ff7e4 :) CREATE TABLE test_dt(t DateTime,tz DateTime('Asia/Shanghai')) ENGINE = Memory;CREATE TABLE test_dt(`t` DateTime,`tz` DateTime('Asia/Shanghai'))ENGINE = MemoryOk.0 rows in set. Elapsed: 0.029 sec.f5abc88ff7e4 :) INSERT INTO test_dt VALUES(1605194721,'2020-11-01 00:00:00'); # <-------------- 这里的1605194721是北京时间2020-11-12 23:25:21的Unix时间戳INSERT INTO test_dt VALUESOk.1 rows in set. Elapsed: 0.006 sec.f5abc88ff7e4 :) SELECT * FROM test_dt;SELECT *FROM test_dt┌───────────────────t─┬──────────────────tz─┐│ 2020-11-12 15:25:21 │ 2020-11-01 00:00:00 │└─────────────────────┴─────────────────────┘1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT toDateTime(t, 'Asia/Shanghai') AS sh_time,toDateTime(tz, 'Europe/London') AS lon_time FROM test_dt;SELECTtoDateTime(t, 'Asia/Shanghai') AS sh_time,toDateTime(tz, 'Europe/London') AS lon_timeFROM test_dt┌─────────────sh_time─┬────────────lon_time─┐│ 2020-11-12 23:25:21 │ 2020-10-31 16:00:00 │└─────────────────────┴─────────────────────┘1 rows in set. Elapsed: 0.004 sec.
DateTime64#
DateTime64其实和DateTime类型差不多,不过可以额外表示亚秒,所谓亚秒,精度就是10 ^ (-n)(10的负n次方)秒,例如0.1秒、0.01秒等等。它的定义格式为:
column_name DateTime64(precision [, time_zone])
测试一下:
f5abc88ff7e4 :) SELECT toDateTime64(now(), 5, 'Asia/Shanghai') AS column, toTypeName(column) AS x;SELECTtoDateTime64(now(), 5, 'Asia/Shanghai') AS column,toTypeName(column) AS x┌────────────────────column─┬─x──────────────────────────────┐│ 2020-11-12 23:45:56.00000 │ DateTime64(5, 'Asia/Shanghai') │└───────────────────────────┴────────────────────────────────┘1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) CREATE TABLE test_dt64(t DateTime64(2),tz DateTime64(3,'Asia/Shanghai')) ENGINE = Memory;CREATE TABLE test_dt64(`t` DateTime64(2),`tz` DateTime64(3, 'Asia/Shanghai'))ENGINE = MemoryOk.0 rows in set. Elapsed: 0.017 sec.f5abc88ff7e4 :) INSERT INTO test_dt64 VALUES(1605194721,'2020-11-01 00:00:00');INSERT INTO test_dt64 VALUESOk.1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT * FROM test_dt64;SELECT *FROM test_dt64┌──────────────────────t─┬──────────────────────tz─┐│ 1970-07-05 18:52:27.21 │ 2020-11-01 00:00:00.000 │└────────────────────────┴─────────────────────────┘1 rows in set. Elapsed: 0.004 sec.
复合类型#
复合类型主要包括数组Array(T)、元组Tuple(T,S….R)、枚举Enum和嵌套Nested,这里的复合指的是同类型多元素复合或者多类型多元素复合。
Array#
数组类型Array(T)中的T可以是任意的数据类型(但是同一个数组的元素类型必须唯一),类似于泛型数组T[]。它的定义如下:
column_name Array(T)## 定义major Array(String)## 写入VALUES (['a','b','c']), (['A','B','C'])
编写测试例子:
f5abc88ff7e4 :) CREATE TABLE test_arr(a Array(UInt8),b Array(String)) ENGINE = Memory;CREATE TABLE test_arr(`a` Array(UInt8),`b` Array(String))ENGINE = MemoryOk.0 rows in set. Elapsed: 0.017 sec.f5abc88ff7e4 :) INSERT INTO test_arr VALUES([1,2,3],['throwable','doge']);INSERT INTO test_arr VALUESOk.1 rows in set. Elapsed: 0.005 sec.f5abc88ff7e4 :) SELECT * FROM test_arr;SELECT *FROM test_arr┌─a───────┬─b────────────────────┐│ [1,2,3] │ ['throwable','doge'] │└─────────┴──────────────────────┘1 rows in set. Elapsed: 0.004 sec.f5abc88ff7e4 :)
需要注意的是:
- 可以使用array()函数或者[]快速创建数组
- 快速创建数组时,ClickHouse会自动将参数类型定义为可以存储所有列出的参数的”最窄”的数据类型,可以理解为最小代价原则
- ClickHouse无法确定数组的数据类型(常见的是快速创建数组使用了多类型元素),将会返回一个异常(例如SELECT array(1, ‘a’)是非法的)
- 如果数组中的元素存在NULL,元素类型将会变为Nullable(T) ```sql f5abc88ff7e4 :) SELECT array(1, 2) AS x, toTypeName(x);
SELECT [1, 2] AS x, toTypeName(x)
┌─x─────┬─toTypeName(array(1, 2))─┐ │ [1,2] │ Array(UInt8) │ └───────┴─────────────────────────┘
1 rows in set. Elapsed: 0.006 sec.
f5abc88ff7e4 :) SELECT [1, 2, NULL] AS x, toTypeName(x);
SELECT [1, 2, NULL] AS x, toTypeName(x)
┌─x──────────┬─toTypeName([1, 2, NULL])─┐ │ [1,2,NULL] │ Array(Nullable(UInt8)) │ └────────────┴──────────────────────────┘
1 rows in set. Elapsed: 0.004 sec.
f5abc88ff7e4 :) SELECT array(1, ‘a’)
SELECT [1, ‘a’]
Received exception from server (version 20.10.3): Code: 386. DB::Exception: Received from clickhouse-server:9000. DB::Exception: There is no supertype for types UInt8, String because some of them are String/FixedString and some of them are not.
0 rows in set. Elapsed: 0.015 sec.
<a name="h7LXo"></a>### Tuple[#](https://www.cnblogs.com/throwable/p/14018954.html#tuple)元组(Tuple(S,T...R))类型的数据由1-n个元素组成,每个元素都可以使用单独(可以不相同)的数据类型。它的定义如下:```sqlcolumn_name Tuple(S,T...R)## 定义x_col Tuple(UInt64, String, DateTime)## 写入VALUES((1,'throwables','2020-11-14 00:00:00')),((2,'throwables','2020-11-13 00:00:00'))
需要注意的是:
- 类似于数组类型Array,元组Tuple对于每个元素的类型推断也是基于最小代价原则
- 创建表的时候明确元组Tuple中元素的类型定义后,数据写入的时候元素的类型会进行检查,必须一一对应,否则会抛出异常(如x_col Tuple(UInt64, String)只能写入(1,’a’)而不能写入(‘a’,’b’)) ```sql f5abc88ff7e4 :) SELECT tuple(1,’1’,NULL) AS x, toTypeName(x);
SELECT (1, ‘1’, NULL) AS x, toTypeName(x)
┌─x────────────┬─toTypeName(tuple(1, ‘1’, NULL))─────────┐ │ (1,’1’,NULL) │ Tuple(UInt8, String, Nullable(Nothing)) │ └──────────────┴─────────────────────────────────────────┘
1 rows in set. Elapsed: 0.004 sec.
f5abc88ff7e4 :) CREATE TABLE test_tp(id UInt64, a Tuple(UInt64,String)) ENGINE = Memory;
CREATE TABLE test_tp
(
id UInt64,
a Tuple(UInt64, String)
)
ENGINE = Memory
Ok.
0 rows in set. Elapsed: 0.018 sec.
f5abc88ff7e4 :) INSERT INTO test_tp VALUES(1,(999,’throwable’)),(2,(996,’doge’));
INSERT INTO test_tp VALUES
Ok.
2 rows in set. Elapsed: 0.003 sec.
f5abc88ff7e4 :) INSERT INTO test_tp VALUES(1,(‘doge’,’throwable’));
INSERT INTO test_tp VALUES
Exception on client: Code: 6. DB::Exception: Cannot parse string ‘doge’ as UInt64: syntax error at begin of string. Note: there are toUInt64OrZero and toUInt64OrNull functions, which returns zero/NULL instead of throwing exception.: while executing ‘FUNCTION CAST(_dummy_0, ‘Tuple(UInt64, String)’) Tuple(UInt64, String) = CAST(_dummy_0, ‘Tuple(UInt64, String)’)’: data for INSERT was parsed from query
这里可以看出ClickHouse在处理Tuple类型数据写入发现类型不匹配的时候,会尝试进行类型转换,也就是按照写入的数据对应位置的元素类型和列定义Tuple中对应位置的类型做转换(如果类型一致则不需要转换),类型转换异常就会抛出异常。类型为Tuple(UInt64,String)实际上可以写入('111','222')或者(111,'222'),但是不能写入('a','b')。转换过程会调用内置函数,如无意外会消耗额外的性能和时间,因此更推荐在写入数据的时候确保每个位置元素和列定义时候的元素类型一致。<a name="UMvCg"></a>### Enum[#](https://www.cnblogs.com/throwable/p/14018954.html#enum)枚举类型Enum算是ClickHouse中独创的复合类型,它使用有限键值对K-V(String:Int)的形式定义数据,有点像Java中的HashMap结构,而KEY和VALUE都不允许NULL值,但是KEY允许设置为空字符串。Enum的数据查询一般返回是KEY的集合,写入可以是KEY也可以是VALUE。它的定义如下:```sqlcolumn_name Enum('str1' = num1, 'str2' = num2 ...)# 例如sex Enum('male' = 1,'female' = 2,'other' = 3)
Enum可以表示的值范围是16位,也就是VALUE只能从[-32768,32767]中取值。它衍生出两种简便的类型Enum8(本质是(String:Int18),代表值范围是8位,也就是[-128,127])和Enum16(本质是(String:Int16),代表值范围是16位,也就是[-32768,32767]),如果直接使用原生类型Enum则会根据实际定义的K-V对数量最终决定具体选用Enum8或是Enum16存储数据。测试一下:
