ClickHouse(05)ClickHouse資料型別詳解

2022-09-03 21:01:40

ClickHouse屬於分析型資料庫,ClickHouse提供了許多資料型別,它們可以劃分為基礎型別、複合型別和特殊型別。其中基礎型別使ClickHouse具備了描述資料的基本能力,而另外兩種型別則使ClickHouse的資料表達能力更加豐富立體。

基礎型別

基礎型別只有數值、字串和時間三種型別,沒有Boolean型別,但可以使用整型的0或1替代。

數值型別

數值型別分為整數、浮點數和定點數三類,接下來分別進行說明。

Int

在普遍觀念中,常用Tinyint、Smallint、Int和Bigint指代整數的不同取值範圍。而ClickHouse則直接使用Int8、Int16、Int32和Int64指代4種大小的Int型別,其末尾的數位正好表明了佔用位元組的大小(8位元=1位元組)。


Float

與整數類似,ClickHouse直接使用Float32和Float64代表單精度浮點數以及雙精度浮點數。在使用浮點數的時候,要意識到它是有限精度的。對Float32和Float64寫入超過有效精度的數值,結果就會出現資料誤差,會被截斷。

另外,ClickHousae對於正無窮、負無窮、以及非數值型別的表示。

  • 正無窮:inf
  • 負無窮:-inf
  • 非數值型別:

Decimal

要更高精度的數值運算,需要使用定點數。ClickHouse提供了Decimal32、Decimal64和Decimal128三種精度的定點數。可以通過兩種形式宣告定點:簡寫方式有Decimal32(S)、Decimal64(S)、Decimal128(S)三種,原生方式為Decimal(P,S),其中:

  • P代表精度,決定總位數(整數部分+小數部分),取值範圍是1~38;
  • S代表規模,決定小數位數,取值範圍是0~P。

字串型別

字串型別可以細分為String、FixedString和UUID三類。

String

字串由String定義,長度不限。因此在使用String的時候無須宣告大小。它完全代替了傳統意義上資料庫的Varchar、Text、Clob和Blob等字元型別。String型別不限定字元集,因為它根本就沒有這個概念,所以可以將任意編碼的字串存入其中。

FixedString

FixedString型別和傳統意義上的Char型別有些類似,對於一些字元有明確長度的場合,可以使用固定長度的字串。定長字串通過FixedString(N)宣告,其中N表示字串長度。但與Char不同的是,FixedString使用null位元組填充末尾字元,而Char通常使用空格填充。比如在下面的例子中,字串‘abc’雖然只有3位,但長度卻是5,因為末尾有2位空字元填充。

UUID

UUID是一種資料庫常見的主鍵型別,在ClickHouse中直接把它作為一種資料型別。UUID共有32位元,它的格式為8-4-4-4-12。如果一個UUID型別的欄位在寫入資料時沒有被賦值,則會依照格式使用0填充。

時間型別

時間型別分為DateTime、DateTime64和Date三類。ClickHouse目前沒有時間戳型別。時間型別最高的精度是秒,也就是說,如果需要處理毫秒、微秒等大於秒解析度的時間,則只能藉助UInt型別實現。

DateTime

DateTime型別包含時、分、秒資訊,精確到秒。

DateTime64

DateTime64可以記錄亞秒,它在DateTime之上增加了精度的設定。

Date

Date型別不包含具體的時間資訊,只精確到天。

複合型別

ClickHouse還提供了陣列、元組、列舉和巢狀四類複合型別。

陣列Array

陣列有兩種定義形式,常規方式array(T),或者簡寫方式[T]。在同一個陣列內可以包含多種資料型別,例如陣列[1,2.0]是可行的。但各型別之間必須相容,例如陣列[1,'2']則會報錯。

在查詢時並不需要主動宣告陣列的元素型別。因為ClickHouse的陣列擁有型別推斷的能力,推斷依據:以最小儲存代價為原則,即使用最小可表達的資料型別。

--常規定義方式
SELECT array(1, 2) as a , toTypeName(a)
┌─a───┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8)              │
└─────┴────────────────┘

--簡寫定義方式
SELECT [1, 2]

--建表時資料型別定義
CREATE TABLE Array_TEST (
    c1 Array(String)
) engine = Memory

元組Tuple

元組型別由1~n個元素組成,每個元素之間允許設定不同的資料型別,且彼此之間不要求相容。元組同樣支援型別推斷,其推斷依據仍然以最小儲存代價為原則。與陣列類似,元組也可以使用兩種方式定義,常規方式tuple(T),或者簡寫方式(T)。

