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 最近玩《王国守卫战-复仇》鸽了很久)


相关文章
|
15天前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
在9月20日2024云栖大会上,阿里云智能集团副总裁,数据库产品事业部负责人,ACM、CCF、IEEE会士(Fellow)李飞飞发表《从数据到智能:Data+AI驱动的云原生数据库》主题演讲。他表示,数据是生成式AI的核心资产,大模型时代的数据管理系统需具备多模处理和实时分析能力。阿里云瑶池将数据+AI全面融合,构建一站式多模数据管理平台,以数据驱动决策与创新,为用户提供像“搭积木”一样易用、好用、高可用的使用体验。
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
|
17天前
|
SQL 关系型数据库 数据库
国产数据实战之docker部署MyWebSQL数据库管理工具
【10月更文挑战第23天】国产数据实战之docker部署MyWebSQL数据库管理工具
56 4
国产数据实战之docker部署MyWebSQL数据库管理工具
|
14天前
|
关系型数据库 分布式数据库 数据库
云栖大会|从数据到决策:AI时代数据库如何实现高效数据管理?
在2024云栖大会「海量数据的高效存储与管理」专场,阿里云瑶池讲师团携手AMD、FunPlus、太美医疗科技、中石化、平安科技以及小赢科技、迅雷集团的资深技术专家深入分享了阿里云在OLTP方向的最新技术进展和行业最佳实践。
|
22天前
|
人工智能 Cloud Native 容灾
云数据库“再进化”,OB Cloud如何打造云时代的数据底座?
云数据库“再进化”,OB Cloud如何打造云时代的数据底座?
|
30天前
|
SQL 存储 关系型数据库
数据储存数据库管理系统(DBMS)
【10月更文挑战第11天】
85 3
|
1月前
|
SQL 存储 关系型数据库
添加数据到数据库的SQL语句详解与实践技巧
在数据库管理中,添加数据是一个基本操作,它涉及到向表中插入新的记录
|
1月前
|
关系型数据库 MySQL 数据库
MySQL数据库基础(数据库操作,常用数据类型,表的操作)
MySQL数据库基础(数据库操作,常用数据类型,表的操作)
34 5
|
1月前
|
SQL 监控 数据处理
SQL数据库数据修改操作详解
数据库是现代信息系统的重要组成部分,其中SQL(StructuredQueryLanguage)是管理和处理数据库的重要工具之一。在日常的业务运营过程中,数据的准确性和及时性对企业来说至关重要,这就需要掌握如何在数据库中正确地进行数据修改操作。本文将详细介绍在SQL数据库中如何修改数据,帮助读者更好
188 4
|
15天前
|
数据采集 存储 分布式计算
ClickHouse大规模数据导入优化:批处理与并行处理
【10月更文挑战第27天】在数据驱动的时代,高效的数据导入和处理能力是企业竞争力的重要组成部分。作为一位数据工程师,我在实际工作中经常遇到需要将大量数据导入ClickHouse的需求。ClickHouse是一款高性能的列式数据库系统,非常适合进行大规模数据的分析和查询。然而,如何优化ClickHouse的数据导入过程,提高导入的效率和速度,是我们面临的一个重要挑战。本文将从我个人的角度出发,详细介绍如何通过批处理、并行处理和数据预处理等技术优化ClickHouse的数据导入过程。
36 0
|
1月前
|
存储 分布式计算 数据库
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版
阿里云国际版设置数据库云分析工作负载的 ClickHouse 版