前提
前邊一篇文章詳細分析瞭如何在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 最近玩《王國守衛戰-復仇》鴿了很久)