2017-08-28 104 views
3

我试图编写一个相当复杂的SQL查询来生成JSON。除了一些硬编码数组,我需要在层次结构中有更深层次的东西,我必须使用UNION ALL来创建。我已经产生了查询,这里显示了我的问题(没有数据是必需的 - 我在Azure的SQL数据库上运行此):如何避免FOR JSON PATH逃离查询结果?

SELECT 
    'Hi' AS Greeting, 
    (
     SELECT 
      CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN 
       'qwerty' 
      ELSE 
       'asdf' 
      END AS Stuff 
     FOR JSON PATH 
    ) AS StuffArray, 
    (
     CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN 
     (
      SELECT 'qwerty' AS Stuff 
      FOR JSON PATH 
     ) 
     ELSE 
     (
      SELECT 'asdf' AS Stuff 
      FOR JSON PATH 
     ) 
     END 
    ) AS QuotedStuffArray, 
    (
     CASE WHEN DatePart(second, GetDate()) % 2 = 1 THEN 
     (
      SELECT * FROM 
      (
       SELECT 'qwerty' AS Stuff 
       UNION ALL 
       SELECT 'zxcvb' AS Stuff 
      ) AS SubSelect 
      FOR JSON PATH 
     ) 
     ELSE 
     (
      SELECT 'asdf' AS Stuff 
      FOR JSON PATH 
     ) 
     END 
    ) AS WhyItMatters, 
    (
     SELECT * FROM 
     (
      SELECT 'qwerty' AS Stuff 
      UNION ALL 
      SELECT 'zxcvb' AS Stuff 
     ) AS SubSelect 
     FOR JSON PATH 
    ) AS ButThisIsFine 
FOR JSON PATH 

这个输出该JSON:

[ 
    { 
     "Greeting": "Hi", 
     "StuffArray": [ 
      { 
       "Stuff": "qwerty" 
      } 
     ], 
     "QuotedStuffArray": "[{\"Stuff\":\"qwerty\"}]", 
     "WhyItMatters": "[{\"Stuff\":\"qwerty\"},{\"Stuff\":\"zxcvb\"}]", 
     "ButThisIsFine": [ 
      { 
       "Stuff": "qwerty" 
      }, 
      { 
       "Stuff": "zxcvb" 
      } 
     ] 
    } 
] 

在此查询,您会在基础对象之外的层次结构中看到四个不同的对象:StuffArray,QuotedStuffArray,WhyItMattersButThisIsFineStuffArray对象正是我希望我的所有对象看起来像 - 纯JSON没有任何转义。但是,当我开始将SELECT语法放入我的CASE语句中时,我的结果开始引用,如QuotedStuffArray对象所示。所以对于前两个对象,这很好。但我有一个问题,我有时需要执行两个硬编码值的条件UNION,这迫使我将SELECT放入CASE语句中,如WhyItMatters对象所示。 ButThisIsFine对象产生格式化的输出,就像我想WhyItMatters对象被格式化,但它删除了我需要的条件UNION

我怎样才能得到这最后WhyItMatters对象产生纯粹的JSON没有像ButThisIsFine对象逃脱报价,同时保持在条件UNION陈述?

+1

我的眼睛正在流血,但在'SELECT'(而不是简单的圆括号)周围添加了'JSON_QUERY()'来达到你想要的效果吗? –

+0

@JeroenMostert是的!请提供该答案作为答案,我会接受它。比我作为可能的解决方案发布的要好得多。谢谢! – Jaxidian

+1

@Jaxidian但是你的解决方案可能有更好的性能?你可以测试吗? –

回答

6

优化器中对于此查询有一些迷人的行为,我不确定它是否是一个错误。下面的查询将添加逃避:

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 

CASE可以被优化掉,这是优化走,而最终的结果是很好的嵌套JSON。但是,如果我们去掉优化东西拿走的能力,它沦为粘贴在一个转义字符串:

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 

似乎不合逻辑一个查询将导致处理输入JSON和其他的不会,但你去那里。JSON是不是在T-SQL(不像XML)的实际类型,所以我们不能CASTCONVERT,但JSON_QUERY会做大致相同的事情:

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 

请注意,这也适用,如果参数已JSON(在常量情况下),所以无论添加如何都是安全的。这两种解决方案的

+0

感谢这个答案 - 这是我正在使用的解决方案。我特别着迷于你的例子,其中优化器事实上改变了输出的格式,只是因为布尔逻辑优化发生。这确实看起来像一个错误。谢谢大家指出,除了给我我正在寻找的答案! – Jaxidian

+0

仅供参考,我已经为此提交了一个Connect bug:https://connect.microsoft.com/SQLServer/feedback/details/3140057 – Jaxidian

2

我发现了一种可能的解决方案,但我真的不喜欢它。我发布了我希望有人有更好的解决方案。

在我的UNION的每个分支上使用WHERE声明,对CASE声明的肯定或否定都可以阻止对结果进行“细化”处理。

例如,下面的查询:

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 

提供了以下的结果:

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

如果没有更好的可以发现,我可以向前这一招。但是,这似乎是一个难以控制的方法。

+1

我收到了相同的解决方案。我测试过它的性能:与通过'JSON_QUERY'的解决方案相比,它提供了两倍的更好的时间 –

1

我已经测试peformance:

第一 - 通过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 

给我平均执行时间91ms。

二:

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 

给我平均执行时间45ms。

table1包含12727行。生成的JSON长度约为1500000个字符。

+1

这是很好的信息,谢谢!这对于一些人来说是一个很好的优化。不过,就我的情况而言,我正在从字面上说我是'UNION'的一些硬编码值。所以我绝对不关心优化硬编码子查询命中** no **表 - 而是我正在优化代码可维护性。因此,我想不必重复(并保持重复的)布尔逻辑,这是驱动我是否想要'UNION'多个结果。但对于其他人来说,这种表现可能至关重要! – Jaxidian