SQL to JSON - массив объектов в массив значений в SQL 2016

В SQL 2016 появилась новая функция, которая преобразует данные на сервере SQL в JSON. У меня возникли трудности в объединении массива объектов в массив значений, т. Е.

ПРИМЕР -

CREATE TABLE #temp (item_id VARCHAR(256))

INSERT INTO #temp VALUES ('1234'),('5678'),('7890')

SELECT * FROM #temp

--convert to JSON

SELECT (SELECT item_id 
FROM #temp
FOR JSON PATH,root('ids')) 

РЕЗУЛЬТАТ -

{
    "ids": [{
        "item_id": "1234"
    },
    {
        "item_id": "5678"
    },
    {
        "item_id": "7890"
    }]
}

Но я хочу результат как -

"ids": [
        "1234",
        "5678",
        "7890"
    ]

Может кто-нибудь помочь мне?

9 ответов

Решение

Спасибо! Душа, которую мы нашли, сначала конвертируется в XML -

SELECT  
JSON_QUERY('[' + STUFF(( SELECT ',' + '"' + item_id + '"' 
FROM #temp FOR XML PATH('')),1,1,'') + ']' ) ids  
FOR JSON PATH , WITHOUT_ARRAY_WRAPPER 

Мартин!

Я считаю, что это еще более простой способ сделать это:

    SELECT '"ids": ' + 
    REPLACE( 
      REPLACE( (SELECT item_id FROM #temp FOR JSON AUTO),'{"item_id":','' ),
      '"}','"' )
declare @temp table (item_id VARCHAR(256))

INSERT INTO @temp VALUES ('123"4'),('5678'),('7890')

SELECT * FROM @temp

--convert to JSON

select 
    json_query(QUOTENAME(STRING_AGG('"' + STRING_ESCAPE(item_id, 'json') + '"', char(44)))) as [json]
from @temp
for json path

Когда мы хотим объединить строки как массив json, тогда:

1) escape-строка - STRING_ESCAPE

2) объединить строку с разделителем-запятой - STRING_AGG, код запятой ascii равен 44

3) добавить цитату в скобках - QUOTENAME (без параметра)

4) вернуть строку (с массивом элементов) в виде json - JSON_QUERY

Поскольку массивы примитивных значений являются допустимыми JSON, кажется странным, что средство выбора массивов примитивных значений не встроено в функциональность JSON SQL Server. (Если, наоборот, такая функциональность существует, я, по крайней мере, не смог обнаружить ее после долгих поисков).

Подход, изложенный выше, работает как описано. Но когда применяется к полю в большем запросе, массив примитивов окружен кавычками.

Например, это

DECLARE @BomTable TABLE (ChildNumber dbo.udt_ConMetPartNumber);
INSERT INTO @BomTable (ChildNumber) VALUES (N'101026'), (N'101027');
SELECT N'"Children": ' + REPLACE(REPLACE((SELECT ChildNumber FROM @BomTable FOR JSON PATH), N'{"ChildNumber":', N''), '"}','');

работает, производя:

"Children": ["101026,"101027]

Но, следуя подходу выше, это:

SELECT
    p.PartNumber,
    p.Description,
    REPLACE(REPLACE((SELECT
                        ChildNumber
                     FROM
                        Part.BillOfMaterials
                     WHERE
                        ParentNumber = p.PartNumber
                     ORDER BY
                        ChildNumber
                    FOR
                     JSON AUTO
                    ), N'{"ChildNumber":', N''), '"}', '"') AS [Children]
FROM
    Part.Parts AS p
WHERE
    p.PartNumber = N'104444'
FOR
    JSON PATH

Производит:

[
    {
        "PartNumber": "104444",
        "Description": "ASSY HUB           R-SER  DRIV HP10  ABS",
        "Children": "[\"101026\",\"101027\",\"102291\",\"103430\",\"103705\",\"104103\"]"
    }
]

Где массив Children обернут как строка.

Эта версия (основанная на других):

  • правильно экранирует специальные символы JSON (например, кавычки)
  • возвращает пустой массив [] без данных

Требуется SQL 2016 или новее (из-за STRING_ESCAPE):

SELECT 
   CONCAT('[', '"' +
        (SELECT STRING_AGG(STRING_ESCAPE(item_id, 'json'), '","') 
         FROM #temp) 
    + '"', ']')

Большинство из этих решений по существу создают CSV, который представляет содержимое массива, и затем помещают этот CSV в окончательный формат JSON. Вот что я использую, чтобы избежать XML:

DECLARE @tmp NVARCHAR(MAX) = ''

SELECT @tmp = @tmp + '"' + [item_id] + '",'
FROM #temp -- Defined and populated in the original question

SELECT [ids] = JSON_QUERY((
    SELECT CASE
        WHEN @tmp IS NULL THEN '[]'
        ELSE '[' + SUBSTRING(@tmp, 0, LEN(@tmp)) + ']'
        END
    ))
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER

Мне нравится ответ @massther для SQL Server 2017 и более поздних версий. Однако результирующий JSON заключен в массив. Чтобы избавиться от массива, используйтеWITHOUT_ARRAY_WRAPPERвариант вFOR JSONпункт.

Кроме того, как кто-то упомянул в комментариях,QUOTENAME()функция вызывает проблемы, если какие-либо данные содержат закрывающую квадратную скобку,].

Ниже представлена ​​оригинальная версия Massther и модифицированная версия с этими изменениями.

      declare @temp table (item_id VARCHAR(256))

INSERT INTO @temp VALUES ('1234'),('5678'),('7890'),('[problem]')

SELECT * FROM @temp

--convert to JSON

-- Original version:
select 
    json_query(QUOTENAME(STRING_AGG('"' + STRING_ESCAPE(item_id, 'json') + '"', char(44)))) 
        as [json]
from @temp
for json path

-- Modified version: 
--    Replaced QUOTENAME() with '[' + ... + ']'
--    Replaced char(44) as the separator character with ','
--    Added WITHOUT_ARRAY_WRAPPER option.
select 
    json_query('[' + STRING_AGG('"' + STRING_ESCAPE(item_id, 'json') + '"', ',') + ']') 
        as [json]
from @temp
for json path, WITHOUT_ARRAY_WRAPPER;

Полученные результаты:

Оригинальная версия:

Обратите внимание, что это массив JSON, а не объект JSON, и двойное «]]» после текста «проблемы».

      [
    {
        "json": [
            "1234",
            "5678",
            "7890",
            "[problem]]"
        ]
    }
]

