【问题标题】:Get array of values instead of array of objects when using FOR JSON使用 FOR JSON 时获取值数组而不是对象数组
【发布时间】:2024-04-17 10:55:01
【问题描述】:

我正在尝试展平由FOR JSON 构造的对象数组。

我的查询如下:

select 
(                           
    select id from MyTable
    where id in (select value from OPENJSON(@jsonArray))
    FOR JSON PATH
) existing,                 
(   
    select value id from OPENJSON(@jsonArray) 
    where value not in (select Id from MyTable)
    FOR JSON PATH                       
) missing
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER

生成的 JSON 是:

{
    "existing": [
        {
            "id": "a00cd8f6-d1c6-4604-b235-59d3cacd5bcc"
        },
        {
            "id": "052455b6-6bf5-47d3-8bee-7ba98d7fbd50"
        }
    ],
    "missing": [
        {
            "id": "328add2d-e8f2-4a0e-af54-5b1733310170"
        }
    ]
}

我想要的是:

{
    "existing": [
        {
            "id": "a00cd8f6-d1c6-4604-b235-59d3cacd5bcc"
        },
        {
            "id": "052455b6-6bf5-47d3-8bee-7ba98d7fbd50"
        }
    ],
    "missing": [
        "328add2d-e8f2-4a0e-af54-5b1733310170"            
    ]
}

缺少的数组不应包含 json 对象,而应仅包含值。 有什么建议吗?

【问题讨论】:

    标签: sql json sql-server tsql for-json


    【解决方案1】:

    如果您使用的是 SQL Server 2017,您可以使用 JSON_QUERYSTRING_AGG 构建您的阵列(对于 SQL Server 2016,您不能使用 STRING_AGG,因此您必须做一些 estra 工作,但以下想法仍然有效):

    declare @missing table(id varchar(max))
    declare @existing table(id varchar(max))
    
    insert into @missing values ('a00cd8f6-d1c6-4604-b235-59d3cacd5bcc')
    insert into @missing values ('052455b6-6bf5-47d3-8bee-7ba98d7fbd50')
    insert into @existing values ('328add2d-e8f2-4a0e-af54-5b1733310170')
    
    select  
    (                           
        select id from @missing
        FOR JSON PATH
    ) existing,                
    (   
        select JSON_QUERY(concat('[' , STRING_AGG(concat('"' , STRING_ESCAPE(id, 'json') , '"'),',') , ']')) 
        from @existing                 
    ) missing 
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
    

    结果:

    {
      "existing": [
        {
          "id": "a00cd8f6-d1c6-4604-b235-59d3cacd5bcc"
        },
        {
          "id": "052455b6-6bf5-47d3-8bee-7ba98d7fbd50"
        }
      ],
      "missing": [
        "328add2d-e8f2-4a0e-af54-5b1733310170"
      ]
    }
    

    【讨论】:

    • 如果id 包含任何要转义的字符,QUOTENAME 将不会生成有效的 JSON。我建议改用'"' + STRING_ESCAPE(id, 'json') + '"'(即使在ids 是GUID 的情况下并不重要)。
    • @JeroenMostert 没想到:你说得对!我更新了我的答案,包括STRING_ESCAPE,谢谢。
    【解决方案2】:

    这并不像应该的那么容易......

    AFAIK 无法使用 sql-server 创建 naked json 数组。但是你可以在字符串级别上欺骗这个:

    DECLARE @exist TABLE(id VARCHAR(100));
    DECLARE @miss TABLE(id VARCHAR(100));
    
    INSERT INTO @exist VALUES ('exist1'),('exist2');
    INSERT INTO @miss VALUES ('miss1'),('miss2');
    

    --这将创建你想要的对象数组

    SELECT id FROM @exist
    FOR JSON PATH
    

    --这将使用一些相当丑陋的技巧来创建 naked 数组。

    SELECT REPLACE(REPLACE(REPLACE(
    (
        SELECT id from @miss
        FOR JSON PATH
    ),'"id":',''),'{',''),'}','')
    

    --现在我们必须将两者结合起来。我们再次需要一个技巧。我们在 JSON 文字上使用 JSON_QUERY() 以避免转义引号。

    SELECT
    (
        SELECT id FROM @exist
        FOR JSON PATH
    ) AS existing
    ,JSON_QUERY(
       REPLACE(REPLACE(REPLACE(
        (
            SELECT id from @miss
            FOR JSON PATH
        ),'"id":',''),'{',''),'}','')
    ) AS missing
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;
    

    这是结果

    {
      "existing":[{"id":"exist1"},{"id":"exist2"}]  <--array of objects
     ,"missing":["miss1","miss2"]                   <--array of naked values
    }
    

    我不知道,为什么不能这样做开箱即用...

    【讨论】:

    • 如果 JSON 值本身包含 {} 字符,这将无法正常工作——这些将被 REPLACE 无情地删除。 (当然,没有 JSON 值会直接包含 "id":,所以这仍然是安全的。)
    • MS SQL Server 中的整个 JSON 功能对我来说似乎不完整。
    【解决方案3】:

    使用从光标追加

    DECLARE @missing nvarchar(max),
        @json nvarchar(max) = (select 
                                (                           
                                    select id from MyTable
                                    where id in (select value from OPENJSON(@jsonArray))
                                    FOR JSON PATH
                                ) existing
                                FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
    
        DECLARE missing_cursor CURSOR FOR   
            select value id 
            from OPENJSON(@jsonArray)
            where value not in (select Id from MyTable)
    
            OPEN missing_cursor  
    
            FETCH NEXT FROM missing_cursor   
            INTO @missing
    
            WHILE @@FETCH_STATUS = 0  
            BEGIN  
                SET @json = JSON_MODIFY(@json,'append $.missing', @missing)
    
                FETCH NEXT FROM missing_cursor   
                INTO @missing  
            END   
            CLOSE missing_cursor;  
            DEALLOCATE missing_cursor; 
    
        select @json
    

    【讨论】: