楔子
之前在介紹數據類型的時候,有一種沒有說,就是 Map。Map 是什么想必無需多言,簡單來說的話就是維護鍵值對之間的映射關系,可以通過鍵迅速定位到值。
下面就先來創建一張表:
-- 在定義 Map 的時候,必須要指定鍵值對的類型
CREATE TABLE table_map(a Map(String, UInt64)) ENGINE = Memory();
但是不出意外我們創建表的時候應該會報錯,原因就是在表中支持定義 Map 類型的字段還只是試驗性的,我們需要將 allow_experimental_map_type 設置為 1,這也是我們單獨拿出來介紹的原因。然后我們插入數據:
set allow_experimental_map_type = 1;
CREATE TABLE table_map(a Map(String, UInt64)) ENGINE = Memory();
INSERT INTO table_map
VALUES ({'key1': 1, 'key2': 10}), ({'key1':2,'key2':20}), ({'key1':3,'key2':30});
下面來對表進行查詢:
SELECT * FROM table_map;
/*
┌─a────────────────────┐
│ {'key1':1,'key2':10} │
│ {'key1':2,'key2':20} │
│ {'key1':3,'key2':30} │
└──────────────────────┘
*/
如果想選擇某個具體的鍵對應的 value,那么直接通過方括號即可,舉個栗子:
SELECT a['key1'], a['key2'], a FROM table_map;
/*
┌─arrayElement(a, 'key1')─┬─arrayElement(a, 'key2')─┬─a────────────────────┐
│ 1 │ 10 │ {'key1':1,'key2':10} │
│ 2 │ 20 │ {'key1':2,'key2':20} │
│ 3 │ 30 │ {'key1':3,'key2':30} │
└─────────────────────────┴─────────────────────────┴──────────────────────┘
*/
如果查詢一個不在 Map 當中 key,那么會返回對應的零值。
SELECT a['key3'] FROM table_map;
/*
┌─arrayElement(a, 'key3')─┐
│ 0 │
│ 0 │
│ 0 │
└─────────────────────────┘
*/
當然我們也可以根據現有的數組結構創建 Map:
WITH [1, 2, 3] AS key, ['a', 'b', 'c'] AS value
SELECT cast((key, value) AS Map(UInt8, String));
/*
┌─CAST(tuple(key, value), 'Map(UInt8, String)')─┐
│ {1:'a',2:'b',3:'c'} │
└───────────────────────────────────────────────┘
*/
-- 從返回的結果集的字段名,我們可以看出,cast(val AS type) 等價於 cast(val, 'type')
-- 比如 cast(3 AS String) 和 cast(3, 'String') 是等價的,不過個人還是習慣前者
我們在選擇的時候也可以只選擇 key 或者 value。
SELECT a.keys, a.values FROM table_map;
/*
┌─a.keys──────────┬─a.values─┐
│ ['key1','key2'] │ [1,10] │
│ ['key1','key2'] │ [2,20] │
│ ['key1','key2'] │ [3,30] │
└─────────────────┴──────────┘
*/
然后我們來看看字典都支持哪些函數操作
map:我們除了可以通過大括號創建 Map,也可以通過 map 函數創建
SELECT map('key1', number, 'key2', number * 2) FROM numbers(3);
/*
┌─map('key1', number, 'key2', multiply(number, 2))─┐
│ {'key1':0,'key2':0} │
│ {'key1':1,'key2':2} │
│ {'key1':2,'key2':4} │
└──────────────────────────────────────────────────┘
*/
-- 注意:SELECT {'key1': number, 'key2': number * 2} 是非法的,必須使用 map 函數創建
同理我們插入數據的時候也可以使用 map 函數:
INSERT INTO table_map VALUES (map('key1', 1, 'key2', 10));
SELECT a.keys, a.values FROM table_map;
/*
┌─a.keys──────────┬─a.values─┐
│ ['key1','key2'] │ [1,10] │
│ ['key1','key2'] │ [2,20] │
│ ['key1','key2'] │ [3,30] │
└─────────────────┴──────────┘
┌─a.keys──────────┬─a.values─┐
│ ['key1','key2'] │ [1,10] │
└─────────────────┴──────────┘
*/
mapContains:檢測 Map 里面是否包含某個 key
WITH map(1, 3, 2, 5) AS m
SELECT mapContains(m, 1), mapContains(m, 3);
/*
┌─mapContains(m, 1)─┬─mapContains(m, 3)─┐
│ 1 │ 0 │
└───────────────────┴───────────────────┘
*/
mapKeys:等價於 Map.keys
SELECT a.keys, mapKeys(a) FROM table_map;
/*
┌─a.keys──────────┬─mapKeys(a)──────┐
│ ['key1','key2'] │ ['key1','key2'] │
│ ['key1','key2'] │ ['key1','key2'] │
│ ['key1','key2'] │ ['key1','key2'] │
└─────────────────┴─────────────────┘
┌─a.keys──────────┬─mapKeys(a)──────┐
│ ['key1','key2'] │ ['key1','key2'] │
└─────────────────┴─────────────────┘
*/
mapValues:等價於 Map.values
SELECT a.values, mapValues(a) FROM table_map;
/*
┌─a.values─┬─mapValues(a)─┐
│ [1,10] │ [1,10] │
│ [2,20] │ [2,20] │
│ [3,30] │ [3,30] │
└──────────┴──────────────┘
┌─a.values─┬─mapValues(a)─┐
│ [1,10] │ [1,10] │
└──────────┴──────────────┘
*/
注意:mapKeys、mapValues 相當於數據全量讀取,然后再選擇所有的 key 或 value,所以建議還是使用 Map.keys、Map.values。但如果將 optimize_functions_to_subcolumns 設置為 1,那么會進行優化:
SELECT mapKeys(m), mapValues(m) FROM table 會轉化成 SELECT m.keys, m.values FROM table
以上就是 Map 的內容,總的來說還是很簡單的。
JSON 的相關操作
既然提到了 Map,那么就不能不提到 JSON,這兩者在結構上有着非常高的相似之處,下面就來看看 JSON 支持哪些操作。
isValidJSON:檢測 JSON 是否合法
JSON 本質上也是一個字符串,isValidJSON 則是檢測該字符串是否符合 JSON 格式。
SELECT isValidJSON('{"a": 1, "b": false}'), isValidJSON('{1, 2, 3}');
/*
┌─isValidJSON('{"a": 1, "b": false}')─┬─isValidJSON('{1, 2, 3}')─┐
│ 1 │ 0 │
└─────────────────────────────────────┴──────────────────────────┘
*/
JSONHas:檢測 JSON 是否包含指定的 key
SELECT JSONHas('{"a": 1, "b": false}', 'a'), JSONHas('{"a": 1, "b": false}', 'a1');
/*
┌─JSONHas('{"a": 1, "b": false}', 'a')─┬─JSONHas('{"a": 1, "b": false}', 'a1')─┐
│ 1 │ 0 │
└──────────────────────────────────────┴───────────────────────────────────────┘
*/
JSONLength:獲取 JSON 的長度
SELECT JSONLength('{"a": 1, "b": false}');
/*
┌─JSONLength('{"a": 1, "b": false}')─┐
│ 2 │
└────────────────────────────────────┘
*/
JSONType:獲取 JSON 中指定 value 的類型
WITH '{"a": 1, "b": true, "c": null, "d": "xx", "e": [1, 2, 3], "f": {"a": 1}}' AS j
SELECT JSONType(j, 'a'), JSONType(j, 'b'), JSONType(j, 'c'),
JSONType(j, 'd'), JSONType(j, 'e'), JSONType(j, 'f');
/*
┌─JSONType(j, 'a')─┬─JSONType(j, 'b')─┬─JSONType(j, 'c')─┬─JSONType(j, 'd')─┬─JSONType(j, 'e')─┬─JSONType(j, 'f')─┐
│ Int64 │ Bool │ Null │ String │ Array │ Object │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┘
*/
toJSONString:將其它數據類型轉成 JSON
-- 不可以寫成 {'a': 1, 'b': 2}
SELECT toJSONString(map('a', 1, 'b', 2));
/*
┌─toJSONString(map('a', 1, 'b', 2))─┐
│ {"a":1,"b":2} │
└───────────────────────────────────┘
*/
JSONExtract:根據 key,從 JSON 中解析出指定的 value,就類似於根據 key 獲取 Map 中的 value 一樣
-- 在獲取 value 的時候,必須要指定 value 是什么類型
-- ClickHouse 中的 Bool 是用整型表示的,所以轉成 UInt8、16、32、64 也是可以的
WITH '{"a": 1, "b": true}' AS j
SELECT JSONExtract(j, 'a', 'UInt8'), JSONExtract(j, 'b', 'Bool');
/*
┌─JSONExtract(j, 'a', 'UInt8')─┬─JSONExtract(j, 'b', 'Bool')─┐
│ 1 │ 1 │
└──────────────────────────────┴─────────────────────────────┘
*/
WITH '{"a": [null, 123], "b": {"a": 1}}' AS j
SELECT JSONExtract(j, 'a', 'Array(UInt8)'),
JSONExtract(j, 'a', 'Array(Nullable(UInt8))');
/*
┌─JSONExtract(j, 'a', 'Array(UInt8)')─┬─JSONExtract(j, 'a', 'Array(Nullable(UInt8))')─┐
│ [0,123] │ [NULL,123] │
└─────────────────────────────────────┴───────────────────────────────────────────────┘
*/
如果解析失敗,那么會得到相應的零值,舉個栗子:
WITH '{"a": [null, 123], "b": {"a": 1}}' AS j
SELECT JSONExtract(j, 'a', 'UInt64');
/*
┌─JSONExtract(j, 'a', 'UInt64')─┐
│ 0 │
└───────────────────────────────┘
*/