1. 基礎(chǔ)數(shù)據(jù)類(lèi)型
1.1 數(shù)值類(lèi)型
1.1.1 Int
ClickHouse | 關(guān)系型數(shù)據(jù)庫(kù) | 字節(jié)數(shù) |
---|---|---|
Int8 | TinyInt | 1 |
Int16 | SmallInt | 2 |
Int32 | Int | 4 |
Int64 | BigInt | 8 |
1.1.2 Float
ClickHouse | 精度 | 字節(jié)數(shù) |
---|---|---|
Float32 | 7位 | 4 |
Float64 | 16位 | 8 |
1.1.3 Decimal
ClickHouse | 原生寫(xiě)法 | 簡(jiǎn)寫(xiě) |
---|---|---|
Decimal32 | Decimal32(P,S) | Decimal32(S)呻逆,等效于Decimal(1~9,S) |
Decimal64 | Decimal64(P,S) | Decimal64(S)夸赫,等效于Decimal(10~18,S) |
Decimal128 | Decimal128(P,S) | Decimal128(S),等效于Decimal(19~38,S) |
說(shuō)明:
P代表精度咖城,決定總位數(shù)(整數(shù)部分+小數(shù)部分)茬腿,取值范圍是[1,38];S代表規(guī)模宜雀,決定小數(shù)位數(shù)切平,取值范圍是[0,P]
-
兩個(gè)不同精度的定點(diǎn)數(shù)進(jìn)行加減法的時(shí)候,它們的小數(shù)點(diǎn)位數(shù)S會(huì)發(fā)生變化辐董,取S的最大值
toDecimal64(2,4)與toDecimal32(2,2)相加后S=4
:) SELECT toDecimal64(2,4) + toDecimal32(2,2); ┌─plus(toDecimal64(2, 4), toDecimal32(2, 2))─┐ │ 4.0000 │ └────────────────────────────────────────────┘
-
兩個(gè)不同精度的定點(diǎn)數(shù)進(jìn)行乘法的時(shí)候悴品,S是兩者之和
:) SELECT toDecimal64(2,4) * toDecimal32(2,2) ┌─multiply(toDecimal64(2, 4), toDecimal32(2, 2))─┐ │ 4.000000 │ └────────────────────────────────────────────────┘
兩個(gè)不同精度的定點(diǎn)數(shù)進(jìn)行除法的時(shí)候,S是被除數(shù)(分子)的S简烘,但要求分子的精度大于等于分母的精度
由于現(xiàn)代計(jì)算器系統(tǒng)只支持32位和64位CPU苔严,所以Decimal128是在軟件層面模擬實(shí)現(xiàn)的,它的速度會(huì)明顯慢于Decimal32與Decimal64
1.1.4 數(shù)值類(lèi)型的特殊值
inf:正無(wú)窮
-inf:負(fù)無(wú)窮
nan:非數(shù)字
1.2 字符串類(lèi)型
1.2.1 String
字符串由String定義孤澎,長(zhǎng)度不限届氢。因此在使用String的時(shí)候無(wú)須聲明大小。它完全代替了傳統(tǒng)意義上數(shù)據(jù)庫(kù)的Varchar覆旭、Text悼沈、Clob和Blob等字符類(lèi)型贱迟。String類(lèi)型不限定字符集,因?yàn)樗揪蜎](méi)有這個(gè)概念絮供,所以可以將任意編碼的字符串存入其中衣吠。但是為了程序的規(guī)范性和可維護(hù)性,在同一套程序中應(yīng)該遵循使用統(tǒng)一的編碼壤靶,例如UTF-8缚俏。
1.2.2 FixedString
FixedString類(lèi)型和傳統(tǒng)意義上的Char類(lèi)型有些類(lèi)似,對(duì)于一些字符有明確長(zhǎng)度的場(chǎng)合贮乳,可以使用固定長(zhǎng)度的字符串忧换。定長(zhǎng)字符串通過(guò)FixedString(N)聲明,其中N表示字符串長(zhǎng)度向拆。但與Char不同的是亚茬,F(xiàn)ixedString使用null字節(jié)填充末尾字符,而Char通常使用空格填充浓恳。比如在下面的例子中刹缝,字符串"abc"雖然只有3位,但長(zhǎng)度卻是5颈将,因?yàn)槟┪灿?位空字符填充梢夯。
:) SELECT toFixedString('abc',5), LENGTH(toFixedString('abc',5)) AS LENGTH;
┌─toFixedString('abc', 5)─┬─LENGTH─┐
│ abc │ 5 │
└─────────────────────────┴────────┘
1.2.3 UUID
UUID是一種數(shù)據(jù)庫(kù)常見(jiàn)的主鍵類(lèi)型,在ClickHouse中直接把它作為一種數(shù)據(jù)類(lèi)型晴圾。UUID共有32位颂砸,它的格式為8-4-4-4-12。如果一個(gè)UUID類(lèi)型的字段在寫(xiě)入數(shù)據(jù)時(shí)沒(méi)有被賦值死姚,則會(huì)依照格式使用0填充人乓,例如:
CREATE TABLE UUID_TEST (c1 UUID,c2 String) ENGINE = Memory;
INSERT INTO UUID_TEST SELECT generateUUIDv4(),'t1';
INSERT INTO UUID_TEST(c2) VALUES('t2');
SELECT * FROM UUID_TEST;
┌───────────────────────────────────c1─┬─c2─┐
│ 308297a4-c66e-4ecd-ac34-f948b0f9752c │ t1 │
└──────────────────────────────────────┴────┘
┌───────────────────────────────────c1─┬─c2─┐
│ 00000000-0000-0000-0000-000000000000 │ t2 │
└──────────────────────────────────────┴────┘
1.3 時(shí)間類(lèi)型
1.3.1 DateTime
DateTime類(lèi)型包含時(shí)、分都毒、秒信息撒蟀,精確到秒,支持使用字符串形式寫(xiě)入:
CREATE TABLE Datetime_TEST (c1 Datetime) ENGINE = Memory;
--以字符串形式寫(xiě)入
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之上增加了精度的設(shè)置保屯,例如:
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;
有問(wèn)題:無(wú)論數(shù)據(jù)類(lèi)型是Datetime64還是DateTime64,建表都會(huì)報(bào)錯(cuò):
DB::Exception: Unknown data type family: DateTime64. Maybe you meant: ['DateTime']
1.3.3 Date
Date類(lèi)型不包含具體的時(shí)間信息涤垫,只精確到天姑尺,它同樣也支持字符串形式寫(xiě)入:
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. 復(fù)合類(lèi)型
2.1 Array
# 常規(guī)方式
SELECT array(1, 2) as a , toTypeName(a);
┌─a─────┬─toTypeName([1, 2])─┐
│ [1,2] │ Array(UInt8) │
└───────┴────────────────────┘
# 簡(jiǎn)寫(xiě)方式
SELECT [1, 2];
┌─[1, 2]─┐
│ [1,2] │
└────────┘
通過(guò)上述的例子可以發(fā)現(xiàn),在查詢(xún)時(shí)并不需要主動(dòng)聲明數(shù)組的元素類(lèi)型蝠猬。因?yàn)镃lickHouse的數(shù)組擁有類(lèi)型推斷的能力切蟋,推斷依據(jù):以最小存儲(chǔ)代價(jià)為原則,即使用最小可表達(dá)的數(shù)據(jù)類(lèi)型榆芦。例如在上面的例子中柄粹,array(1,2)會(huì)通過(guò)自動(dòng)推斷將UInt8作為數(shù)組類(lèi)型喘鸟。但是數(shù)組元素中如果存在Null值,則元素類(lèi)型將變?yōu)镹ullable驻右,例如:
SELECT [1, 2, null] as a , toTypeName(a);
┌─a──────────┬─toTypeName([1, 2, NULL])─┐
│ [1,2,NULL] │ Array(Nullable(UInt8)) │
└────────────┴──────────────────────────┘
在同一個(gè)數(shù)組內(nèi)可以包含多種數(shù)據(jù)類(lèi)型什黑,例如數(shù)組[1,2.0]是可行的。但各類(lèi)型之間必須兼容堪夭,例如數(shù)組[1,'2']則會(huì)報(bào)錯(cuò)愕把。
在定義表字段時(shí),數(shù)組需要指定明確的元素類(lèi)型森爽,例如:
CREATE TABLE Array_TEST (c1 Array(String)) engine = Memory;
2.2 Tuple
組類(lèi)型由1~n個(gè)元素組成恨豁,每個(gè)元素之間允許設(shè)置不同的數(shù)據(jù)類(lèi)型,且彼此之間不要求兼容爬迟。元組同樣支持類(lèi)型推斷橘蜜,其推斷依據(jù)仍然以最小存儲(chǔ)代價(jià)為原則。
# 常規(guī)方式
SELECT tuple(1,'a',now()) AS x, toTypeName(x);
┌─x─────────────────────────────┬─toTypeName(tuple(1, 'a', now()))─┐
│ (1,'a','2021-08-16 16:08:30') │ Tuple(UInt8, String, DateTime) │
└───────────────────────────────┴──────────────────────────────────┘
# 簡(jiǎn)寫(xiě)方式
SELECT (1,2.0,null) AS x, toTypeName(x);
┌─x──────────┬─toTypeName(tuple(1, 2., NULL))───────────┐
│ (1,2,NULL) │ Tuple(UInt8, Float64, Nullable(Nothing)) │
└────────────┴──────────────────────────────────────────┘
在定義表字段時(shí)付呕,元組也需要指定明確的元素類(lèi)型计福,元素類(lèi)型和泛型的作用類(lèi)似,可以進(jìn)一步保障數(shù)據(jù)質(zhì)量凡涩。在數(shù)據(jù)寫(xiě)入的過(guò)程中會(huì)進(jìn)行類(lèi)型檢查。
CREATE TABLE Tuple_TEST (c1 Tuple(String,Int8)) ENGINE = Memory;
2.3 Enum
ClickHouse支持枚舉類(lèi)型疹蛉,這是一種在定義常量時(shí)經(jīng)常會(huì)使用的數(shù)據(jù)類(lèi)型活箕。ClickHouse提供了Enum8和Enum16兩種枚舉類(lèi)型,它們除了取值范圍不同之外可款,別無(wú)二致育韩。枚舉固定使用(String:Int)Key/Value鍵值對(duì)的形式定義數(shù)據(jù),所以Enum8和Enum16分別會(huì)對(duì)應(yīng)(String:Int8)和(String:Int16)闺鲸,例如:
CREATE TABLE Enum_TEST (c1 Enum8('ready'=1,'start'=2,'success'=3,'error'=4)) ENGINE = Memory;
在定義枚舉集合的時(shí)候筋讨,有幾點(diǎn)需要注意。首先摸恍,Key和Value是不允許重復(fù)的悉罕,要保證唯一性。其次立镶,Key和Value的值都不能為Null壁袄,但Key允許是空字符串。在寫(xiě)入枚舉數(shù)據(jù)的時(shí)候媚媒,只會(huì)用到Key字符串部分嗜逻,例如:
INSERT INTO Enum_TEST VALUES('ready');
INSERT INTO Enum_TEST VALUES('start');
select * from Enum_TEST;
┌─c1────┐
│ ready │
└───────┘
┌─c1────┐
│ start │
└───────┘
數(shù)據(jù)在寫(xiě)入的過(guò)程中,會(huì)對(duì)照枚舉集合項(xiàng)的內(nèi)容逐一檢查缭召。如果Key字符串不在集合范圍內(nèi)則會(huì)拋出異常栈顷。
使用枚舉類(lèi)型可以提高計(jì)算性能逆日,雖然枚舉定義中的Key屬于String類(lèi)型,但是在后續(xù)對(duì)枚舉的所有操作中(包括排序萄凤、分組室抽、去重、過(guò)濾等)蛙卤,會(huì)使用Int類(lèi)型的Value值狠半。
2.4 Nested
嵌套類(lèi)型,顧名思義是一種嵌套表結(jié)構(gòu)颤难。一張數(shù)據(jù)表神年,可以定義任意多個(gè)嵌套類(lèi)型字段,但每個(gè)字段的嵌套層級(jí)只支持一級(jí)行嗤,即嵌套表內(nèi)不能繼續(xù)使用嵌套類(lèi)型已日。對(duì)于簡(jiǎn)單場(chǎng)景的層級(jí)關(guān)系或關(guān)聯(lián)關(guān)系,使用嵌套類(lèi)型也是一種不錯(cuò)的選擇栅屏。例如飘千,下面的nested_test是一張模擬的員工表,它的所屬部門(mén)字段就使用了嵌套類(lèi)型:
CREATE TABLE nested_test (
name String,
age UInt8 ,
dept Nested(
id UInt8,
name String
)
) ENGINE = Memory;
以上面這張表為例栈雳,如果按照它的字面意思來(lái)理解护奈,會(huì)很容易理解成nested_test與dept是一對(duì)一的包含關(guān)系,其實(shí)這是錯(cuò)誤的:
INSERT INTO nested_test VALUES ('nauu',18,10000,'研發(fā)部');
# 報(bào)錯(cuò):DB::Exception: Type mismatch in IN or VALUES section. Expected: Array(UInt8). Got: UInt64
注意上面的異常信息哥纫,它提示期望寫(xiě)入的是一個(gè)Array數(shù)組類(lèi)型霉旗。
嵌套類(lèi)型本質(zhì)是一種多維數(shù)組的結(jié)構(gòu)。嵌套表中的每個(gè)字段都是一個(gè)數(shù)組蛀骇,并且行與行之間數(shù)組的長(zhǎng)度無(wú)須對(duì)齊厌秒。所以需要把剛才的INSERT語(yǔ)句調(diào)整成下面的形式:
INSERT INTO nested_test VALUES ('bruce',30,[10000,10001,10002],['研發(fā)部','技術(shù)支持中心','測(cè)試部']);
-- 行與行之間,數(shù)組長(zhǎng)度無(wú)須對(duì)齊
INSERT INTO nested_test VALUES ('bruce',30,[10000,10001],['研發(fā)部','技術(shù)支持中心']);
行內(nèi)數(shù)組字段的長(zhǎng)度沒(méi)有對(duì)齊,會(huì)拋出異常:
INSERT INTO nested_test VALUES ('bruce',30,[10000,10001],['研發(fā)部','技術(shù)支持中心','測(cè)試部']);
# DB::Exception: Elements 'dept.id' and 'dept.name' of Nested data structure 'dept' (Array columns) have different array sizes..
在訪問(wèn)嵌套類(lèi)型的數(shù)據(jù)時(shí)需要使用點(diǎn)符號(hào)擅憔,例如:
SELECT name, dept.id, dept.name FROM nested_test;
┌─name──┬─dept.id────┬─dept.name──────────────────────────┐
│ bruce │ [16,17,18] │ ['研發(fā)部','技術(shù)支持中心','測(cè)試部'] │
└───────┴────────────┴────────────────────────────────────┘
┌─name──┬─dept.id─┬─dept.name─────────────────┐
│ bruce │ [16,17] │ ['研發(fā)部','技術(shù)支持中心'] │
└───────┴─────────┴───────────────────────────┘
3. 特殊類(lèi)型
3.1 Nullable
準(zhǔn)確來(lái)說(shuō)鸵闪,Nullable并不能算是一種獨(dú)立的數(shù)據(jù)類(lèi)型,它更像是一種輔助的修飾符暑诸,需要與基礎(chǔ)數(shù)據(jù)類(lèi)型一起搭配使用蚌讼。Nullable類(lèi)型與Java8的Optional對(duì)象有些相似,它表示某個(gè)基礎(chǔ)數(shù)據(jù)類(lèi)型可以是Null值个榕。其具體用法如下所示:
CREATE TABLE Null_TEST (
c1 String,
c2 Nullable(UInt8)
) ENGINE = TinyLog;
# 通過(guò)Nullable修飾后c2字段可以被寫(xiě)入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 │ ???? │ Nullable(UInt8) │
│ bruce │ 20 │ Nullable(UInt8) │
└───────┴──────┴─────────────────┘
在使用Nullable類(lèi)型的時(shí)候還有兩點(diǎn)值得注意:
首先啦逆,它只能和基礎(chǔ)類(lèi)型搭配使用,不能用于數(shù)組和元組這些復(fù)合類(lèi)型笛洛,也不能作為索引字段夏志;
其次,應(yīng)該慎用Nullable類(lèi)型,包括Nullable的數(shù)據(jù)表沟蔑,不然會(huì)使查詢(xún)和寫(xiě)入性能變慢湿诊。因?yàn)樵谡G闆r下,每個(gè)列字段的數(shù)據(jù)會(huì)被存儲(chǔ)在對(duì)應(yīng)的[Column].bin文件中瘦材。如果一個(gè)列字段被Nullable類(lèi)型修飾后厅须,會(huì)額外生成一個(gè)[Column].null.bin文件專(zhuān)門(mén)保存它的Null值。這意味著在讀取和寫(xiě)入數(shù)據(jù)時(shí)食棕,需要一倍的額外文件操作朗和。
3.2 Domain
域名類(lèi)型分為IPv4和IPv6兩類(lèi),本質(zhì)上它們是對(duì)整型和字符串的進(jìn)一步封裝簿晓。IPv4類(lèi)型是基于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;
IPv4類(lèi)型支持格式檢查,格式錯(cuò)誤的IP數(shù)據(jù)是無(wú)法被寫(xiě)入的:
INSERT INTO IP4_TEST VALUES ('www.nauu.com','192.0.0');
# DB::Exception: Invalid IPv4 value.
IPv4使用UInt32存儲(chǔ)憔儿,相比String更加緊湊忆植,占用的空間更小,查詢(xún)性能更快谒臼。
IPv6類(lèi)型是基于FixedString(16)封裝的朝刊,它的使用方法與IPv4別無(wú)二致。
雖然Domain類(lèi)型從表象上看起來(lái)與String一樣蜈缤,但Domain類(lèi)型并不是字符串拾氓,所以它不支持隱式的自動(dòng)類(lèi)型轉(zhuǎn)換。如果需要返回IP的字符串形式底哥,則需要顯式調(diào)用IPv4NumToString或IPv6NumToString函數(shù)進(jìn)行轉(zhuǎn)換咙鞍。