ClickHouse存储解析JSON数据

很多场景中由于业务变化数据结构不能确定,数据对象属性也可能随时间而变化。这时使用json保持动态数据是较好的选择,ClickHouse提供相应的工具支持存储和解析JSON数据。
ClickHouse除了以string存储json,22.3版本开始还提供json对象类型实验特性。

存储JSON数据

最简单方式使用字符串列存储JSON对象,创建示例表:

CREATE TABLE test_string ( `t` DateTime, `v` String )
ENGINE = MergeTree ORDER BY t

插入示例数据:

INSERT INTO test_string VALUES(now(), '{"name":"Joe","age":95}')
INSERT INTO test_string VALUES(now(), '{"name":"Jack","rating":9}')
INSERT INTO test_string VALUES(now(), '{"name":"Tom","rating":8}')

ClickHouse 提供了几个函数处理、验证JSON数据,下面通过分别进行验证。

验证检查key

可以使用 isVAlidJSON函数进行检查:

SELECT v, isValidJSON(v) FROM test_string

返回1、0 标识是否有效:

┌─v──────────────────────────┬─isValidJSON(v)─┐
│ {"name":"Joe","age":95}    │              1 │
│ {"name":"Jack","rating":9} │              1 │
│ {"name":"Tom","rating":8}  │              1 │
└────────────────────────────┴────────────────┘

也可以检查json对象是否包括特定key,这在数据清洗时经常使用:

SELECT
    JSONHas(v, 'name') AND JSONHas(v, 'rating') AS is_valid,
    count(*)
FROM test_string GROUP BY is_valid

上面SQL检查json是否包含name和rating属性,返回结果显示有两条记录符合条件:

┌─is_valid─┬─count()─┐
│        0 │       1 │
│        1 │       2 │
└──────────┴─────────┘

抽取值

通常我们需要操作json对象的属性值,有多种方式可以实现,首先我们看基于key的抽取函数:

SELECT
    v,
    JSONExtract(v, 'name', 'String'),
    JSONExtract(v, 'rating', 'UInt32')
FROM test_string
LIMIT 5

// 返回结果

┌─v──────────────────────────┬─JSONExtract(v, 'name', 'String')─┬─JSONExtract(v, 'rating', 'UInt32')─┐
│ {"name":"Joe","age":95}    │ Joe                              │                                  0 │
│ {"name":"Jack","rating":9} │ Jack                             │                                  9 │
│ {"name":"Tom","rating":8}  │ Tom                              │                                  8 │
└────────────────────────────┴──────────────────────────────────┴────────────────────────────────────┘

这里name为key(字符串类型),rating为无符号整型。支持抽取标量类型(String, Int/UInt* , Float*),也支持复合数据类型,如Array 、 Tuple、any组合:

SELECT JSONExtract('{"val": [1,2,3,4]}', 'val', 'Array(UInt8)')[2] as item
// 返回结果
┌─item─┐
│    2 │
└──────┘

还可以使用JSON_VALUE函数,无需考虑数据类型:

SELECT
    v,
    JSON_VALUE(v, '$.name') AS name
FROM test_string
LIMIT 5

// 返回结果

┌─v──────────────────────────┬─name─┐
│ {"name":"Joe","age":95}    │ Joe  │
│ {"name":"Jack","rating":9} │ Jack │
│ {"name":"Tom","rating":8}  │ Tom  │
└────────────────────────────┴──────┘

在看一个复杂示例:

WITH JSONExtract(json, 'Tuple(a UInt32, b UInt32, c Nested(d UInt32, e String))') AS parsed_json
SELECT
    JSONExtractUInt(json, 'a') AS a,
    JSONExtractUInt(json, 'b') AS b,
    JSONExtractArrayRaw(json, 'c') AS array_c,
    tupleElement(parsed_json, 'a') AS a_tuple,
    tupleElement(parsed_json, 'b') AS b_tuple,
    tupleElement(parsed_json, 'c') AS array_c_tuple,
    tupleElement(tupleElement(parsed_json, 'c'), 'd') AS `c.d`,
    tupleElement(tupleElement(parsed_json, 'c'), 'e') AS `c.e`
FROM
(
SELECT '{"a":1,"b":2,"c":[{"d":3,"e":"str_1"}, {"d":4,"e":"str_2"}, {"d":3,"e":"str_1"},  {"d":4,"e":"str_1"}, {"d":7,"e":"str_9"}]}' AS json
)
FORMAT Vertical

首先在了解json结构的情况下,使用JSONExtract函数生成tuple结构,上面代码使用with子句。然后再使用tupleElement函数从tuple中抽取值。当有嵌套结构或数组时,可以嵌套调用tupleElement函数。

json key索引

ClickHouse支持使用函数构建排序键,这样可以在索引中使用JSON抽取函数来优化某些查询:

CREATE TABLE test_index ( 
    `t` Int64, 
    `v` String 
    )ENGINE = MergeTree 
ORDER BY JSONExtractUInt(v, 'rating')

上面抽取rating属性值作为排序键,从而使得相关查询效率更高:

SELECT count(*) FROM test_index
WHERE JSONExtractUInt(v, 'rating') = 9

上面语句ClickHouse因使用索引使得查询效率很高,相反,如果过滤JSON属性没有索引,会执行全表扫描。相同查询在相同表上,但排序索引不同,结果相差很大,内存占用和使用时间都有极大差异。

这时你可能会有疑问:如何选择独立列或JSON属性?如果你明确知道json需要抽取哪个字段及数据类型,最好使用独立字段代替。这样可以节省空间(使用特定类型代替字符串)和性能提升(无需json抽取负载)。

试验特性——json对象类型

ClickHouse有试验特性JSON类型,现在考虑生产还为时过早,但我们可以先行学习。因为是试样特性,需要修改配置:

SET allow_experimental_object_type = 1

现在可以使用JSON类型创建表:

CREATE TABLE test_json ( `t` DateTime, `v` JSON )
ENGINE = MergeTree ORDER BY t

插入演示数据,则可以在查询中直接使用对象符号:

SELECT v.name, v.rating FROM test_json LIMIT 5

返回结果:

SELECT v.name, v.rating FROM test_json LIMIT 5

另外使用json对象类型暂用空间更好,示例场景中比字符串类型减少大约30%,但缺点是插入速度慢,对比插入字符串和JSON对象,将近有10倍差异。无论如何这仍然是实验性的,希望Clickhouse团队能够提供更详细文档并尽快正式发布该特性。

总结

json数据在ClickHouse中就和string字段一样,但可以使用JSON*函数检查并抽取json键值。还可以使用抽取函数作为索引提升查询性能,但最好考虑将它们移动到单独的列中,只留下动态内容存储在JSON列中。
参考:https://altinity.com/blog/clickhouse-json-data-type-version-22-6;https://medium.com/datadenys/working-with-json-in-clickhouse-32e41ac0ff86

猜你喜欢

转载自blog.csdn.net/neweastsun/article/details/129867012