OPENJSON + CROSS APPLY 是解析 SQL Server 多层嵌套 JSON 的标准解法,通过逐级展开 value 字段实现链式解析;硬套子查询因路径无法参数化而失败,JSON 路径必须为编译期常量。

APPLY 是处理 SQL Server 中嵌套 JSON 的关键手段,尤其当 JSON_VALUE 无法直接展开数组、或需要逐层“钻取”动态结构时,CROSS APPLY 或 OUTER APPLY 几乎是唯一可靠的选择。硬套子查询会报错或返回空,因为 JSON 解析函数不能在子查询的 SELECT 列表里直接引用外层字段做路径拼接。
为什么不能用普通子查询解析 $.items[0].name 这类路径
子查询作用域隔离,无法把外层变量注入 JSON 路径字符串;JSON_VALUE(@json, '$.items[' + CAST(@i AS VARCHAR) + '].name') 这种写法在子查询中不被允许,SQL Server 会报 Invalid column name 或直接忽略变量。更本质的问题是:JSON 路径必须是编译期可确定的字符串字面量,不能是运行期拼接表达式(除非走 EXEC 动态 SQL,但那引入新风险)。
- 路径字符串必须是常量,如
'$.items[0].name',不能是'$.items[' + @idx + '].name' -
JSON_VALUE和JSON_QUERY都不支持参数化路径 - 子查询里调用这些函数时,无法访问父查询的列值来构造路径
OPENJSON + CROSS APPLY 是标准解法
把 JSON 字符串先用 OPENJSON 拆成行集,再用 APPLY 对每一行继续解析其内部 JSON 字段——这是 SQL Server 处理多层嵌套的惯用链式模式。例如解析 $.data.boards 下每个 board 的 items 数组:
SELECT b.name AS board_name, i.name AS item_name, cv.text AS column_text FROM OPENJSON(@MondayComApi, '$.data.boards') WITH (name NVARCHAR(100) '$.name') AS b CROSS APPLY OPENJSON(b.value, '$.items') WITH (name NVARCHAR(100) '$.name') AS i CROSS APPLY OPENJSON(i.value, '$.column_values') WITH (text NVARCHAR(100) '$.text') AS cv;
-
b.value是每个board对象的完整 JSON 字符串,可再次传给OPENJSON - 每级
APPLY都基于上一级输出的value字段,形成“一层剥开一层”的解析流 - 若某层 JSON 不存在(比如
items是null或缺失),CROSS APPLY会跳过该行;需保留则改用OUTER APPLY
遇到 $.items[0] 这种固定索引时,别手动写死
写 '$.items[0].name' 看似简单,但一旦数组为空或 [0] 不存在,整个 JSON_VALUE 返回 NULL,且无法区分是字段为空还是路径根本不存在。更健壮的做法是先用 OPENJSON 展开数组,再用 ROW_NUMBER() 或条件过滤取首项:
SELECT TOP 1 JSON_VALUE(item.value, '$.name') AS name FROM OPENJSON(@json, '$.items') AS item ORDER BY CAST(JSON_VALUE(item.value, '$.id') AS INT);
- 避免依赖
[0]索引,JSON 数组顺序不保证,[0]可能不是你想要的“第一个” - 用
TOP 1+ORDER BY显式控制取哪一条,语义清晰且可索引 - 如果必须取索引为 0 的元素,先确认
ISJSON和路径存在性:JSON_PATH_EXISTS(@json, '$.items[0]')(SQL Server 2022+)
性能陷阱:别在 WHERE 里反复调用 OPENJSON
对大表逐行执行 OPENJSON 开销极高,尤其是嵌套多层时。常见错误是把 OPENJSON 放在 WHERE 子句里做条件过滤:
-- ❌ 千万别这么写 WHERE JSON_VALUE(data, '$.user.address.city') = 'beijing'
- 每次比较都触发完整 JSON 解析,无法走索引,CPU 和内存压力陡增
- 正确做法:提前用计算列固化常用路径,例如
ALTER TABLE T ADD city AS JSON_VALUE(data, '$.user.address.city'),再对该列建索引 - SQL Server 2022 起可用
JSON_PATH_EXISTS快速判断路径是否存在,比JSON_VALUE轻量得多
真正难的不是语法,而是理解 OPENJSON 输出的 key、value、type 三列如何被下一级 APPLY 复用;很多人卡在没意识到 value 才是下一层要解析的 JSON 字符串,而误用了原始字段名。


















