ClickHouse的數據類型介紹


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函數進行轉換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM