You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SQL Server中用JSON_VALUE构建保留类型的新JSON?

问题描述

现有JSON结构如下:

{
  "_key": {
    "id": "3b8d1ed7-c3ec-32ec-a083-2ddc17152e94",
    "rootId": "15c85327-9628-3685-b84a-375b546ba92a"
  },
  "employeeInfo": {
    "idNumber": "3",
    "gender": "Male",
    "age": 20,
    ....
  },
  "product": {
    "plan": "prod",
    "class": "1",
    "_type": "Product",
    ...
  }
}

需要提取指定字段生成新JSON,当前使用的SQL查询手动拼接字符串导致类型丢失(字符串值不带引号):

SELECT 
    '{ "employeeInfo": {"age: ' + 
    JSON_VALUE(info, '$.employeeInfo.age') + ', "gender": ' + 
    JSON_VALUE(info, '$.employeeInfo.gender') + ' }' AS info
FROM 
    item.[Item] AS c
INNER JOIN 
    (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
     FROM item."Item"
     WHERE "rootId" = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
     GROUP BY "rootId") AS subquery ON c."rootId" = subquery."rootId";

得到的结果:

{ "employeeInfo": {"age: 38, "gender": Female }

期望保留原类型的结果:

{ "employeeInfo": {"age: 38, "gender": "Female" }

使用环境:Microsoft Azure SQL Edge Developer (RTM) - 15.0.2000.1552 (ARM64)
已拥有PostgreSQL解决方案:

SELECT jsonb_strip_nulls(json_build_object('employee_info', json_build_object('age', c."info"->'employeeInfo' -> 'age', 'gender', c."info"->'employeeInfo' -> 'gender'), 'product', c."info"->'product')::jsonb) as info ...

需求:为SQL Server编写支持动态字段选择的查询方案,同时支持提取product.plan等任意字段。


解决方案

1. 静态字段提取(自动保留类型)

如果要提取的字段是固定的,推荐用以下两种方式避免手动拼接的类型问题:

方式A:使用JSON_QUERY保留原类型

JSON_QUERY会返回原始的JSON片段(带正确的类型格式),直接拼接即可:

SELECT 
    JSON_QUERY('{
        "employeeInfo": {
            "age": ' + JSON_QUERY(info, '$.employeeInfo.age') + ',
            "gender": ' + JSON_QUERY(info, '$.employeeInfo.gender') + '
        },
        "product": {
            "plan": ' + JSON_QUERY(info, '$.product.plan') + '
        }') AS info
FROM 
    item.[Item] AS c
INNER JOIN 
    (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
     FROM item."Item"
     WHERE "rootId" = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
     GROUP BY "rootId") AS subquery 
    ON c."rootId" = subquery."rootId";

方式B:用FOR JSON PATH自动构造JSON(更可靠)

通过将提取的字段作为SQL列,再用FOR JSON PATH生成最终JSON,自动处理类型映射:

SELECT 
    (SELECT 
        JSON_VALUE(info, '$.employeeInfo.age') AS age,
        JSON_VALUE(info, '$.employeeInfo.gender') AS gender
     FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS employeeInfo,
    (SELECT 
        JSON_VALUE(info, '$.product.plan') AS plan
     FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS product
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER AS info
FROM 
    item.[Item] AS c
INNER JOIN 
    (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
     FROM item."Item"
     WHERE "rootId" = 'E3B455EF-D48E-338C-B6D4-FFD8B41243F9' 
     GROUP BY "rootId") AS subquery 
    ON c."rootId" = subquery."rootId";

这种方式无需手动写JSON语法,FOR JSON PATH会根据SQL列的类型自动生成对应JSON类型(字符串带引号、数字/布尔保持原样)。

2. 动态字段选择(支持任意字段)

如果需要动态指定要提取的字段(比如从参数传入),可以用动态SQL结合FOR JSON PATH实现:

实现步骤

  1. 定义字段映射参数:用JSON格式指定要提取的源路径和目标键(支持嵌套结构)
  2. 动态生成SQL语句:根据字段映射生成列定义,再用FOR JSON PATH生成最终JSON

示例代码:

-- 定义要提取的字段:源JSON路径 -> 目标JSON键
DECLARE @fields NVARCHAR(MAX) = N'[
    {"sourcePath": "$.employeeInfo.age", "targetKey": "employeeInfo.age"},
    {"sourcePath": "$.employeeInfo.gender", "targetKey": "employeeInfo.gender"},
    {"sourcePath": "$.product.plan", "targetKey": "product.plan"}
]';

DECLARE @sql NVARCHAR(MAX);

-- 生成FOR JSON PATH需要的列定义(将嵌套键的.替换为_,避免SQL列名冲突)
SELECT @sql = STRING_AGG(
    CONCAT(
        'JSON_VALUE(info, ''', sourcePath, ''') AS ', 
        QUOTENAME(REPLACE(targetKey, '.', '_'))
    ),
    ', '
)
FROM OPENJSON(@fields)
WITH (
    sourcePath NVARCHAR(MAX) '$.sourcePath',
    targetKey NVARCHAR(MAX) '$.targetKey'
);

-- 拼接完整查询语句
SET @sql = CONCAT(
    N'SELECT ', @sql, '
    FROM item.[Item] AS c
    INNER JOIN 
        (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
         FROM item."Item"
         WHERE "rootId" = ''E3B455EF-D48E-338C-B6D4-FFD8B41243F9'' 
         GROUP BY "rootId") AS subquery 
        ON c."rootId" = subquery."rootId"
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, INCLUDE_NULL_VALUES;'
);

-- 执行动态SQL
EXEC sp_executesql @sql;

扩展:支持提取完整子对象

如果需要提取整个子对象(比如完整的product),可以在字段映射中加入类型标记,用JSON_QUERY提取:

DECLARE @fields NVARCHAR(MAX) = N'[
    {"sourcePath": "$.employeeInfo.age", "targetKey": "employeeInfo.age", "type": "value"},
    {"sourcePath": "$.employeeInfo.gender", "targetKey": "employeeInfo.gender", "type": "value"},
    {"sourcePath": "$.product", "targetKey": "product", "type": "query"}
]';

DECLARE @sql NVARCHAR(MAX);

SELECT @sql = STRING_AGG(
    CASE type
        WHEN 'value' THEN CONCAT(
            'JSON_VALUE(info, ''', sourcePath, ''') AS ', 
            QUOTENAME(REPLACE(targetKey, '.', '_'))
        )
        WHEN 'query' THEN CONCAT(
            'JSON_QUERY(info, ''', sourcePath, ''') AS ', 
            QUOTENAME(targetKey)
        )
    END,
    ', '
)
FROM OPENJSON(@fields)
WITH (
    sourcePath NVARCHAR(MAX) '$.sourcePath',
    targetKey NVARCHAR(MAX) '$.targetKey',
    type NVARCHAR(10) '$.type'
);

SET @sql = CONCAT(
    N'SELECT ', @sql, '
    FROM item.[Item] AS c
    INNER JOIN 
        (SELECT "rootId", MAX("revisionNo") AS maxRevisionNo 
         FROM item."Item"
         WHERE "rootId" = ''E3B455EF-D48E-338C-B6D4-FFD8B41243F9'' 
         GROUP BY "rootId") AS subquery 
        ON c."rootId" = subquery."rootId"
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, INCLUDE_NULL_VALUES;'
);

EXEC sp_executesql @sql;

内容的提问来源于stack exchange,提问作者Valeriy K.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:35:54