--常規定義方式
SELECT tuple(1,'a',now()) AS x, toTypeName(x)
┌─x─────────────────┬─toTypeName(tuple(1, 'a', now()))─┐
│ (1,'a','2019-08-28 21:36:32') │ Tuple(UInt8, String, DateTime)    │
└───────────────────┴─────────────────────┘

--簡寫定義方式
SELECT (1,'a',now()) AS x, toTypeName(x)
┌─x─────────────────┬─toTypeName(tuple(1, 'a', now()))─┐
│ (1,'a','2019-08-28 21:36:32') │ Tuple(UInt8, String, DateTime)    │
└───────────────────┴─────────────────────┘

--建表時元組型別定義
CREATE TABLE Array_TEST (
    c1 Array(String)
) engine = Memory

列舉Enum

ClickHouse支援列舉型別,這是一種在定義常數時經常會使用的資料型別。ClickHouse提供了Enum8和Enum16兩種列舉型別,它們除了取值範圍不同之外,別無二致。列舉固定使用(String:Int)Key/Value鍵值對的形式定義資料,所以Enum8和Enum16分別會對應(String:Int8)和(String:Int16)。

在定義列舉集合的時候,有幾點需要注意。首先,Key和Value是不允許重複的,要保證唯一性。其次,Key和Value的值都不能為Null,但Key允許是空字串。在寫入列舉資料的時候,只會用到Key字串部分。

資料在寫入的過程中,會對照列舉集合項的內容逐一檢查。如果Key字串不在集合範圍內則會丟擲異常。

為什麼還需要專門的列舉型別呢?這是出於效能的考慮。因為列舉定義中的Key屬於String型別,但在後續對列舉的所有操作中(包括排序、分組、去重、過濾等),會使用Int型別的Value值。

--列舉型別定義
CREATE TABLE Enum_TEST (
    c1 Enum8('ready' = 1, 'start' = 2, 'success' = 3, 'error' = 4)
) ENGINE = Memory;

--列舉型別插入
INSERT INTO Enum_TEST VALUES('ready');
INSERT INTO Enum_TEST VALUES('start');

巢狀Nested

巢狀型別,顧名思義是一種巢狀表結構。一張資料表,可以定義任意多個巢狀型別欄位,但每個欄位的巢狀層級只支援一級,即巢狀表內不能繼續使用巢狀型別。對於簡單場景的層級關係或關聯關係,使用巢狀型別也是一種不錯的選擇。

--建立Nested語句
CREATE TABLE nested_test (
    name String,
    age  UInt8 ,
    dept Nested(
        id UInt8,
        name String
    )
) ENGINE = Memory;

ClickHouse的巢狀型別和傳統的巢狀型別不相同,導致在初次接觸它的時候會讓人十分困惑。以上面這張表為例,如果按照它的字面意思來理解,會很容易理解成nested_test與dept是一對一的包含關係,其實這是錯誤的。

巢狀型別本質是一種多維陣列的結構。巢狀表中的每個欄位都是一個陣列,並且行與行之間陣列的長度無須對齊,在同一行資料內每個陣列欄位的長度必須相等。

插入資料時候每一個nestd欄位要需要一個陣列。

--插入資料
INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001,10002], ['研發部','技術支援中心','測試部']);
--行與行之間,陣列長度無須對齊
INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研發部','技術支援中心']); 


--查詢資料
SELECT name, dept.id, dept.name FROM nested_test
┌─name─┬─dept.id──┬─dept.name─────────────┐
│ bruce │ [16,17,18] │ ['研發部','技術支援中心','測試部'] │
└────┴───────┴────────────────────┘

特殊資料型別

Nullable

Nullable並不能算是一種獨立的資料型別,它更像是一種輔助的修飾符,需要與基礎資料型別一起搭配使用。Nullable型別與Java8的Optional物件有些相似,它表示某個基礎資料型別可以是Null值。

CREATE TABLE Null_TEST (
    c1 String,
    c2 Nullable(UInt8)
) ENGINE = TinyLog;
--通過Nullable修飾後c2欄位可以被寫入Null值:
INSERT INTO Null_TEST VALUES ('nauu',null)
INSERT INTO Null_TEST VALUES ('bruce',20)
SELECT c1 , c2 ,toTypeName(c2) FROM Null_TEST
┌─c1───┬───c2─┬─toTypeName(c2)─┐
│ nauu   │ NULL    │ Nullable(UInt8) │
│ bruce  │ 20      │ Nullable(UInt8) │
└─────┴──────┴───────────┘

Domain

域名型別分為IPv4和IPv6兩類,本質上它們是對整型和字串的進一步封裝。IPv4型別是基於UInt32封裝的。

ClickHouse相關資料分享

ClickHouse經典中文檔案分享

參考文章

ClickHouse(05)ClickHouse資料型別詳解