复合类型
复合类型主要包括数组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 = Memory Ok. 0 rows in set. Elapsed: 0.017 sec. f5abc88ff7e4 :) INSERT INTO test_arr VALUES([1,2,3],['throwable','doge']); INSERT INTO test_arr VALUES Ok. 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)
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. 复制代码
Tuple
元组(Tuple(S,T...R)
)类型的数据由1-n
个元素组成,每个元素都可以使用单独(可以不相同)的数据类型。它的定义如下:
column_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')
)
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')
。转换过程会调用内置函数,如无意外会消耗额外的性能和时间,因此更推荐在写入数据的时候确保每个位置元素和列定义时候的元素类型一致。
Enum
枚举类型Enum
算是ClickHouse
中独创的复合类型,它使用有限键值对K-V(String:Int)
的形式定义数据,有点像Java
中的HashMap
结构,而KEY
和VALUE
都不允许NULL
值,但是KEY
允许设置为空字符串。Enum
的数据查询一般返回是KEY
的集合,写入可以是KEY
也可以是VALUE
。它的定义如下:
column_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
存储数据。测试一下:
f5abc88ff7e4 :) CREATE TABLE test_e(sex Enum('male' = 1,'female' = 2,'other' = 3)) ENGINE = Memory; CREATE TABLE test_e ( `sex` Enum('male' = 1, 'female' = 2, 'other' = 3) ) ENGINE = Memory Ok. 0 rows in set. Elapsed: 0.021 sec. f5abc88ff7e4 :) INSERT INTO test_e VALUES(1),(2),('other'); INSERT INTO test_e VALUES Ok. 3 rows in set. Elapsed: 0.004 sec. f5abc88ff7e4 :) SELECT sex,CAST(sex,'Int8') FROM test_e SELECT sex, CAST(sex, 'Int8') FROM test_e ┌─sex────┬─CAST(sex, 'Int8')─┐ │ male │ 1 │ │ female │ 2 │ │ other │ 3 │ └────────┴───────────────────┘ 3 rows in set. Elapsed: 0.005 sec. 复制代码
ClickHouse
中的Enum
本质就是String:Int
,特化一个这样的类型,方便定义有限集合的键值对,枚举的VALUE
是整型数值,会直接参与ORDER BY
、GROUP BY
、IN
、DISTINCT
等操作。按照常规思维来说,排序、聚合、去重等操作使用整型对比使用字符串在性能上应该有不错的提升,所以在使用有限状态集合的场景使用Enum
类型比使用String
定义枚举集合理论上有天然优势。
Nested
嵌套类型Nested
算是一种比较奇特的类型。如果使用过GO
语言,Nested
类型数据列定义的时候有点像GO
语言的结构体:
column_name Nested( field_name_1 Type1, field_name_2 Type2 ) ## 定义 major Nested( id UInt64, name String ) ## 写入 VALUES ([1,2],['Math','English']) ## 查询 SELECT major.id,major.name FROM 复制代码
ClickHouse
的嵌套类型和固有思维中传统的嵌套类型大有不同,它的本质是一种多维数组结构,可以这样理解:
major Nested( id UInt64, name String ) ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ major.id Array(UInt64) major.name Array(String) ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Java中的实体类 class Entity { Long id; List<Major> majors; } class Major { Long id; String name; } 复制代码
嵌套类型行与行之间的数组长度无须固定,但是同一行中嵌套表内每个数组的长度必须对齐,例如:
行号 | major.id |
major.name |
1 | [1,2] | ['M','N'] |
2 | [1,2,3] | ['M','N','O'] |
3(异常) | [1,2,3,4] | ['M','N'] |
测试一下:
f5abc88ff7e4 :) CREATE TABLE test_nt(id UInt64,n Nested(id UInt64,name String)) ENGINE Memory; CREATE TABLE test_nt ( `id` UInt64, `n` Nested( id UInt64, name String) ) ENGINE = Memory Ok. 0 rows in set. Elapsed: 0.020 sec. f5abc88ff7e4 :) INSERT INTO test_nt VALUES (1,[1,2,3],['a','b','c']),(2,[999],['throwable']); INSERT INTO test_nt VALUES Ok. 2 rows in set. Elapsed: 0.003 sec. f5abc88ff7e4 :) SELECT * FROM test_nt; SELECT * FROM test_nt ┌─id─┬─n.id────┬─n.name────────┐ │ 1 │ [1,2,3] │ ['a','b','c'] │ │ 2 │ [999] │ ['throwable'] │ └────┴─────────┴───────────────┘ 2 rows in set. Elapsed: 0.005 sec. 复制代码
可以通过ARRAY JOIN
子句实现嵌套类型的子表数据平铺,类似于MySQL
中的行转列:
f5abc88ff7e4 :) SELECT n.id,n.name FROM test_nt ARRAY JOIN n; SELECT n.id, n.name FROM test_nt ARRAY JOIN n ┌─n.id─┬─n.name────┐ │ 1 │ a │ │ 2 │ b │ │ 3 │ c │ │ 999 │ throwable │ └──────┴───────────┘ 复制代码
特殊类型
特殊类型主要包括Nullable
、域名Domain
和Nothing
。
Nullable
Nullable
不算一种独立的类型,它是一种其他类型的类似辅助修饰符的修饰类型,与其他基本类型搭配使用。如果熟悉Java
中的java.lang.Optional
,Nullable
的功能就是与Optional
相似,表示某个基本数据类型可以为Null
值(写入时候不传值)。它的定义如下:
column_name Nullable(TypeName) # 如 amount Nullable(Decimal(10,2)) age Nullable(UInt16) createTime Nullable(DateTime) 复制代码
需要注意几点:
NULL
是Nullable
的默认值,也就是INSERT
时候可以使用NULL
指定空值或者不传值- 不能使用
Nullable
修饰复合数据类型,但是复合数据类型中的元素可以使用Nullable
修饰 Nullable
修饰的列不能添加索引- 官网文档有一段提醒:
Nullable
几乎总是造成负面的性能影响,在设计数据库的时候必须牢记这一点,这是因为Nullable
中的列的NULL
值和列的非NULL
值会存放在两个不同的文件,所以不能添加索引,查询和写入还会涉及到非单个文件的操作
测试一下:
f5abc88ff7e4 :) CREATE TABLE test_null(id UInt64,name Nullable(String)) ENGINE = Memory; CREATE TABLE test_null ( `id` UInt64, `name` Nullable(String) ) ENGINE = Memory Ok. 0 rows in set. Elapsed: 0.022 sec. f5abc88ff7e4 :) INSERT INTO test_null VALUES(1,'throwable'),(2,NULL); INSERT INTO test_null VALUES Ok. 2 rows in set. Elapsed: 0.004 sec. f5abc88ff7e4 :) SELECT * FROM test_null; SELECT * FROM test_null ┌─id─┬─name──────┐ │ 1 │ throwable │ │ 2 │ NULL │ └────┴───────────┘ 2 rows in set. Elapsed: 0.004 sec. f5abc88ff7e4 :) 复制代码
Domain
Domain
类型也是ClickHouse
独有的类型,是基于其他类型进行封装的一种特殊类型,包括IPv4
(本质上是基于UInt32
封装,以紧凑的二进制形式存储)和IPv6
(本质上是基于FixedString(16)
封装)两种类型。它们的定义如下:
column_name IPv4 column_name IPv6 复制代码
Domain
类型的局限性:
- 不能通过
ALTER TABLE
改变当前Domain
类型列的类型 - 不能通过字符串隐式转换从其他列或者其他表插入
Domain
类型的列数据,例如A
表有String
类型存储的IP
地址格式的列,无法导入B
表中Domain
类型的列 Domain
类型对存储的值不做限制,但是写入数据的时候会校验是否满足IPv4
或者IPv6
的格式
此外,Domain
类型数据的INSERT
或者SELECT
都做了人性化格式化操作,所以在使用INSERT
语句的时候可以直接使用字符串形式写入,查询的结果虽然在客户端命令行展示的是可读的"字符串",但是如果想查询到字符串格式的结果需要使用内置函数IPv4NumToString()
和IPv6NumToString()
(这里也就说明了不支持隐式类型转换,文档中也提到CAST()
内置函数可以把IPv4
转化为UInt32
,把IPv6
转化为FixedString(16)
)。测试一下:
f5abc88ff7e4 :) CREATE TABLE test_d(id UInt64,ip IPv4) ENGINE = Memory; CREATE TABLE test_d ( `id` UInt64, `ip` IPv4 ) ENGINE = Memory Ok. 0 rows in set. Elapsed: 0.029 sec. f5abc88ff7e4 :) INSERT INTO test_d VALUES(1,'192.168.1.0'); INSERT INTO test_d VALUES Ok. 1 rows in set. Elapsed: 0.003 sec. f5abc88ff7e4 :) SELECT ip,IPv4NumToString(ip) FROM test_d; SELECT ip, IPv4NumToString(ip) FROM test_d ┌──────────ip─┬─IPv4NumToString(ip)─┐ │ 192.168.1.0 │ 192.168.1.0 │ └─────────────┴─────────────────────┘ 1 rows in set. Elapsed: 0.004 sec. 复制代码
Nothing
Nothing
不是一种显式的数据类型,它存在的唯一目的就是表示不希望存在值的情况,使用者也无法创建Nothing
类型。例如字面量NULL
其实是Nullable(Nothing)
类型,空的数组array()
(内置函数)是Nothing
类型。
f5abc88ff7e4 :) SELECT toTypeName(array()); SELECT toTypeName([]) ┌─toTypeName(array())─┐ │ Array(Nothing) │ └─────────────────────┘ 1 rows in set. Elapsed: 0.006 sec. 复制代码
所有类型的零值
ClickHouse
中所有列定义完毕之后如果没有定义默认值(这个比较复杂,在以后介绍DDL
相关的文章的时候再说),如果不使用Nullable
,那么写入数据的时候空的列会被填充对应类型的零值。各类型零值归类如下:
- 数值类型的零值为数字
0
- 字符串类型的零值为空字符串
''
,UUID
的零值为00000000-0000-0000-0000-000000000000
- 日期时间类型的零值为其存储的时间偏移量的零值
Enum
类型是定义的VALUE
值最小的为零值Array
类型的零值为[]
Tuple
类型的零值为[类型1的零值,类型2的零值......]
Nested
类型的零值为多维数组并且每个数组都是[]
- 特殊地,可以认为
Nullable
修饰的类型的零值为NULL
使用JDBC驱动
这里模拟一个场景,基本上使用所有的ClickHouse
中常用的类型。定义一张订单表:
CREATE TABLE ShoppingOrder ( id UInt64 COMMENT '主键', orderId UUID COMMENT '订单ID', amount Decimal(10,2) COMMENT '金额', createTime DateTime COMMENT '创建日期时间', customerPhone FixedString(11) COMMENT '顾客手机号', customerName String COMMENT '顾客姓名', orderStatus Enum('init' = 0,'cancel' = -1,'paid' = 1) COMMENT '订单状态', goodsIdList Array(UInt64) COMMENT '货物ID数组', address Nested(province String, city String, street String, houseNumber UInt64) COMMENT '收货地址' ) ENGINE = Memory; // 合成一行 CREATE TABLE ShoppingOrder (id UInt64 COMMENT '主键',orderId UUID COMMENT '订单ID',amount Decimal(10,2) COMMENT '金额',createTime DateTime COMMENT '创建日期时间',customerPhone FixedString(11) COMMENT '顾客手机号',customerName String COMMENT '顾客姓名', orderStatus Enum('init' = 0,'cancel' = -1,'paid' = 1) COMMENT '订单状态',goodsIdList Array(UInt64) COMMENT '货物ID数组',address Nested(province String, city String, street String, houseNumber UInt64) COMMENT '收货地址') ENGINE = Memory; 复制代码
创建完成后,调用DESC ShoppingOrder
:
f5abc88ff7e4 :) DESC ShoppingOrder; DESCRIBE TABLE ShoppingOrder ┌─name────────────────┬─type─────────────────────────────────────────┬─default_type─┬─default_expression─┬─comment──────┬─codec_expression─┬─ttl_expression─┐ │ id │ UInt64 │ │ │ 主键 │ │ │ │ orderId │ UUID │ │ │ 订单ID │ │ │ │ amount │ Decimal(10, 2) │ │ │ 金额 │ │ │ │ createTime │ DateTime │ │ │ 创建日期时间 │ │ │ │ customerPhone │ FixedString(11) │ │ │ 顾客手机号 │ │ │ │ customerName │ String │ │ │ 顾客姓名 │ │ │ │ orderStatus │ Enum8('cancel' = -1, 'init' = 0, 'paid' = 1) │ │ │ 订单状态 │ │ │ │ goodsIdList │ Array(UInt64) │ │ │ 货物ID数组 │ │ │ │ address.province │ Array(String) │ │ │ 收货地址 │ │ │ │ address.city │ Array(String) │ │ │ 收货地址 │ │ │ │ address.street │ Array(String) │ │ │ 收货地址 │ │ │ │ address.houseNumber │ Array(UInt64) │ │ │ 收货地址 │ │ │ └─────────────────────┴──────────────────────────────────────────────┴──────────────┴────────────────────┴──────────────┴──────────────────┴────────────────┘ 12 rows in set. Elapsed: 0.004 sec. 复制代码
引入clickhouse-jdbc
依赖:
<dependency> <groupId>ru.yandex.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.2.4</version> </dependency> 复制代码
编写测试案例:
@RequiredArgsConstructor @Getter public enum OrderStatus { INIT("init", 0), CANCEL("cancel", -1), PAID("paid", 1), ; private final String type; private final Integer status; public static OrderStatus fromType(String type) { for (OrderStatus status : OrderStatus.values()) { if (Objects.equals(type, status.getType())) { return status; } } return OrderStatus.INIT; } } @Data public class Address { private String province; private String city; private String street; private Long houseNumber; } @Data public class ShoppingOrder { private Long id; private String orderId; private BigDecimal amount; private OffsetDateTime createTime; private String customerPhone; private String customerName; private Integer orderStatus; private Set<Long> goodsIdList; /** * 这里实际上只有一个元素 */ private List<Address> addressList; } @Test public void testInsertAndSelectShoppingOrder() throws Exception { ClickHouseProperties props = new ClickHouseProperties(); props.setUser("root"); props.setPassword("root"); // 不创建数据库的时候会有有个全局default数据库 ClickHouseDataSource dataSource = new ClickHouseDataSource("jdbc:clickhouse://localhost:8123/default", props); ClickHouseConnection connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement("INSERT INTO ShoppingOrder VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"); // 这里可以考虑使用Snowflake算法生成自增趋势主键 long id = System.currentTimeMillis(); int idx = 1; ps.setLong(idx ++, id); ps.setString(idx ++, "00000000-0000-0000-0000-000000000000"); ps.setBigDecimal(idx ++, BigDecimal.valueOf(100L)); ps.setTimestamp(idx ++, new Timestamp(System.currentTimeMillis())); ps.setString(idx ++, "12345678901"); ps.setString(idx ++, "throwable"); ps.setString(idx ++, "init"); ps.setString(idx ++, "[1,999,1234]"); ps.setString(idx ++, "['广东省']"); ps.setString(idx ++, "['广州市']"); ps.setString(idx ++, "['X街道']"); ps.setString(idx , "[10087]"); ps.execute(); ClickHouseStatement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("SELECT * FROM ShoppingOrder"); List<ShoppingOrder> orders = Lists.newArrayList(); while (rs.next()) { ShoppingOrder order = new ShoppingOrder(); order.setId(rs.getLong("id")); order.setOrderId(rs.getString("orderId")); order.setAmount(rs.getBigDecimal("amount")); order.setCreateTime(OffsetDateTime.ofInstant(rs.getTimestamp("createTime").toInstant(), ZoneId.systemDefault())); order.setCustomerPhone(rs.getString("customerPhone")); order.setCustomerName(rs.getString("customerName")); String orderStatus = rs.getString("orderStatus"); order.setOrderStatus(OrderStatus.fromType(orderStatus).getStatus()); // Array(UInt64) -> Array<BigInteger> Array goodsIdList = rs.getArray("goodsIdList"); BigInteger[] goodsIdListValue = (BigInteger[]) goodsIdList.getArray(); Set<Long> goodsIds = Sets.newHashSet(); for (BigInteger item : goodsIdListValue) { goodsIds.add(item.longValue()); } order.setGoodsIdList(goodsIds); List<Address> addressList = Lists.newArrayList(); // Array(String) -> Array<String> Array province = rs.getArray("address.province"); List<String> provinceList = arrayToList(province); // Array(String) -> Array<String> Array city = rs.getArray("address.city"); List<String> cityList = arrayToList(city); // Array(String) -> Array<String> Array street = rs.getArray("address.street"); List<String> streetList = arrayToList(street); // UInt64 -> Array<BigInteger> Array houseNumber = rs.getArray("address.houseNumber"); BigInteger[] houseNumberValue = (BigInteger[]) houseNumber.getArray(); List<Long> houseNumberList = Lists.newArrayList(); for (BigInteger item : houseNumberValue) { houseNumberList.add(item.longValue()); } int size = provinceList.size(); for (int i = 0; i < size; i++) { Address address = new Address(); address.setProvince(provinceList.get(i)); address.setCity(cityList.get(i)); address.setStreet(streetList.get(i)); address.setHouseNumber(houseNumberList.get(i)); addressList.add(address); } order.setAddressList(addressList); orders.add(order); } System.out.println("查询结果:" + JSON.toJSONString(orders)); } private List<String> arrayToList(Array array) throws Exception { String[] v = (String[]) array.getArray(); return Lists.newArrayList(Arrays.asList(v)); } 复制代码
输出结果:
查询结果: [{ "addressList": [{ "city": "广州市", "houseNumber": 10087, "province": "广东省", "street": "X街道" }], "amount": 100.00, "createTime": "2020-11-17T23:53:34+08:00", "customerName": "throwable", "customerPhone": "12345678901", "goodsIdList": [1, 1234, 999], "id": 1605628412414, "orderId": "00000000-0000-0000-0000-000000000000", "orderStatus": 0 }] 复制代码
客户端查询:
f5abc88ff7e4 :) SELECT * FROM ShoppingOrder; SELECT * FROM ShoppingOrder ┌────────────id─┬──────────────────────────────orderId─┬─amount─┬──────────createTime─┬─customerPhone─┬─customerName─┬─orderStatus─┬─goodsIdList──┬─address.province─┬─address.city─┬─address.street─┬─address.houseNumber─┐ │ 1605628412414 │ 00000000-0000-0000-0000-000000000000 │ 100.00 │ 2020-11-17 15:53:34 │ 12345678901 │ throwable │ init │ [1,999,1234] │ ['广东省'] │ ['广州市'] │ ['X街道'] │ [10087] │ └───────────────┴──────────────────────────────────────┴────────┴─────────────────────┴───────────────┴──────────────┴─────────────┴──────────────┴──────────────────┴──────────────┴────────────────┴─────────────────────┘ 1 rows in set. Elapsed: 0.004 sec. 复制代码
实践表明:
ClickHouseDataType
中可以查看ClickHouse
各种数据类型和Java
数据类型以及SQLType
之间的对应关系,如UInt64 => BigInteger
ClickHouse
的Array
类型写入数据的时候可以使用[元素x,元素y]
的格式,也可以使用java.sql.Array
进行传递,具体是ClickHouseArray
,读取数据也可以类似地操作- 枚举
Enum
会直接转换为Java
中的String
类型
小结
本文已经十分详细分析了ClickHouse
的各种数据类型的功能和基本使用例子,下一篇文章将会分析DDL
部分。ClickHouse
中的很多DDL
的用法比较独特,和传统关系型数据库的DDL
区别比较大。
(本文完 c-7-d e-a-20201118 最近玩《王国守卫战-复仇》鸽了很久)