必须用NVARCHAR(MAX)声明参数且第一步永远是ISJSON()校验,否则非法JSON会静默返回NULL;提取多字段优先用OPENJSON WITH;遍历数组须用CROSS APPLY分层展开;JSON_VALUE返回NULL无法区分缺失、null或空字符串。

必须用 NVARCHAR(MAX) 声明参数,且第一步永远是 ISJSON() 校验——不校验就调 JSON_VALUE 或 OPENJSON,非法 JSON 会静默返回 NULL,查不到数据却无报错,这是最常踩的坑。
为什么 ISJSON() 是不可跳过的第一步
前端传来的 JSON 很容易是空字符串 ''、字符串 'null'、未转义双引号(如 "msg":"he\"llo"),这些都会让 ISJSON(@json) 返回 0。一旦跳过校验,后续所有解析函数都“假装正常”,但结果全为 NULL。
-
ISJSON(@json) = 1才代表可安全进入解析流程;否则应直接RETURN或THROW 50000, 'Invalid JSON', 1 - 别在存储过程里反复调
ISJSON——只校验一次,后续所有操作都基于这个前提 - 若参数来自外部 API,建议在应用层也加校验,避免无效 JSON 流入数据库层
提取多字段优先用 OPENJSON WITH,别堆 JSON_VALUE
要取 $.user.name、$.order.id、$.order.items[0].price 这类跨层级字段,硬写三个 JSON_VALUE 看似简单,但每次调用都会完整重解析整段 JSON 字符串,CPU 开销随字段数线性上涨。
- 正确写法:
OPENJSON(@json) WITH (user_name NVARCHAR(100) '$.user.name', order_id INT '$.order.id', first_price DECIMAL(10,2) '$.order.items[0].price') - 路径中某层缺失(如
user不存在),对应列自动为NULL,无需额外判空 - 字段值可能超 4000 字符(如长描述、Base64 图片),必须显式声明为
NVARCHAR(MAX),否则被无声截断 - 别漏掉
WITH子句——没它的话OPENJSON只返回key/value/type三列字符串,没法直接 join 或 insert
遍历 JSON 数组必须用 CROSS APPLY 分层展开
想处理 $.orders 下每个订单,再遍历每个订单里的 items,千万别用 WHILE 循环拼索引路径,比如 '$.orders[' + CAST(@i AS VARCHAR) + '].id'——这既无法检测越界,又性能极差。
- 标准写法:先
OPENJSON(@json, '$.orders')展开订单列表,再对每行CROSS APPLY OPENJSON(value, '$.items')展开商品 - 外层路径必须精确到数组本身,不能写成
'$.orders.items'——那会把整个数组当对象解析,只出一行 - 如果 JSON 顶层就是数组(如
[{"id":1},{"id":2}]),直接OPENJSON(@json)即可,不加第二参数 - 数组元素为空时,
CROSS APPLY自动跳过该行,不会报错
JSON_VALUE 返回 NULL 不等于字段缺失
JSON_VALUE(@json, '$.status') 返回 NULL 有三种可能:路径不存在、原始值是 JSON null、原始值是空字符串 ""。你无法区分它们,所以不能拿 WHERE ... IS NULL 当“字段没传”来用。
- 判断字段是否存在:用
ISJSON(JSON_QUERY(@json, '$.field')) = 1(JSON_QUERY对不存在路径返回NULL,而ISJSON(NULL)是0) - 想区分空字符串和缺失:在
OPENJSON WITH中设默认值,例如status NVARCHAR(20) '$.status' DEFAULT 'pending' - 数字字段若原始 JSON 中带引号(如
"age": "25"),JSON_VALUE返回字符串,需手动TRY_CAST(... AS INT) - 在
WHERE条件里裸用JSON_VALUE(如WHERE JSON_VALUE(data, '$.type') = 'user')会导致全表逐行解析,没索引时性能崩得快
真正复杂的嵌套校验、深度递归或模式匹配,SQL Server 的 JSON 函数并不擅长——它本质仍是字符串解析,没有真正的 JSON 类型语义。遇到这类需求,更稳的做法是在应用层解析后,只把关键字段透传进存储过程做轻量处理。











