How do I keep FOR JSON PATH from escaping query results?

前端 未结 3 1276
小鲜肉
小鲜肉 2021-01-12 08:28

I\'m trying to write a fairly complicated SQL Query that produces JSON as the result. All is working great except for some hardcoded arrays I need to have deeper in the hier

相关标签:
3条回答
  • 2021-01-12 08:48

    I have tested peformance of both solutions:

    first - via JSON_QUERY:

    declare @i int = 1;
    SELECT
        JSON_QUERY(
            CASE when @i = 1 THEN
            (
                SELECT * FROM
                (
                    select textCol AS Stuff from table1 where id % 2 = 0 
                    UNION ALL
                    SELECT textCol AS Stuff from table1 where id % 2 <> 0
                ) AS SubSelect
                FOR JSON PATH
            )
            ELSE
            (
                SELECT textCol AS Stuff from table1
                FOR JSON PATH
            )
            END
        ) AS WhyItMatters
    FOR JSON path
    

    gives me average execution time 91ms.

    Second:

    declare @i int = 1;
    SELECT
        (SELECT * FROM
            (
                select textCol AS Stuff from table1 where id % 2 = 0 and @i = 1
                UNION ALL
                SELECT textCol AS Stuff from table1 where id % 2 <> 0 and @i = 1
                union all
                SELECT textCol AS Stuff from table1 where @i <> 1
            ) AS SubSelect
            FOR JSON PATH
        ) AS WhyItMatters
    FOR JSON path
    

    gives me average execution time 45ms.

    table1 contains 12727 rows. Resulting JSON has length about 1500000 characters.

    0 讨论(0)
  • 2021-01-12 08:50

    There's some fascinating behavior going on in the optimizer for this query, and I'm not sure if it's a bug. The following query will not add escaping:

    SELECT
        'Hi' AS Greeting,
        (
            CASE WHEN 1 = 1 THEN (
                SELECT * FROM (
                    SELECT 'qwerty' AS [Stuff]
                    UNION ALL
                    SELECT 'zxcvb' AS [Stuff]
                ) _
                FOR JSON PATH
            ) ELSE (
                SELECT 'asdf' AS [Stuff]
                FOR JSON PATH
            )
            END
        ) AS WhyItMatters
    FOR JSON PATH
    

    The CASE can be optimized away, and it is optimized away, and the end result is nicely nested JSON. But if we remove the ability to optimize things away, it degenerates into pasting in an escaped string:

    SELECT
        'Hi' AS Greeting,
        (
            CASE WHEN RAND() = 1 THEN (
                SELECT * FROM (
                    SELECT 'qwerty' AS [Stuff]
                    UNION ALL
                    SELECT 'zxcvb' AS [Stuff]
                ) _
                FOR JSON PATH
            ) ELSE (
                SELECT 'asdf' AS [Stuff]
                FOR JSON PATH
            )
            END
        ) AS WhyItMatters
    FOR JSON PATH
    

    It seems illogical that one query would result in processing typed JSON and the other would not, but there you go. JSON is not an actual type in T-SQL (unlike XML), so we can't CAST or CONVERT, but JSON_QUERY will do roughly the same thing:

    SELECT
        'Hi' AS Greeting,
        JSON_QUERY(
            CASE WHEN RAND() = 1 THEN (
                SELECT * FROM (
                    SELECT 'qwerty' AS [Stuff]
                    UNION ALL
                    SELECT 'zxcvb' AS [Stuff]
                ) _
                FOR JSON PATH
            ) ELSE (
                SELECT 'asdf' AS [Stuff]
                FOR JSON PATH
            )
            END
        ) AS WhyItMatters
    FOR JSON PATH
    

    Note that this also works if the argument is already JSON (in the constant case), so it's safe to add regardless.

    0 讨论(0)
  • 2021-01-12 09:07

    I have found one possible solution but I really don't like it. I'm posting what I have in hopes that somebody has a better solution.

    Using a WHERE statement on every branch of my UNION with either the affirmative or exact negative of my CASE statement can prevent the "strigifying" of my results.

    For example, this query:

    SELECT
        'Hi' AS Greeting,
        (
            SELECT * FROM
            (
                SELECT 'asdf' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 0
                UNION ALL
                SELECT 'qwerty' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 1
                UNION ALL
                SELECT 'zxcvb' AS Stuff WHERE DatePart(second, GetDate()) % 2 = 1
            ) AS SubSelect
            FOR JSON PATH
        ) AS Try1
    FOR JSON PATH
    

    provides these results:

    [
        {
            "Greeting": "Hi",
            "Try1": [
                {
                    "Stuff": "qwerty"
                },
                {
                    "Stuff": "zxcvb"
                }
            ]
        }
    ]
    

    If nothing better can be found, I can move forward with this. But this seems like a hacky way to control this.

    0 讨论(0)
提交回复
热议问题