Модифицированная версия:

Объект JSON, а не массив JSON, и закрывающий "]" после текста "проблемы" обрабатывается правильно.

      {
    "json": [
        "1234",
        "5678",
        "7890",
        "[problem]"
    ]
}

Вот дикая идея, которая может быть или не быть практичной. Рекурсивно перебирайте свой набор данных и добавляйте вещи в свои массивы JSON, используя JSON_MODIFY:

      with
  d (d) as (select * from (values (1),(2),(3),(4)) t (d)),
  j (d, j) as (
    -- Adapt the recursion to make it dynamic
    select 1, json_modify('[]', 'append $', d)
    from d
    where d = 1
    union all
    select d.d, json_modify(j, 'append $', d.d)
    from d join j on d.d = j.d + 1
  )
select * 
from j;

Я сделал это простым для иллюстрации. Конечно, вы адаптируете его, чтобы сделать его динамичным. Это производит:

      |d  |j        |
|---|---------|
|1  |[1]      |
|2  |[1,2]    |
|3  |[1,2,3]  |
|4  |[1,2,3,4]|

Может даже использоваться для эмуляции стандартного SQL JSON_ARRAYAGG

Я думаю, что в SQL Server 2017 было бы проще:

      select
 JSON_QUERY
 (
     '["' + STRING_AGG(t.item_id,'","') + '"]'
 ) as ids
 from #temp t
 for json auto, without_array_wrapper
Другие вопросы по тегам