取消透视JSON数据字段的最佳方法

时间:2019-10-24 19:56:15

标签: sql json sql-server database sql-server-2016

我正在与数据库合作,将存储的原始JSON格式的数据列转换为以下列的格式。

UserId | QuestionText | AnswerText | LoadDate

JSON数据遵循一致的结构,从一组通用的共享字段开始,然后在数组中列出特定的输入形式。表中的每个记录都包含这些原始JSON字符串之一。尽管输入表单被列为数组,但表中的单行最多只能选择一个。此数据存储在SQL Server 2016数据库中,并且JSON数据中的某些字段本身就是对象或数组。这些对象或数组之一可以是子对象或数组本身的任何数量的子对象,依此类推。

使用CROSS APPLY OPENJSONOUTER APPLY OPENJSON关键字与WITH关键字一起返回JSON数据,以指定需要使用适当的数据类型返回的字段。所有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;

对于处理存储为表中的VARCHAR(MAX)字段的大量原始JSON数据,这似乎非常慢。 WHERE子句中筛选的字段都已建立索引,并且按预期方式命中了索引。对于其中一种使用频率更高的输入表单,大约需要3分钟才能返回15分钟的数据。

问题

是否有更好的方法?数据必须采用此输出格式,并且输入格式不能从原始JSON字符串更改为转储到必须仅针对请求的特定输入形式进行过滤的列中。该表的行本身无法在JSON字符串之外进行过滤,仅返回单个输入表单的JSON数据。无论出于何种原因,都会设置此数据,因此每个记录都是为该用户提交的所有内容的总集合,而不是每个输入表单的提交都显示在其自己的行中。我估计,使用这种方法来回载较大的输入表单之一的数据将花费14个小时以上,而我需要回载大约250个输入表单。

0 个答案:

没有答案