SQL Server 2016+ 支持 FOR JSON PATH/AUTO 生成嵌套 JSON,但仅限两级;深层嵌套需 OPENJSON 分层解析或 CTE 分步处理,子查询必须单行单列且用 WITHOUT_ARRAY_WRAPPER 或显式别名。

SQL Server 2016+ 必须用 OPENJSON,不能用子查询直接解析嵌套 JSON
SQL Server 不支持像 MySQL 的 JSON_OBJECT 那样在 SELECT 中直接构造嵌套结构,更不支持用普通子查询去“读取”JSON 字段里的深层字段——SELECT ... (SELECT value FROM OPENJSON(...)) 这类写法会报错或返回空,因为 OPENJSON 返回的是行集,不是标量值。真正的嵌套解析必须分层展开:先用外层 OPENJSON 解出第一层对象数组,再对每一行用 WITH 子句或嵌套 APPLY 处理子数组。
用 CROSS APPLY + OPENJSON 展开多层嵌套数组(如 tags、categories)
常见错误是把多个 OPENJSON 写成并列子查询,导致笛卡尔积膨胀。正确做法是链式 APPLY:
SELECT p.id, p.title, t.[value] AS tag_name, c.[value] AS category_name FROM posts p CROSS APPLY OPENJSON(p.metadata, '$.tags') t CROSS APPLY OPENJSON(p.metadata, '$.categories') c;
但注意:这会产生所有 tags × categories 的组合。若只需各自独立列表,应拆成两个独立子查询并用 COALESCE 补空:
- 用
(SELECT STRING_AGG(t.[value], ',') FROM OPENJSON(p.metadata, '$.tags') WITH ([value] NVARCHAR(100) '$') t)提取扁平字符串 - 或用
(SELECT JSON_QUERY('[' + STRING_AGG(JSON_QUOTE(t.[value]), ',') + ']') FROM OPENJSON(...) t)构造合法 JSON 数组 - 若子路径不存在,
OPENJSON返回空结果集,不是 NULL —— 所以聚合函数不会报错,但需确认业务是否接受空数组
FOR JSON PATH 嵌套子查询的唯一合法写法
SQL Server 允许在主查询中用标量子查询生成 JSON 字段,但有硬性限制:子查询必须返回单行单列,且必须用 FOR JSON PATH 并显式加别名 AS [key]:
SELECT id, title, (SELECT name FROM categories c WHERE c.post_id = p.id FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS category, (SELECT tag FROM tags t WHERE t.post_id = p.id FOR JSON PATH) AS tags FROM posts p;
关键点:
-
WITHOUT_ARRAY_WRAPPER仅当确定最多匹配一行时才可用,否则会拼接成非法 JSON - 子查询若无结果,整个字段为 NULL,不是
[]—— 如需空数组,得套ISNULL(..., '[]') -
FOR JSON PATH默认将列名转为 key,若要自定义 key 名,必须用AS [customKey] - 多层嵌套超过两级(比如 post → category → department)无法用单条
FOR JSON实现,必须用 CTE 或临时表分步处理
性能陷阱:OPENJSON 没索引,FOR JSON 不缓存
每次调用 OPENJSON 都会重新解析整个 JSON 文本,字段越长、嵌套越深,CPU 开销越大;FOR JSON 同样是运行时序列化,无法走索引。真实场景中容易忽略的点:
- JSON 字段未建计算列或持久化视图,导致每次查询都重复解析
- 在 WHERE 条件里用
JSON_VALUE(metadata, '$.status')过滤,但该列没建索引,全表扫描不可避免 - 误以为
FOR JSON输出可被客户端复用,实际它生成的是 NVARCHAR(MAX),网络传输和内存占用翻倍 - 混合使用
OPENJSON和JSON_VALUE时,路径表达式大小写敏感('$.Tags'≠'$.tags'),且不自动 trim 空格











