ClickHouse資料庫數據定義手記之數據類型
前提
前邊一篇文章詳細分析了如何在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;
輸出結果:
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(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.
尷尬,上面的shell
執行結果有點長,變形了。
浮點數
浮點數包括單精度浮點數Float32
和雙精度浮點數Float64
👇👇
類型 | 位元組(byte)大小 | 有效精度(排除最左邊的零小數位數) | 備註 |
---|---|---|---|
Float32 |
4 |
7 |
小數點後除去左邊的零後第8 位起會產生數據溢出 |
Float64 |
8 |
16 |
小數點後除去左邊的零後第17 位起會產生數據溢出 |
可以做一個小測試:
f5abc88ff7e4 :) SELECT toFloat32('0.1234567890') AS a,toTypeName(a);
SELECT
toFloat32('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);
SELECT
toFloat32('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);
SELECT
toFloat64('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);
SELECT
toFloat64('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] |
(-1*10^(P - S), 1*10^(P - S)) |
Decimal32(S) |
[1,9] |
[0,P] |
(-1*10^(9 - S), 1*10^(9 - S)) |
Decimal64(S) |
[10,18] |
[0,P] |
(-1*10^(18 - S), 1*10^(18 - S)) |
Decimal128(S) |
[19,38] |
[0,P] |
(-1*10^(38 - S), 1*10^(38 - S)) |
Decimal256(S) |
[39,76] |
[0,P] |
(-1*10^(76 - S), 1*10^(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
(結果小數位和被除數小數位一致)
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.
重點注意:如果從事的是金融領域等追求準確精度的數值存儲,不能使用浮點數,而應該考慮使用整型或者定點數,舍入儘可能交由程式規則處理,畢竟資料庫是存儲數據的工具,不應該承擔太多處理數據計算的職能。
布爾值
ClickHouse
中不存在布爾值類型,官方建議使用UInt8
類型,通過值0
或1
表示false
或true
。
字元串類型
字元串類型主要包括:
- 不定長(動態長度)字元串
String
- 固定長度字元串
FixedString(N)
,這裡的N
是最大位元組數,而不是長度,例如UTF-8
字元佔用3
個位元組,GBK
字元佔用2
個位元組 - 特殊字元串
UUID
(存儲的是數值,只是形式是字元串)
ClickHouse
中沒有編碼的概念,字元串可以包含一組任意位元組,這些位元組按原樣存儲和輸出。這個編碼和解碼操作推測完全移交給客戶端完成。一般情況下,推薦使用UTF-8
編碼存儲文本類型內容,這樣就能在不進行轉換的前提下讀取和寫入數據。
String
String
類型不限制字元串的長度,可以直接替代其他DBMS
的VARCHAR
、BLOB
、CLOB
等字元串類型,相比VARCHAR
這類要考慮預測數據最大長度,顯然String
無比便捷。使用Java
語言開發,直接使用String
類型承接即可。String
類型的數據列的定義如下:
column_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 = Memory
Ok.
0 rows in set. Elapsed: 0.018 sec.
f5abc88ff7e4 :) INSERT INTO test_u VALUES (1,generateUUIDv4());
INSERT INTO test_u VALUES
Ok.
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 = Memory
Ok.
0 rows in set. Elapsed: 0.025 sec.
f5abc88ff7e4 :) INSERT INTO dt VALUES(1),(2),('0000-00-00'),('2020-11-11');
INSERT INTO dt VALUES
Received 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 VALUES
Ok.
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 = Memory
Ok.
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 VALUES
Ok.
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;
SELECT
toDateTime(t, 'Asia/Shanghai') AS sh_time,
toDateTime(tz, 'Europe/London') AS lon_time
FROM 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;
SELECT
toDateTime64(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 = Memory
Ok.
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 VALUES
Ok.
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 = 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 最近玩《王國守衛戰-復仇》鴿了很久)