Оптимальный подход к разворачиванию полей данных JSON - PullRequest
1 голос
/ 24 октября 2019

Я работаю с базой данных, чтобы преобразовать столбец сохраненных необработанных данных в формате JSON в формат со следующими столбцами.

UserId | QuestionText | AnswerText | LoadDate

Данные JSON следуют согласованной структуре, начиная с общего набораобщих полей, прежде чем конкретные формы ввода перечислены в массиве. Каждая запись в таблице содержит одну из этих необработанных строк JSON. Несмотря на то, что формы ввода перечислены в виде массива, для одной строки таблицы будет выбрана только одна (не более). Эти данные хранятся в базе данных SQL Server 2016, а некоторые поля в данных JSON сами являются объектами или массивами. Может быть любое количество дочерних элементов одного из этих объектов или массивов, которые сами являются объектами или массивами и т. Д.

Данные JSON возвращаются с использованием CROSS APPLY OPENJSON или OUTER APPLY OPENJSON вместе с WITHключевое слово для указания полей, которые должны быть возвращены с соответствующими типами данных. Все запросы JSON выполняются с lax, а не с strict.

Результирующая общая оболочка запроса выглядит примерно так:

SELECT
    formData.UserId,
    interests.Weekends,
    interests.Holidays,
    jdt.LoadDate
FROM
    dbo.JsonDataTable jdt
CROSS APPLY OPENJSON(jdt.JsonField, 'lax $')
WITH
(
    [Source] VARCHAR(15) '$."source"',
    [UserId] VARCHAR(25) '$."UserId"',
    [InterestsJson] NVARCHAR(MAX) '$."Interests"' AS JSON
) formData
OUTER APPLY OPENJSON(formData.InterestsJson, 'lax $')
WITH
(
    [Weekends] VARCHAR(200) '$."weekends"',
    [Holidays] VARCHAR(200) '$."holidays"'
) interests
WHERE
    jdt.JsonField IS NOT NULL
    AND jdt.JsonField <> ''
    AND jdt.FormId = @FormId
    AND jdt.FormId = formData.source
    AND jdt.LoadDate BETWEEN @StartDate AND @EndDate;

Этот шаблон продолжается до тех пор, пока все дочерние массивыи объекты разрешаются в соответствующие поля, и каждое из этих объединений OPENJSON разбивает результирующий набор на количество записей, хранящихся в каждом массиве / объекте. Очевидно, что это добавляет много накладных расходов, так как число строк увеличивается с каждым дополнительным массивом, добавляемым в запрос.

Как только эти данные завершены, набор результатов помещается в операцию UNPIVOT.

SELECT
    u.UserId,
    u.QuestionText,
    u.AnswerText,
    u.LoadDate
FROM
(
    SELECT
        formData.UserId,
        interests.Weekends,
        interests.Holidays,
        jdt.LoadDate
    FROM
        dbo.JsonDataTable jdt
    CROSS APPLY OPENJSON(jdt.JsonField, 'lax $')
    WITH
    (
        [Source] VARCHAR(15) '$."source"',
        [UserId] VARCHAR(25) '$."UserId"',
        [InterestsJson] NVARCHAR(MAX) '$."Interests"' AS JSON
    ) formData
    OUTER APPLY OPENJSON(formData.InterestsJson, 'lax $')
    WITH
    (
        [Weekends] VARCHAR(200) '$."weekends"',
        [Holidays] VARCHAR(200) '$."holidays"'
    ) interests
    WHERE
        jdt.JsonField IS NOT NULL
        AND jdt.JsonField <> ''
        AND jdt.FormId = @FormId
        AND jdt.FormId = formData.source
        AND jdt.LoadDate BETWEEN @StartDate AND @EndDate
) q
UNPIVOT
(
    AnswerText
    FOR QuestionText IN
    (
        Weekends,
        Holidays
    )
) u;

Это кажется очень медленным для обработки больших объемов необработанных данных JSON, хранящихся в виде поля VARCHAR (MAX) в таблице. Поля, которые фильтруются в предложении WHERE, индексируются, и индексы обрабатываются, как и ожидалось. Для одной из наиболее часто используемых форм ввода требуется около 3 минут, чтобы вернуть 15 минут данных.

ВОПРОС

Есть ли лучший подход к этому? Данные должны быть в этом формате вывода, и формат ввода не может быть изменен из необработанных строк JSON, выгруженных в столбец, который должен быть отфильтрован только для конкретной запрашиваемой формы ввода. Сами строки таблицы не могут быть отфильтрованы вне строки JSON, чтобы возвращать только данные JSON одной формы ввода. По какой-то причине эти данные были настроены таким образом, что каждая запись представляет собой совокупность всего, что было отправлено для этого пользователя, а не каждой формы ввода, отображаемой в виде отдельной строки. Я подсчитал, что для обратной загрузки данных для одной из более крупных форм ввода потребуется более 14 часов при таком подходе, и мне нужно загрузить примерно 250 форм ввода.

...