Doris JSON函数快速上手:建表、写路径、选函数的完整指南
【免费下载链接】dorisApache Doris is an easy-to-use, high performance and unified analytics database.项目地址: https://gitcode.com/gh_mirrors/dori/doris
Apache Doris JSON函数套件让半结构化数据查询变得直接而简单:JSON 类型列无需拆字段即可进入 SQL,get_json_string、json_extract、json_array_length 三个函数配合 JSON Path 语法,能覆盖绝大多数取值场景。
业务场景:一张表里塞进整段 JSON
以电商商品追踪为例,每次行为上报的载荷都是结构漂移的 JSON:店铺信息嵌套一层、SKU 是个不定长的数组、优惠券字段时有时无。如果强行把每一层都拍平成列,表结构会跟着上游接口频繁变动。更稳妥的做法是让 Doris 的JSON类型列原样承接整段载荷,查询时再用路径按需取字段。
这种"列存 JSON、查询时解析"的模式,代价是把解析工作从写表时移到了读表时,因此路径写法和函数选型就成了性能与正确性的关键。
JSON Path 三步写好路径
JSON Path 的读法可以类比为"在 JSON 这棵树上逐层报门牌号",路径永远以$开头指向根节点。
用点号取对象值
$后面每跟一个.key,就向下钻进一层对象。$.shop.name表示先取shop这个成员,再取它的name。路径中不允许出现引号、方括号之外的特殊分隔,这是底层正则解析(见 be/src/exprs/json_functions.cpp 中的JSON_PATTERN)刻意约束的。
用方括号取数组下标
方括号里的数字从 0 开始计数:$.sku[0].id先取数组第 0 个元素,再取该对象的id。越界下标不会报错,而是当作"路径不存在"返回 NULL,这一点在写兜底逻辑时要心里有数。
用通配符匹配全部元素
[*]代表"数组里所有元素"。$.sku[*].id会取出每个 SKU 的 id 并组成一个数组返回。源码中下标-2即对应这个通配符的展开逻辑,实现在match_value里,适合"统计所有子项"这类不需要逐个写下标的查询。
函数选型:三个核心函数的返回类型与场景
选错函数最常见的后果是拿到一串带引号的文本而不是能直接参与计算的数值。三个函数的定位差异如下:
| 函数 | 返回类型 | 适用场景 | 样例输入输出 |
|---|---|---|---|
get_json_string(json, path) | STRING | 取出字符串值写入 STRING 列,或与其他字符串函数串联 | get_json_string('{"sku":"A1"}', '$.sku')→A1 |
json_extract(json, path) | JSON(保留原始类型) | 需要保留数值/对象/数组的原始结构,交给后续 JSON 函数继续处理 | json_extract('{"cnt":3}', '$.cnt')→3(数值,非文本) |
json_array_length(json) | BIGINT | 统计根节点为数组的 JSON 含有多少个元素 | json_array_length('[1,2,3]')→3 |
选型经验是:结果要落到字符串列就选get_json_string,结果还要继续被 JSON 函数加工就选json_extract,只要个数就选json_array_length。get_json_string在底层注册为json_extract_string的别名,二者语义一致(见 be/src/vec/functions/function_json.cpp 的函数名注册)。
下面这条 SQL 演示了三个函数在同一次查询中的分工:从追踪记录里取店铺名、首个 SKU 编号,并数出 SKU 总数:
SELECT get_json_string(trace, '$.shop.name') AS shop_name, get_json_string(trace, '$.sku[0].id') AS first_sku_id, json_array_length(trace) AS sku_count FROM product_trace WHERE trace_id = 10001;注意:sku_count直接作用于根节点,前提是整列存的是数组;若根节点是对象,需先json_extract(trace, '$.sku')取出数组再求长度。
坑与解法:建表、NULL 兜底、特殊字符
坑一:列类型选错,写入即失败。半结构化列应声明为JSON类型而非STRING,这样加载时就会校验合法性,非法载荷在导入阶段就被过滤,而不是查询时才发现。建表写法可参考回归用例 regression-test/suites/json_p0/test_json_load_and_function.groovy 中的 DDL 风格:
CREATE TABLE product_trace ( trace_id BIGINT, trace JSON ) ENGINE=OLAP DUPLICATE KEY(trace_id) DISTRIBUTED BY HASH(trace_id) BUCKETS 8;坑二:路径不存在时结果是 NULL,直接参与计算会被"吞掉"。优惠券编码这类可空字段,标准写法是IFNULL给一个哨兵默认值,保证下游报表列不空:
SELECT IFNULL(get_json_string(trace, '$.coupon.code'), 'NONE') AS coupon_code FROM product_trace;坑三:字段名含空格或点号时路径解析失败。这类 key 必须用双引号整段包裹,例如$.shop.name会被当作两层对象,而实际 key 就是带点的shop.name时要写成$.\"shop.name\"。含空格的写法同理:
SELECT get_json_string(trace, '$."user name"');带引号片段的路径在解析器中按整串 key 精确匹配,这也是 be/src/exprs/json_functions.h 中JsonPath结构只维护key与idx两个槽位的原因。
性能调优:两条"做与不做"对照
建议一:高频字段冗余为独立列(做),而不是每次查询现场解析(不做)。若shop.name、sku[0].id出现在几乎每条报表里,在写入链路或视图层把它们落为普通列,查询即可走列存与索引,省掉逐行解析。反过来,对全表做$.a.b.c[10].d这种深层路径的聚合,解析成本会随路径深度线性放大,是最典型的反模式。
建议二:依赖向量化解析的批量收益(做),而不是假设单次小查询有常数级开销(不做)。Doris 的 JSON 解析基于 simdjson,be/src/vec/json/simd_json_parser.h 中的实现面向批量列式数据做指令集级加速;大批量行的解析吞吐远好于逐行调用,但单行小查询的相对优势会缩小。因此调优时优先保证"大查询少路径",而不是纠结单条语句的毫秒级差异。
延伸资源:源码、回归测试与社区
- 路径解析与取值匹配:be/src/exprs/json_functions.cpp(
parse_json_paths拆分路径,match_value逐层定位,print_json_value输出最终值) - 向量层函数实现:be/src/vec/functions/function_json.cpp
- 端到端回归用例(建表、Stream Load、全量函数断言):regression-test/suites/json_p0/
- 更完整的函数矩阵与版本行为变化,建议到 Apache Doris 官方文档与社区渠道(官网页脚的社区入口)查询或提问
掌握"路径三步 + 函数三分 + 三个坑"之后,JSON 类型列上的日常查询基本不会再踩到解析与 NULL 的边界问题。
【免费下载链接】dorisApache Doris is an easy-to-use, high performance and unified analytics database.项目地址: https://gitcode.com/gh_mirrors/dori/doris
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考