Tenha paciência comigo - o código de exemplo está acontecendo e farei o meu melhor para explicá-lo abaixo.
with ENTITIES as (
select [name] as entityName from (values ('A'), ('B'), ('C')) X([name])
),
PROPERTIES as (
select [propName] as entityName, [propValue] as propertyValue from (values ('A','1'),('A','2'),('B','3'),('UNIVERSAL','999')) Y([propName], [propValue])
),
SUBPROPERTIES as (
select [propValue] as propertyValue, [subPropValue] as subPropertyValue from (values ('1','x'),('1','y'),('2','z'),('999','xyz')) Y([propValue], [subPropValue])
)
--select * from ENTITIES
--select * from PROPERTIES
select (
select entityName as 'entityName',
(
select * from
(
select propertyValue as 'propertyValue',
(
select subPropertyValue from SUBPROPERTIES where SUBPROPERTIES.propertyValue = PROPERTIES.propertyValue
FOR JSON PATH, INCLUDE_NULL_VALUES
) Y
from PROPERTIES where PROPERTIES.entityName = ENTITIES.entityName
-- For testing, comment out from here -------------------------------------------------------------------------------------
UNION
select propertyValue as 'propertyValue',
(
select subPropertyValue from SUBPROPERTIES where SUBPROPERTIES.propertyValue = PROPERTIES.propertyValue
FOR JSON PATH, INCLUDE_NULL_VALUES
) Y
from PROPERTIES where PROPERTIES.entityName = 'UNIVERSAL'
-- For testing, stop commenting out here ----------------------------------------------------------------------------------
) X
FOR JSON PATH, INCLUDE_NULL_VALUES
) as entityProperties
from ENTITIES
FOR JSON PATH, INCLUDE_NULL_VALUES
) jsondata
Configurei 3 CTEs - representando 3 tabelas que se conectam. Um registro de ENTIDADES (como 'A') pode ter várias PROPRIEDADES (como '1' e '2') e cada registro de PROPRIEDADES pode ter várias SUBPROPRIEDADES (como '1' tem subpropriedades 'x' e 'y').
Agora, a consulta principal está simplesmente tentando construir um objeto JSON que retornará todas as ENTITIES juntamente com todas as suas PROPRIEDADES vinculadas e as SUBPROPRIEDADES vinculadas.
Portanto, a primeira coluna de seleção fornece apenas o entityName - uma consulta de nível superior.
A próxima coluna é uma subconsulta para obter todos os valores PROPERTIES relacionados ao registro ENTITIES fornecido. Você verá que retorna algo com o alias 'propertyValue' - é claro que isso será um conjunto de valores - um valor para cada registro PROPERTIES vinculado ao registro ENTITIES atual.
A subconsulta possui uma segunda coluna que é outra subconsulta - para a tabela SUBPROPERTIES - semelhante à anterior.
Agora aqui está o problema - neste nível médio (relatando PROPRIEDADES), eu realmente quero obter os dados de uma UNIÃO.
(Neste exemplo, a segunda consulta na união está consultando a mesma tabela de origem - para evitar uma união alterando a cláusula WHERE para ter or PROPERTIES.entityName = 'UNIVERSAL'
- mas no mundo real estou obtendo dados de duas tabelas diferentes, então quero agregado com uma UNIÃO).
Eu entendo que, ao usar FOR XML PATH
com um UNION, você precisa envolver todos os SELECTs de UNION-ed entre parênteses e fornecer um alias (X, neste exemplo) e, em seguida, SELECT a partir disso - o que eu fiz com select * from
. (Ignore isso select *
é uma má prática - esse não é o ponto).
Essa abordagem para UNIONs funciona bem - desde que as consultas que foram UNION-ed não contenham subconsultas - mas como você pode ver aqui, eu tenho subconsultas.
Então - para ver o que estou tentando ilustrar, se você comentar o UNION (e o segundo select), você obterá um JSON bem estruturado conforme a ilustração a seguir . Eu destaquei onde as SUBPROPRIEDADES são mostradas - corretamente - em JSON estruturado.
No entanto, quando adiciono UNION, essas subconsultas retornam strings, em vez de JSON , conforme ilustração abaixo. Eu destaquei onde estão as cordas.
Eu tentei várias abordagens para adicionar uma função JSON_QUERY (que é recomendada em alguns casos para analisar uma string como JSON), mas a) não conseguiu resolver o problema e b) não há necessidade real para exatamente os mesmos dados quando há não é UNIÃO.
Também tentei várias abordagens para select * from
as subconsultas de nível mais baixo - sem sucesso.
Desde já, obrigado.
O problema parece ser que o SQL está tratando o pseudocampo do SELECT interno como texto em vez de JSON quando ele foi unido. Quando o FOR JSON externo é aplicado a esse pseudocampo, ele tenta escapar dos caracteres especiais no campo de texto. Veja este link para mais informações.
Você pode negar isso usando a função JSON_QUERY , essencialmente forçando o SQL Server a tratar o campo JSON como JSON em vez de texto. Veja este violino para um exemplo de trabalho.
Consulta (observe o uso de JSON_QUERY duas vezes, uma para cada SELECT interno):
Resultados