StarRocks JSON 函数与操作符全景:构造函数、查询处理函数、JSON 操作符与路径表达式
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
本文以 StarRocks 官方文档中「JSON functions and operators overview」页面为主体,系统梳理 StarRocks 对 JSON 数据的三类处理能力:JSON 构造函数(json_object、json_array、parse_json)、JSON 查询/处理函数(箭头函数->、cast、get_json_*、json_query、json_remove、json_each、json_exists、json_keys、json_length、json_string)、JSON 比较操作符(<、<=、>,>=、=、!=),以及 StarRocks 方言下的 JSON 路径表达式语法($、.、[n]、[*]、[start:end])。读完本文,你可以直接掌握如何在 StarRocks 中构造、查询、修改和比较 JSON 数据,并能结合 BE 源码(如 json_functions.h 与 jsonpath.h)理解这些函数在查询引擎中的实现方式,进而在日志分析、用户画像等半结构化数据场景写出可运行的 JSON 查询。
一、总览:三类 JSON 能力
StarRocks 文档将 JSON 能力划分为三组:
- JSON 构造函数(JSON constructor functions):用于从零构造 JSON 对象、JSON 数组,或把字符串解析为 JSON 值;
- JSON 查询函数与处理函数(JSON query functions and processing functions):用于查询 JSON 内部元素(按路径定位)、在 JSON 与 SQL 类型之间转换、删除/展开/校验 JSON 数据;
- JSON 操作符与路径表达式(JSON operators and path expressions):用于比较两个 JSON 值、用路径字符串定位 JSON 对象中的元素。
官方文档还特别提示:可以用生成列(generated columns)来加速对 JSON 列的重复查询——即把常用路径提前物化成列,避免每行重复解析。
二、JSON 构造函数
JSON 构造函数用于构造 JSON 对象和 JSON 数组等 JSON 数据,包含三个函数:
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
| json_object | 将一组键值对转换为由这些键值对组成的 JSON 对象,键值对按键的字典序排序 | SELECT JSON_OBJECT('Daniel Smith', 26, 'Lily Smith', 25); | {"Daniel Smith": 26, "Lily Smith": 25} |
| json_array | 将 SQL 数组的每个元素转换为 JSON 值,返回由这些值组成的 JSON 数组 | SELECT JSON_ARRAY(1, 2, 3); | [1,2,3] |
| parse_json | 将字符串转换为 JSON 值 | SELECT PARSE_JSON('{"a": 1}'); | {"a": 1} |
2.1 json_object:由键值对构造对象
语法为json_object(key, value, ...)。参数约束如下(摘自 json_object 文档):
key:JSON 对象的键,仅支持 VARCHAR 类型;value:JSON 对象的值,支持NULL以及 STRING、VARCHAR、CHAR、JSON、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DOUBLE、FLOAT、BOOLEAN。
返回值是一个 JSON 对象;如果键值总数为奇数,最后一个字段会被填充NULL。典型示例:
-- 由不同类型的值构造对象 SELECT json_object('name', 'starrocks', 'active', true, 'published', 2020); -- 返回 {"active": true, "name": "starrocks", "published": 2020} -- 嵌套构造 SELECT json_object('k1', 1, 'k2', json_object('k2', 2), 'k3', json_array(4, 5)); -- 返回 {"k1": 1, "k2": {"k2": 2}, "k3": [4, 5]} -- 空对象 SELECT json_object(); -- 返回 {}2.2 parse_json:把字符串变成 JSON 值
语法为parse_json(string_expr),参数只支持 STRING/VARCHAR/CHAR 类型。返回值是一个 JSON 值;如果字符串无法解析为标准 JSON 值,返回NULL(例如{star: "rocks"}中键未加双引号时)。要点示例:
SELECT parse_json('1'); -- 标量 "1" SELECT parse_json('[1,2,3]'); -- JSON 数组 [1, 2, 3] SELECT parse_json('{"star": "rocks"}'); -- JSON 对象 {"star": "rocks"} SELECT parse_json('null'); -- JSON 字面量 null SELECT parse_json('{star: "rocks"}'); -- 非法 JSON,返回 NULL一个容易踩坑的细节:当 JSON 键本身包含.时,路径中必须转义或用引号包裹整个键,例如:
SELECT parse_json('{"b":4, "a.1": "1"}')->"a\\.1"; -- 返回 "1" SELECT parse_json('{"b":4, "a.1": "1"}')->'"a.1"'; -- 返回 "1"2.3 源码视角:这些函数在 BE 中如何落地
从源码结构看,上述构造函数都在 BE 端以向量化函数实现。be/src/exprs/json_functions.h 中的JsonFunctions类声明了parse_json、json_object、json_object_empty、json_array、json_array_empty等DEFINE_VECTORIZED_FN函数——参数与返回类型分别对应BinaryColumn(字符串)与JsonColumn(JSON 列),说明 StarRocks 对 JSON 采用了列式(Column)存储与向量化求值,而非逐行标量计算。值得注意的是,BE 源码中还存在json_set、json_pretty、json_contains、to_json、is_json_scalar等总览表之外、但仓库文档目录下已收录的函数,可见实际函数面比总览页更宽。
三、JSON 查询函数与处理函数
用于查询和处理 JSON 数据。例如,可以用路径表达式在 JSON 对象中定位某个元素。总览页收录的函数如下:
| 函数 | 说明 | 示例 | 返回值 |
|---|---|---|---|
| 箭头函数 | 查询 JSON 对象中可通过路径表达式定位的元素 | SELECT parse_json('{"a": {"b": 1}}') -> '$.a.b'; | 1 |
| cast | 在 JSON 数据类型与 SQL 数据类型之间转换 | SELECT CAST(1 AS JSON); | 1 |
| get_json_double | 解析 JSON 字符串并从指定路径取出浮点值 | SELECT get_json_double('{"k1":1.3, "k2":"2"}', "$.k1"); | 1.3 |
| get_json_int | 解析 JSON 字符串并从指定路径取出整数值 | SELECT get_json_int('{"k1":1, "k2":"2"}', "$.k1"); | 1 |
| get_json_string | 解析 JSON 字符串并从指定路径取出字符串 | SELECT get_json_string('{"k1":"v1", "k2":"v2"}', "$.k1"); | v1 |
| json_query | 查询 JSON 对象中可通过路径表达式定位的元素的值 | SELECT JSON_QUERY('{"a": 1}', '$.a'); | 1 |
| json_remove | 从一个或多个指定 JSON 路径处删除 JSON 文档中的数据 | SELECT JSON_REMOVE('{"a": 1, "b": [10, 20, 30]}', '$.a', '$.b[1]'); | {"b": [10, 30]} |
| json_each | 将 JSON 对象的顶层元素展开为键值对 | SELECT * FROM tj_test, LATERAL JSON_EACH(j); | 见 json_each 示例图 |
| json_exists | 检查 JSON 对象中是否包含可通过路径定位的元素,存在返回 1,否则返回 0 | SELECT JSON_EXISTS('{"a": 1}', '$.a'); | 1 |
| json_keys | 以 JSON 数组形式返回 JSON 对象的顶层键;若指定路径则返回该路径下的顶层键 | SELECT JSON_KEYS('{"a": 1, "b": 2, "c": 3}'); | ["a", "b", "c"] |
| json_length | 返回 JSON 文档的长度 | SELECT json_length('{"Name": "Alice"}'); | 1 |
| json_string | 将 JSON 对象转换为 JSON 字符串 | SELECT json_string(parse_json('{"Name": "Alice"}')); | {"Name": "Alice"} |
下面对其中最具代表性的几个函数展开讲解。
3.1 箭头函数->与 json_query:按路径取值
箭头函数 的语法是json_object_expr -> json_path,它比 json_query 函数json_query(json_object_expr, json_path)更紧凑易用。两者第一个参数都可以是 JSON 列,或PARSE_JSON等构造函数产生的 JSON 对象;第二个参数是表示路径的字符串。返回值都是 JSON 值,若元素不存在则返回 SQL 的NULL。
-- 基本用法 SELECT parse_json('{"a": {"b": 1}}') -> '$.a.b'; -- 返回 1 -- 嵌套箭头函数:内层结果继续定位 SELECT parse_json('{"a": {"b": 1}}')->'a'->'b'; -- 返回 1 -- 路径可省略根节点 $ SELECT parse_json('{"a": "b"}') -> 'a'; -- 返回 "b"json_query的行为完全对应,且数组索引越界时同样返回NULL:
SELECT json_query(PARSE_JSON('{"a": {"b": 1}}'), '$.a.b'); -- 1 SELECT json_query(PARSE_JSON('{"a": {"b": 1}}'), '$.a.c'); -- NULL SELECT json_query(PARSE_JSON('{"a": [1,2,3]}'), '$.a[2]'); -- 3 SELECT json_query(PARSE_JSON('{"a": [1,2,3]}'), '$.a[3]'); -- NULL从源码结构看,BE 端 json_functions.h 中json_query被声明为接受[JsonColumn, BinaryColumn]、返回 JSON 列的向量化函数,并存在get_native_json_*系列函数(如get_native_json_int、get_native_json_string),即对原生 JSON 列取标量值时会走类型化的快路径,避免完整解析整棵 JSON 树。
3.2 json_each:把 JSON 对象展开成行
json_each 是一个表函数,把 JSON 对象的顶层元素展开为键值对行,典型用法是配合LATERAL:SELECT * FROM tj_test, LATERAL JSON_EACH(j);。其运行结果见下(原文档中的示例截图):
从源码结构看,json_each在 BE 中实现为表函数(table function),位于 be/src/exprs/table_function/json_each.h,这也解释了为什么它必须出现在FROM子句而非普通的SELECT表达式位置。
3.3 get_json_* 系列:面向字符串的便捷取值
get_json_int、get_json_double、get_json_string直接接受 JSON 字符串和路径两个参数,省去先PARSE_JSON再取值的步骤。从源码结构看,json_functions.h 中这些函数签名是[json_string, tagged_value](两个BinaryColumn入参,返回对应标量列),与json_query接受JsonColumn入参形成两套入口,便于在不同数据类型场景下选用。
四、JSON 操作符:比较规则
StarRocks 支持以下 JSON 比较操作符:<、<=、>、>=、=、!=,可用它们查询 JSON 数据;但不允许使用IN查询 JSON 数据。规则如下(摘自 JSON operators 文档):
- 操作符的两个操作数都必须是 JSON 值;若一个是 JSON、另一个不是,则非 JSON 操作数会在运算期间按 CAST 的转换规则转为 JSON 值。
- 同类型操作数:
- 若两者都是 NUMBER、STRING、BOOLEAN 等基本类型,则按该基本类型的算术/比较规则执行;若一方是 DOUBLE、另一方是 INT,则 INT 先转换为 DOUBLE。
- 若两者都是 OBJECT 或 ARRAY 等复合类型,则先按第一个操作数中键的顺序对双方键做字典序排列,再逐键比较值。
- 不同类型操作数:按类型序比较,规则为
NULL < BOOLEAN < ARRAY < OBJECT < DOUBLE < INT < STRING。
文档给出了两个可复现的对比示例:
-- 示例 1:按第一个操作数的键序比较键 a 的值:1 < 2,故前者更小 SELECT PARSE_JSON('{"a": 1, "c": 2}') < PARSE_JSON('{"b": 1, "a": 2}'); -- 返回 1 -- 示例 2:键 a 的值相等(1 = 1),再比较键 c:第二个操作数没有键 c,故前者更大 SELECT PARSE_JSON('{"a": 1, "c": 2}') < PARSE_JSON('{"b": 1, "a": 1}'); -- 返回 0 -- 跨类型比较:STRING 位于 OBJECT 之后(NULL < BOOLEAN < ARRAY < OBJECT < DOUBLE < INT < STRING) SELECT PARSE_JSON('"a"') < PARSE_JSON('{"a": 1, "c": 2}'); -- 返回 0需要留意的是示例 2 的结论方向:键a相等后,第一个操作数多出了键c,因此判定{"a": 1, "c": 2}大于{"b": 1, "a": 1},<的比较结果为 0。
五、JSON 路径表达式:StarRocks 支持的语法
JSON 路径表达式用于查询 JSON 对象中的元素,其数据类型为 STRING,在大多数情况下与JSON_QUERY等各类 JSON 函数配合使用。StarRocks 的路径语法并不完全遵循 SQL/JSON 路径规范,官方以如下 JSON 对象为例说明支持的符号:
{ "people": [{ "name": "Daniel", "surname": "Smith" }, { "name": "Lily", "surname": "Smith", "active": true }] }| JSON 路径符号 | 说明 | 路径示例 | 返回值 |
|---|---|---|---|
$ | 表示根 JSON 对象 | '$' | 整个根对象(people数组含两个元素) |
. | 表示子 JSON 对象 | '$.people' | people数组(两个对象) |
[] | 表示一个或多个数组索引,[n]表示第 n 个元素,索引从 0 开始。StarRocks 2.5 支持查询多维数组,例如["Lucy", "Daniel"], ["James", "Smith"],用$.people[0][0]可查询 "Lucy" 元素 | '$.people[0]' | { "name": "Daniel", "surname": "Smith" } |
[*] | 表示数组中的所有元素 | '$.people[*].name' | ["Daniel", "Lily"] |
[start: end] | 表示数组元素的一个子集,由[start, end]区间指定,不包含 end 索引对应的元素 | '$.people[0: 1].name' | ["Daniel"] |
注意切片区间是左闭右开语义:$.people[0:1].name只取到索引 0 为止。另外,如前文 parse_json 示例所示,键中包含.等符号时需要在路径中转义("a\\.1")或用引号整体包裹键名('"a.1"')。
5.1 源码视角:路径解析器如何对应这套语法
从源码结构看,be/src/exprs/jsonpath.h 中的JsonPath/JsonPathPiece结构正是上述语法的实现:JsonPathPiece::parse负责把路径字符串解析为「键 + 数组选择器」的序列,而数组选择器被抽象为ArraySelector及其四类子类——ArraySelectorSingle(arr[x]取第 x 个元素)、ArraySelectorWildcard(arr[*]取全部元素)、ArraySelectorSlice(arr[1:3]取切片)以及ArraySelectorNone,与路径表达式表格中[n]、[*]、[start:end]三种写法一一对应。JsonPath还提供了starts_with与relativize(例如把$.a.b[1]相对$.a化为$.b[1])等方法,为 JSON 列的扁平化存储(flat JSON)提供路径前缀匹配能力;BE 存储层还有配套的 be/src/storage/json_path_deriver.h,可推断 StarRocks 会把高频查询路径推导为物化子列,这正是「用生成列/路径物化加速 JSON 查询」建议背后的实现基础。
六、实践要点小结
- 构造与解析:用
json_object/json_array拼 JSON、用parse_json把字符串转 JSON;解析失败统一返回NULL,写条件判断时可用json_exists或IS NOT NULL甄别脏数据。 - 取值:优先用箭头函数
->或json_query按路径取元素,路径越界或键不存在都返回NULL;面向字符串的轻量取值场景用get_json_int/get_json_double/get_json_string。 - 变形与展开:
json_remove删除指定路径数据,json_set(见 json_set 文档)插入或更新,json_each把对象展开成行,json_keys、json_length做元信息提取。 - 比较:六种比较操作符可用,
IN不可用于 JSON;复合类型按「首操作数键序 + 字典序」比较,跨类型按NULL < BOOLEAN < ARRAY < OBJECT < DOUBLE < INT < STRING排序。 - 路径语法:牢记
$、.、[n](0 起)、[*]、[start:end](右开)五个符号;多维数组下标(2.5 起)与含特殊字符键的转义写法按第五节表格使用。 - 性能:对反复查询的 JSON 路径,文档建议采用生成列物化,配合 BE 端 flat JSON 路径推导机制降低每行解析开销。
相关参考文档均位于 docs/en/sql-reference/sql-functions/json-functions/ 目录;BE 端实现可进一步查阅 be/src/exprs/json_functions.cpp、be/src/exprs/jsonpath.cpp 与 be/src/types/simple_json_path.h。
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考