Синтаксический анализ T-SQL JSON, содержащий динамический массив значений - PullRequest
1 голос
/ 09 ноября 2019

В T-SQL, при синтаксическом анализе JSON и динамическом обращении с массивами, кажется очевидным, что нужно использовать функцию табличных значений OPENJSON, чтобы учесть тот факт, что нет сведений о доступных индексах. Если этот массив является простым массивом значений:

SELECT * FROM OPENJSON(N'["element1","element2","element3"]')

, он рендерится с элементом на строку, как и следовало ожидать;идеально подходит для подсчета строк и повторения значений. Однако затем мне нужно выполнить эту операцию в обратном порядке и построить массив значений. Чтобы проверить, я думал, что что-то вроде этого может работать:

SELECT RTRIM([value]) FROM OPENJSON(N'["element1","element2","element3"]') ORDER BY [key] FOR JSON PATH

, но я получаю ошибку:

Сообщение 13605, Уровень 16, Состояние 1, Строка 8 Выражения столбцов и источники данныхбез имен или псевдонимов нельзя форматировать как текст JSON, используя предложение FOR JSON. Добавьте псевдоним в безымянный столбец или таблицу.

Я пробовал разбираться с различными формами функции OPENJSON и функции JSON_QUERY, но безрезультатно. В конце концов я пришел к этому «решению»:

SELECT REPLACE(REPLACE(REPLACE(CATEND,'},{"V":',','),'[{"V":','['),'}]',']')FROM(SELECT(
SELECT [value]AS V FROM OPENJSON(N'["element1","element2","element3"]')ORDER BY [key] FOR JSON PATH)AS CATEND)T

, но мне это не подходит. Моя цель и цель использования библиотечных функций JSON - избежать синтаксического анализа текста, и необходимость включать дорогостоящую функцию REPLACE ощущается так, как будто в этом нет необходимости. Я что-то упустил?!

1 Ответ

1 голос
/ 10 ноября 2019

К сожалению, разработчики забыли добавить что-то вроде AS ARRAY подсказка.

OPENJSON() может прочитать голый массив , где [key] - это позиция элемента, а [value] это элемент, но мы не можем создать такой массив с функциональностью JSON:

Есть несколько обходных путей:

- Создайте макет для имитации вашей проблемы:

DECLARE @tbl1 TABLE(ID INT IDENTITY,SomeValue VARCHAR(100));
INSERT INTO @tbl1 VALUES('Row 1'),('Row 2');

DECLARE @tbl2 TABLE(ID1 INT,SomeDetail VARCHAR(100));
INSERT INTO @tbl2 VALUES(1,'Det 1.1'),(1,'Det 1.2')
                       ,(2,'Det 2.1'),(2,'Det 2.2.'),(2,'Det 2.3');

- Использование режима AUTO обнаружит JOIN и создаст массив объектов.
- Это близко, но вы не получите безымянный массив:

SELECT *
FROM @tbl1 t1
INNER JOIN @tbl2 t2 ON t1.ID=t2.ID1
FOR JSON AUTO;

/*
    {
        "ID": 1,
        "SomeValue": "Row 1",
        "t2": [
            {
                "ID1": 1,
                "SomeDetail": "Det 1.1"
            },
            {
                "ID1": 1,
                "SomeDetail": "Det 1.2"
            }
        ]
    }
*/

- Использование режима PATH вернет каждую комбинацию - не то, что вам нужно

SELECT *
FROM @tbl1 t1
INNER JOIN @tbl2 t2 ON t1.ID=t2.ID1
FOR JSON PATH;

/*
    {
        "ID": 1,
        "SomeValue": "Row 1",
        "ID1": 1,
        "SomeDetail": "Det 1.1"
    }
*/

- Использование коррелированного подзапроса приведет к тому жерезультат в режиме AUTO выше

SELECT *
      ,(
        SELECT *
        FROM @tbl2 t2 
        WHERE t2.ID1=t1.ID --<-- correlated sub-query 
        FOR JSON PATH
       ) AS Details
FROM @tbl1 t1
FOR JSON PATH;

- Таким образом, мы можем использовать коррелированный подзапрос вместе с XML-хаком для конкатенации строк:

SELECT *
      ,JSON_QUERY
      (
           CONCAT('['
          ,STUFF((
            SELECT CONCAT(',"',t2.SomeDetail,'"') 
            FROM @tbl2 t2 
            WHERE t2.ID1=t1.ID --<-- correlated sub-query 
            FOR XML PATH(''),TYPE).value('.','nvarchar(max)'),1,1,''),']')
      ) AS Details
FROM @tbl1 t1
FOR JSON PATH;

- Наконец, это то, что вы хотите:

/*
    {
        "ID": 1,
        "SomeValue": "Row 1",
        "Details": [
            "Det 1.1",
            "Det 1.2"
        ]
    }
*/

Если у вас v2017 +, вам повезло, потому что вы можете использовать STRING_AGG():

SELECT t1.ID
      ,JSON_QUERY(CONCAT('[',STRING_AGG(CONCAT('"',t2.SomeDetail,'"'),','),']')) AS Details
FROM @tbl1 t1
INNER JOIN @tbl2 t2 ON t1.ID=t2.ID1
GROUP BY t1.ID
FOR JSON PATH;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...