1、基礎類型
基礎類型只有數值、字符串、時間三種類型,沒有Boolean類型,但可以使用整型的0或1替代。
1.1、數值類型
數值類型分為整數、浮點數和定點數三類。
1.1.1、整數
普遍觀念中,用Tinyint、Smallint、Int和Bigint指代整數的不同取值范圍。ClickHouse則直接使用Int8、Int16、Int32和Int64指代4種大小的Int類型,其末尾的數字正好表明了占用字節的大小(8位=1字節),具體信息如下表所示。
1)有符號整型Int(-2n-1~2n-1):
Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 9223372036854775807]
2)無符號整型范圍UInt(0~2n-1,使用前綴U表示):
Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 9223372036854775807]
1.1.2、浮點型(Float)
與整數類似,ClickHouse直接使用Float32和Float64代表單精度浮點數以及雙精度浮點數。具體如下圖所示:
1)單精度浮點數:Float32 - float
2)雙精度浮點數:Float64 – double
在使用浮點數的時候,應當要意識到它是有限精度的。假如,分別對Float32和Float64寫入超過有效精度的數值,結果就會出現數據誤差。例如,將擁有20位小數的數值分別寫入Float32和Float64,此時結果就會出現數據誤差:
SELECT toFloat32('0.12345678901234567890') as a , toTypeName(a) a toTypeName(toFloat32(‘0.12345678901234567890’)) 0.12345679 Float32 SELECT toFloat64('0.12345678901234567890') as a , toTypeName(a) a toTypeName(toFloat64(‘0.12345678901234567890’)) 0.12345678901234568 Float64
由上:Float32從小數點后第8位起及Float64從小數點后第17位起,都產生了數據溢出。
ClickHouse的浮點數支持正無窮、負無窮以及非數字的表達方式。
--正無窮: SELECT 0.8/0 divide(0.8, 0) inf --負無窮: SELECT -0.8/0 divide(-0.8, 0) -inf --非數字: SELECT 0/0 divide(0, 0) nan
1.1.3、定點數(Decimal)
如果要求更高精度的數值運算,則需要使用定點數。ClickHouse提供了Decimal32、Decimal64和Decimal128三種精度的定點數。可以通過兩種形式聲明定點:簡寫方式有Decimal32(S)、Decimal64(S)、Decimal128(S)三種,原生方式為Decimal(P,S)。
其中:·P代表精度,決定總位數(整數部分+小數部分),取值范圍是1 ~38;·S代表規模,決定小數位數,取值范圍是0~P。
簡寫方式與原生方式的對應關系如表4-4所示。
對於不同精度定點數之間的四則運算,其精度S的變化會遵循表4-5所示的規則。
在使用定點數時還有一點值得注意:由於現代計算器系統只支持32位和64位CPU,所以Decimal128是在軟件層面模擬實現的,它的速度會明顯慢於Decimal32與Decimal64。
1.2、字符串
1.2.1、變長字符串(String)
字符串可以任意長度的,可以包含任意的字節集,包含空字節。
在使用String的時候無須聲明大小。
字符串它完全代替了傳統意義上數據庫的Varchar、Text、Clob和Blob等字符類型。
String類型不限定字符集,所以可以將任意編碼的字符串存入其中。但是為了程序的規范性和可維護性,在同一套程序中應該遵循使用統一的編碼,例如“統一保持UTF- 8編碼”就是種約定。
1.2.2、定長字符串 (FixedString)
FixedString類型和傳統意義上的Char類型有些類似,對於一些字符有明確長度的場合,可以使用固定長度的字符串。定長字符串通過FixedString(N)聲明,其中N表示字符串長度。但與Char不同的是,FixedString使用null字節填充末尾字符,而Char通常使用空格填充。例如,字符串‘abc’雖然只有3位,但長度卻是5,因為末尾有2位空字符填充:
SELECT toFixedString('abc',5) , LENGTH(toFixedString('abc',5)) AS LENGTH toFixedString('abc', 5) LENGTH abc 5
固定長度 N 的字符串,N 必須是嚴格的正自然數。
當服務端讀取長度小於 N 的字符串時候,通過在字符串末尾添加空字節來達到 N 字節長度。
當服務端讀取長度大於 N 的字符串時候,將返回錯誤消息。
與String相比,極少會使用FixedString,因為使用起來不是很方便。
1.2.3、UUID
UUID是一種數據庫常見的主鍵類型,在ClickHouse中直接把它作為一種數據類型。UUID共有32位,它的格式為8-4-4-4-12。如果一個UUID類型的字段在寫入數據時沒有被賦值,則會依照格式使用0填充,例如:
CREATE TABLE UUID_TEST ( c1 UUID, c2 String ) ENGINE = Memory; --第一行UUID有值 INSERT INTO UUID_TEST SELECT generateUUIDv4(),'t1' --第二行UUID沒有值 INSERT INTO UUID_TEST(c2) VALUES('t2') SELECT * FROM UUID_TEST c1 c2 f36c709e-1b73-4370-a703-f486bdd22749 t1 00000000-0000-0000-0000-000000000000 t2
由上,第二行沒有被賦值的UUID被0填充了。
1.3、時間類型
時間類型分為DateTime、DateTime64、Date三類。ClickHouse目前沒有時間戳類型。時間類型最高的精度是秒,也就是說,如果需要處理毫秒、微秒等大於秒分辨率的時間,則只能借助UInt類型實現。
1.3.1、DateTime
用四個字節(無符號的)存儲 Unix 時間戳,最小值為 0000-00-00 00:00:00,時間戳類型值精確到秒。DateTime類型包含時、分、秒信息,精確到秒,支持使用字符串形式寫入:
CREATE TABLE Datetime_TEST ( c1 Datetime ) ENGINE = Memory --以字符串形式寫入 INSERT INTO Datetime_TEST VALUES('2019-06-22 00:00:00') SELECT c1, toTypeName(c1) FROM Datetime_TEST c1 toTypeName(c1) 2019-06-22 00:00:00 DateTime
1.3.2、DateTime64
DateTime64可以記錄亞秒,它在DateTime之上增加了精度的設置,例如:
CREATE TABLE Datetime64_TEST ( c1 Datetime64(2) ) ENGINE = Memory --以字符串形式寫入 INSERT INTO Datetime64_TEST VALUES('2019-06-22 00:00:00') SELECT c1, toTypeName(c1) FROM Datetime64_TEST c1 toTypeName(c1) 2019-06-22 00:00:00.00 DateTime
1.3.3、Date
Date類型不包含具體的時間信息,只精確到天,它同樣也支持字符串形式寫入:
CREATE TABLE Date_TEST ( c1 Date ) ENGINE = Memory --以字符串形式寫入 INSERT INTO Date_TEST VALUES('2019-06-22') SELECT c1, toTypeName(c1) FROM Date_TEST c1 toTypeName(c1) 2019-06-22 Date
2、復合類型
除了基礎數據類型之外,ClickHouse還提供了數組、元組、枚舉和嵌套四類復合類型。這些類型通常是其他數據庫原生不具備的特性。擁有了復合類型之后,ClickHouse的數據模型表達能力更強了。
2.1、數組 (Array)
數組有兩種定義形式,常規方式array(T)、或者簡寫方式[T]。T 可以是任意類型,包含數組類型,但不推薦使用多維數組,ClickHouse 對多維數組的支持有限。可以使用array()函數和中括號來創建數組。
1>array(T) SELECT array(1, 2) as a , toTypeName(a) c1 toTypeName(array(1, 2)) [1,2] Array(UInt8) 2>[T] SELECT [1, 2]
在查詢時並不需要主動聲明數組的元素類型。因為ClickHouse的數組擁有類型推斷的能力,推斷依據:以最小存儲代價為原則,即使用最小可表達的數據類型。例如在上面的例子中,array(1,2)會通過自動推斷將UInt8作為數組類型。但是數組元素中如果存在Null值,則元素類型將變為Nullable,例如:
SELECT [1, 2, null] as a , toTypeName(a) a toTypeName([1, 2, NULL]) [1,2,NULL] Array(Nullable(UInt8))
在同一個數組內可以包含多種數據類型,例如數組[1,2.0]是可行的。但各類型之間必須兼容,例如數組[1,‘2’]則會報錯。
在定義表字段時,數組需要指定明確的元素類型,例如:
CREATE TABLE Array_TEST ( c1 Array(String) ) engine = Memory --應用舉例: --新建兩張帶Array類型的表: localhost :) CREATE TABLE array_t (arr Array(UInt8)) ENGINE = TinyLog; localhost :) CREATE TABLE array_ta (arr Array(String)) ENGINE = TinyLog; --插入數組 localhost :) INSERT INTO array_t VALUES([1,2,3,4,5]),(array(11,22,33,44,55)); localhost :) INSERT INTO array_ta VALUES(['a','b','c']),(array('x','y','z','123')); SELECT * FROM array_t ; arr [1,2,3,4,5] [11,22,33,44,55] localhost :) SELECT * FROM array_ta; arr [‘a’,‘b’,‘c’] [‘x’,‘y’,‘z’,‘123’]
2.2、元組 (Tuple)
元組類型由1~n個元素組成,每個元素之間允許設置不同的數據類型,且彼此之間不要求兼容。
元組同樣支持類型推斷,其推斷依據仍然以最小存儲代價為原則。
與數組類似,元組也可以使用兩種方式定義。
1>tuple(T) SELECT tuple(1,'a',now()) AS x, toTypeName(x) x toTypeName(tuple(1, ‘a’, now())) [‘a’,‘b’,‘c’] (1,‘a’,‘2019-08-28 21:36:32’) Tuple(UInt8, String, DateTime) 2>(T) SELECT (1,2.0,null) AS x, toTypeName(x) x toTypeName(tuple(1, 2., NULL)) [‘a’,‘b’,‘c’] (1,2,NULL) Tuple(UInt8, Float64, Nullable(Nothing))
在定義表字段時,元組也需要指定明確的元素類型:
CREATE TABLE Tuple_TEST ( c1 Tuple(String,Int8) ) ENGINE = Memory;
元素類型和泛型的作用類似,可以進一步保障數據質量。在數據寫入的過程中會進行類型檢查。例如,寫入INSERT INTO Tuple_TEST VALUES((‘abc’,123))是可行的,而寫入INSERT INTO Tuple_TEST VALUES((‘abc’,‘efg’))則會報錯。
2.3、枚舉類型 (Enum)
ClickHouse支持枚舉類型,這是一種在定義常量時經常會使用的數據類型。
提供以下兩種枚舉類型,除了取值范圍不同之外,別無二致。
枚舉固定使用(String:Int)Key/Value鍵值對的形式定義數據。
Enum8對應(String:Int8)
Enum16對應 (String:Int16)
例如:
CREATE TABLE Enum_TEST ( c1 Enum8('ready' = 1, 'start' = 2, 'success' = 3, 'error' = 4) ) ENGINE = Memory;
在定義枚舉集合的時候,需要注意:
Key和Value是不允許重復的,要保證唯一性。
Key和Value的值都不能為Null,但Key允許是空字符串。
在寫入枚舉數據的時候,只會用到Key字符串部分
例如:
INSERT INTO Enum_TEST VALUES('ready'); INSERT INTO Enum_TEST VALUES('start'); --數據在寫入的過程中,會對照枚舉集合項的內容逐一檢查。如果Key字符串不在集合范圍內則會拋出異常,比如執行下面的語句就會出錯: INSERT INTO Enum_TEST VALUES('stop');
Enum 保存 ‘string’= integer 的對應關系。在 ClickHouse 中,盡管用戶使用的是字符串常量,但所有含有 Enum 數據類型的操作都是按照包含整數的值來執行。這在性能方面比使用 String 數據類型更有效。
舉例:
#新建一張帶Enum8類型的表:
localhost :) CREATE TABLE enum_t ( et Enum8('a' = 1, 'b' = 2, 'c' =3)) ENGINE = TinyLog; localhost :) DESC enum_t ; name type et Enum8(‘a’ = 1, ‘b’ = 2, ‘c’ = 3) --插入數據 localhost :) INSERT INTO enum_t(et) VALUES ('a'),('a'),('b'); --查看 localhost :) SELECT * FROM enum_t ; et a b c --如果需要看到對應行的數值,則必須將 Enum 值轉換為整數類型: localhost :) SELECT CAST(et, 'Int8') FROM enum_t ; CAST(et, ‘Int8’)
出於性能的考慮。因為雖然枚舉定義中的Key屬於String類型,但是在后續對枚舉的所有操作中(包括排序、分組、去重、過濾等),會使用Int類型d的Value值。
2.4、嵌套結構 (Nested)
嵌套類型,顧名思義是一種嵌套表結構。對於簡單場景的層級關系或關聯關系,使用嵌套類型也是一種不錯的選擇。
一張數據表,可以定義任意多個嵌套類型字段,但每個字段的嵌套層級只支持一級,即嵌套表內不能繼續使用嵌套類型。
例如,下面的nested_test是一張模擬的員工表,它的所屬部門字段就使用了嵌套類型:
CREATE TABLE nested_test ( name String, age UInt8 , dept Nested( id UInt8, name String ) ) ENGINE = Memory; --以上面這張表為例,如果按照它的字面意思來理解,會很容易理解成nested_test與dept是一對一的包含關系,其實這是錯誤的。不信可以執行下面的插入語句,看看會是什么結果: INSERT INTO nested_test VALUES ('nauu',18, 10000, '研發部'); --結果 Exception on client: Code: 53. DB::Exception: Type mismatch in IN or VALUES section. Expected: Array(UInt8). Got: UInt64
嵌套類型本質是一種多維數組的結構。
嵌套表中的每個字段都是一個數組,並且行與行之間數組的長度無須對齊。
所以需要把剛才的INSERT語句調整成下面的形式:
INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001,10002], ['研發部','技術支持中心','測試部']); --行與行之間,--數組長度無須對齊 INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研發部','技術支持中心']); --在同一行數據內每個數組字段的長度必須相等。 --例如,在下面的示例中,由於行內數組字段的長度沒有對齊,所以會拋出異常: INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研發部','技術支持中心', '測試部']); -- 結果 DB::Exception: Elements 'dept.id' and 'dept.name' of Nested data structure 'dept' (Array columns) have different array sizes..
在訪問嵌套類型的數據時需要使用點符號
例如:
SELECT name, dept.id, dept.name FROM nested_test name dept.id dept.name bruce [16,17,18] [‘研發部’,‘技術支持中心’,‘測試部’]
3、特殊類型
ClickHouse還有一類不同尋常的數據類型,我將它們定義為特殊類型。
3.1、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)
在使用Nullable類型的時候需要注意:
它只能和基礎類型搭配使用,不能用於數組和元組這些復合類型,也不能作為索引字段;
應該慎用Nullable類型,包括Nullable的數據表,不然會使查詢和寫入性能變慢。(因為在正常情況下,每個列字段的數據會被存儲在對應的[Column].bin文件中。如果一個列字段被Nullable類型修飾后,會額外生成一個[Column].null.bin文件專門保存它的Null值。這意味着在讀取和寫入數據時,需要一倍的額外文件操作。)
3.2、Domain
域名類型分為IPv4和IPv6兩類,本質上它們是對整型和字符串的進一步封裝。IPv4類型是基於UInt32封裝的,它的具體用法如下所示:
CREATE TABLE IP4_TEST ( url String, ip IPv4 ) ENGINE = Memory; INSERT INTO IP4_TEST VALUES ('www.nauu.com','192.0.0.0') SELECT url , ip ,toTypeName(ip) FROM IP4_TEST url ip toTypeName(c2) nauu NULL toTypeName(ip) www.nauu.com 192.0.0.0 IPv4
使用Domain而不直接使用字符串的原因如下:
1)出於便捷性的考量,例如IPv4類型支持格式檢查,格式錯誤的IP數據是無法被寫入的,例如:
INSERT INTO IP4_TEST VALUES ('www.nauu.com','192.0.0') Code: 441. --結果 DB::Exception: Invalid IPv4 value.
2)出於性能的考量,同樣以IPv4為例,IPv4使用UInt32存儲,相比String更加緊湊,占用的空間更小,查詢性能更快。IPv6類型是基於FixedString(16)封裝的,它的使用方法與IPv4別無二致。
在使用Domain類型的時候需要注意:
雖然它從表象上看起來與String一樣,但Domain類型並不是字符串,所以它不支持隱式的自動類型轉換。
如果需要返回IP的字符串形式,則需要顯式調用IPv4NumToString或IPv6NumToString函數進行轉換。
小結
基礎類型
基礎類型只有數值、字符串和時間三種類型,沒有Boolean類型,可使用整型的0或1替代。
數值類型
數值類型分為整數、浮點數和定點數三類
字符串
可以細分為String、FixedString和UUID三類。
String長度不限,不限定字符集,但建議遵循使用統一的編碼;
FixedString,FixedString(N),使用null字節填充末尾字符;
UUID,32位,它的格式為8-4-4-4-12,空值默認用0填充,即0…0-…00
時間
時間類型分為DateTime、DateTime64和Date三類。
CK目前沒有時間戳類型。時間類型最高的精度是秒,即若需要處理毫秒、微秒等大於秒分辨率的時間,則只能借助UInt類型實現。
DateTimeDateTime類型包含時、分、秒信息,精確到秒,支持使用字符串形式寫入。
DateTime64DateTime64可以記錄亞秒,在DateTime之上增加精度設置。
DateDate類型不包含具體的時間信息,只精確到天,支持字符串形式寫入。
復合類型
數組、元組、枚舉和嵌套
Array
有兩種定義形式,以最小存儲代價為原則,即使用最小可表達的數據類型
Tuple
元組類型由1~n個元素組成,每個元素之間允許設置不同的數據類型,且彼此之間不要求兼容。元組同樣支持類型推斷,其推斷依據仍然以最小存儲代價為原則
Enum
包括Enum8和Enum16兩種枚舉類型,Key和Value是不允許重復的,要保證唯一性。其次,Key和Value的值都不能為Null,但Key允許是空字符串。
Nested
一種嵌套表結構。一張數據表,可以定義任意多個嵌套類型字段,但每個字段的嵌套層級只支持一級,即嵌套表內不能繼續使用嵌套類型。嵌套類型本質是一種多維數組的結構。嵌套表中的每個字段都是一個數組,並且行與行之間數組的長度無須對齊。在訪問嵌套類型的數據時需要使用點符號。
特殊類型
Nullable
並不能算是一種獨立的數據類型。只能和基礎類型搭配使用,也不能作為索引字段。應該慎用Nullable類型,包括Nullable的數據表,不然會使查詢和寫入性能變慢。因為在正常情況下,每個列字段的數據會被存儲在對應的[Column].bin文件中。如果一個列字段被Nullable類型修飾后,會額外生成一個[Column].null.bin文件專門保存它的Null值。即在讀取和寫入數據時,需要一倍的額外文件操作。
Domain
域名類型,分為IPv4和IPv6兩類,本質上是對整型和字符串的進一步封裝。IPv4類型基於UInt32封裝的,IPv6類型是基於FixedString(16)封裝。如果需要返回IP的字符串形式,則需要顯式調用IPv4NumToString或IPv6NumToString函數進行轉換。