ClickHouse 的 Map 類型以及相關操作


楔子

之前在介紹數據類型的時候,有一種沒有說,就是 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 │
└───────────────────────────────┘
*/


免責聲明!

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



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