ClickHouse数据库数据定义手记之数据类型(下)

简介: 前边一篇文章详细分析了如何在Windows10系统下搭建ClickHouse的开发环境,接着需要详细学习一下此数据库的数据定义,包括数据类型、DDL和DML。ClickHouse作为一款完备的DBMS,提供了类似于MySQL(其实有部分语法差别还是比较大的)的DDL与DML功能,并且实现了大部分标准SQL规范中的内容。系统学习ClickHouse的数据定义能够帮助开发者更深刻地理解和使用ClickHouse。

复合类型



复合类型主要包括数组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结构,而KEYVALUE都不允许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 BYGROUP BYINDISTINCT等操作。按照常规思维来说,排序、聚合、去重等操作使用整型对比使用字符串在性能上应该有不错的提升,所以在使用有限状态集合的场景使用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、域名DomainNothing


Nullable


Nullable不算一种独立的类型,它是一种其他类型的类似辅助修饰符的修饰类型,与其他基本类型搭配使用。如果熟悉Java中的java.lang.OptionalNullable的功能就是与Optional相似,表示某个基本数据类型可以为Null值(写入时候不传值)。它的定义如下:


column_name Nullable(TypeName)
# 如
amount Nullable(Decimal(10,2))
age Nullable(UInt16)
createTime Nullable(DateTime)
复制代码


需要注意几点:

  • NULLNullable的默认值,也就是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
  • ClickHouseArray类型写入数据的时候可以使用[元素x,元素y]的格式,也可以使用java.sql.Array进行传递,具体是ClickHouseArray,读取数据也可以类似地操作
  • 枚举Enum会直接转换为Java中的String类型


小结



本文已经十分详细分析了ClickHouse的各种数据类型的功能和基本使用例子,下一篇文章将会分析DDL部分。ClickHouse中的很多DDL的用法比较独特,和传统关系型数据库的DDL区别比较大。


(本文完 c-7-d e-a-20201118 最近玩《王国守卫战-复仇》鸽了很久)


相关文章
|
8天前
|
存储 关系型数据库 MySQL
MySQL数据库的数据类型、语法和高级查询
MySQL数据库的数据类型、语法和高级查询
22 0
|
2天前
|
JSON 前端开发 JavaScript
SSMP整合案例第五步 在前端页面上拿到service层调数据库里的数据后列表
SSMP整合案例第五步 在前端页面上拿到service层调数据库里的数据后列表
6 2
|
2天前
|
SQL druid Java
传统后端SQL数据层替代解决方案: 内置数据源+JdbcTemplate+H2数据库 详解
传统后端SQL数据层替代解决方案: 内置数据源+JdbcTemplate+H2数据库 详解
7 1
|
2天前
|
分布式计算 大数据 数据处理
MaxCompute操作报错合集之odps数据库T1有几百行的数据,为什么出来只有5行的数据
MaxCompute是阿里云提供的大规模离线数据处理服务,用于大数据分析、挖掘和报表生成等场景。在使用MaxCompute进行数据处理时,可能会遇到各种操作报错。以下是一些常见的MaxCompute操作报错及其可能的原因与解决措施的合集。
|
5天前
|
存储 关系型数据库 MySQL
关系型数据库mysql数据文件存储
【6月更文挑战第15天】
15 4
|
2天前
|
分布式计算 运维 DataWorks
MaxCompute产品使用问题之数据如何导出到本地部署的CK
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
5天前
|
存储 SQL 关系型数据库
SQL 用于各种数据库的数据类型
SQL 用于各种数据库的数据类型
14 2
|
6天前
|
Java 数据库连接 数据库
实现Spring Boot与MyBatis结合进行数据库历史数据的定时迁移
实现Spring Boot与MyBatis结合进行数据库历史数据的定时迁移
21 2
|
4天前
|
SQL 数据库
零基础学习数据库SQL语句之操作表中数据的DML语句
零基础学习数据库SQL语句之操作表中数据的DML语句
9 0
零基础学习数据库SQL语句之操作表中数据的DML语句
|
10天前
|
存储 NoSQL 算法
图数据库:连接数据的新模式
【6月更文挑战第16天】图数据库是处理复杂关系数据的新兴技术,使用节点、边和属性表示数据间关系。它提供强大的关系表达能力、灵活性、实时性和扩展性。新模式包括关系网络可视化、基于路径的查询、内置图算法支持,适用于推荐系统和社交网络分析,助力企业挖掘数据价值并应对大数据时代挑战。随着技术发展,图数据库将在数据连接和分析中扮演关键角色。

热门文章

最新文章