O SQL Server tem uma função com valor de tabela chamada
OPENJSON()
que cria uma visualização relacional de dados JSON. Ao chamá-lo, você passa um documento JSON como argumento e
OPENJSON()
em seguida, analisa e retorna os objetos e propriedades do documento JSON em um formato tabular – como linhas e colunas. Exemplo
Aqui está um exemplo simples para demonstrar.
SELECT * FROM OPENJSON('["Cat","Dog","Bird"]');
Resultado:
+-------+---------+--------+ | key | value | type | |-------+---------+--------| | 0 | Cat | 1 | | 1 | Dog | 1 | | 2 | Bird | 1 | +-------+---------+--------+
Por padrão,
OPENJSON()
retorna uma tabela com três colunas; chave , valor e tipo . Você também tem a opção de especificar seu próprio esquema (o que significa que você pode definir suas próprias colunas). No meu exemplo simples, usei o esquema padrão e, portanto, as três colunas padrão foram retornadas.
Essas colunas são definidas da seguinte forma:
Coluna | Descrição |
---|---|
chave | Contém o nome da propriedade especificada ou o índice do elemento na matriz especificada. Este é um nvarchar(4000) valor e a coluna tem um agrupamento BIN2. |
valor | Contém o valor da propriedade. Este é um nvarchar(max) valor e a coluna herda seu agrupamento do JSON fornecido. |
tipo | Contém o tipo JSON do valor. Isso é representado como um int valor (de 0 para 5 ). Esta coluna só é retornada quando você usa o esquema padrão. |
Tipos padrão
No mundo do JSON, existem seis tipos de dados. Estes são string , número , verdadeiro/falso (booleano), nulo , objeto e matriz .
Quando você analisa algum JSON por meio de
OPENJSON()
usando o esquema padrão, OPENJSON()
descobre qual é o tipo JSON e, em seguida, preenche o tipo coluna com um int valor que representa esse tipo. O int o valor pode, portanto, variar de
0
para 5
. Cada int value representa um tipo JSON conforme descrito na tabela a seguir. Valor na coluna "tipo" | Tipo de dados JSON |
---|---|
0 | nulo |
1 | cadeia |
2 | número |
3 | verdadeiro/falso |
4 | matriz |
5 | objeto |
O exemplo a seguir retorna todos os seis tipos JSON.
SELECT * FROM OPENJSON('{"name" : null}');
SELECT * FROM OPENJSON('["Cat","Dog","Bird"]');
SELECT * FROM OPENJSON('[1,2,3]');
SELECT * FROM OPENJSON('[true,false]');
SELECT * FROM OPENJSON('{"cats":[{ "id":1, "name":"Fluffy"},{ "id":2, "name":"Scratch"}]}');
SELECT * FROM OPENJSON('[{"A":1,"B":0,"C":1}]');
Resultado:
+-------+---------+--------+ | key | value | type | |-------+---------+--------| | name | NULL | 0 | +-------+---------+--------+ (1 row affected) +-------+---------+--------+ | key | value | type | |-------+---------+--------| | 0 | Cat | 1 | | 1 | Dog | 1 | | 2 | Bird | 1 | +-------+---------+--------+ (3 rows affected) +-------+---------+--------+ | key | value | type | |-------+---------+--------| | 0 | 1 | 2 | | 1 | 2 | 2 | | 2 | 3 | 2 | +-------+---------+--------+ (3 rows affected) +-------+---------+--------+ | key | value | type | |-------+---------+--------| | 0 | true | 3 | | 1 | false | 3 | +-------+---------+--------+ (2 rows affected) +-------+----------------------------------------------------------+--------+ | key | value | type | |-------+----------------------------------------------------------+--------| | cats | [{ "id":1, "name":"Fluffy"},{ "id":2, "name":"Scratch"}] | 4 | +-------+----------------------------------------------------------+--------+ (1 row affected) +-------+---------------------+--------+ | key | value | type | |-------+---------------------+--------| | 0 | {"A":1,"B":0,"C":1} | 5 | +-------+---------------------+--------+ (1 row affected)
Retornar JSON aninhado
Você pode retornar um objeto ou array aninhado especificando seu caminho como um segundo argumento opcional.
Em outras palavras, você não precisa analisar todo o documento JSON – você pode optar por analisar apenas a parte de seu interesse.
Aqui está um exemplo.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT * FROM OPENJSON(@json, '$.pets.cats');
Resultado:
+-------+------------------------------------------------------+--------+ | key | value | type | |-------+------------------------------------------------------+--------| | 0 | { "id" : 1, "name" : "Fluffy", "sex" : "Female" } | 5 | | 1 | { "id" : 2, "name" : "Long Tail", "sex" : "Female" } | 5 | | 2 | { "id" : 3, "name" : "Scratch", "sex" : "Male" } | 5 | +-------+------------------------------------------------------+--------+
Nesse caso, especifiquei um caminho de
$.pets.cats
, que resultou apenas no valor de gatos sendo devolvido. O valor de gatos é um array, então todo o array foi retornado. Para retornar apenas um cat (ou seja, um elemento de array), podemos usar a sintaxe de colchetes para retornar valores de array (como este
$.pets.cats[1]
). Aqui está o mesmo exemplo modificado para retornar apenas um elemento de array:
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT * FROM OPENJSON(@json, '$.pets.cats[1]');
Resultado:
+-------+-----------+--------+ | key | value | type | |-------+-----------+--------| | id | 2 | 2 | | name | Long Tail | 1 | | sex | Female | 1 | +-------+-----------+--------+
Índices de matriz JSON são baseados em zero, então este exemplo retornou o segundo valor de matriz (porque eu especifiquei
$.pets.cats[1]
). Se eu tivesse especificado
$.pets.cats[0]
, o primeiro valor teria sido retornado (ou seja, o gato chamado “Fluffy”). Definir um esquema
Conforme mencionado, você pode especificar seu próprio esquema (ou seja, definir suas próprias colunas e tipos).
Aqui está um exemplo de como fazer isso.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT * FROM OPENJSON(@json, '$.pets.cats')
WITH (
[Cat Id] int '$.id',
[Cat Name] varchar(60) '$.name',
[Sex] varchar(6) '$.sex',
[Cats] nvarchar(max) '$' AS JSON
);
Resultado:
+----------+------------+--------+------------------------------------------------------+ | Cat Id | Cat Name | Sex | Cats | |----------+------------+--------+------------------------------------------------------| | 1 | Fluffy | Female | { "id" : 1, "name" : "Fluffy", "sex" : "Female" } | | 2 | Long Tail | Female | { "id" : 2, "name" : "Long Tail", "sex" : "Female" } | | 3 | Scratch | Male | { "id" : 3, "name" : "Scratch", "sex" : "Male" } | +----------+------------+--------+------------------------------------------------------+
Podemos ver que os nomes das colunas refletem aqueles que especifiquei no
WITH
cláusula. Nessa cláusula, mapeei cada chave JSON para meus próprios nomes de coluna preferidos. Também especifiquei o tipo de dados do SQL Server que desejo para cada coluna. Eu também usei
AS JSON
na última coluna para retornar essa coluna como um fragmento JSON. Quando você usa AS JSON, o tipo de dados deve ser nvarchar(max) . Verifique os tipos de dados
Podemos usar a seguinte consulta para verificar os tipos de dados de cada coluna.
Esta consulta usa o
sys.dm_exec_describe_first_result_set
visualização de gerenciamento dinâmico do sistema, que retorna metadados sobre o primeiro conjunto de resultados de uma consulta. DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT
name,
system_type_name
FROM sys.dm_exec_describe_first_result_set(
'SELECT * FROM OPENJSON(@json, ''$.pets.cats'') WITH (
[Cat Id] int ''$.id'',
[Cat Name] varchar(60) ''$.name'',
[Sex] varchar(6) ''$.sex'',
[Cats] nvarchar(max) ''$'' AS JSON
)',
null,
0
);
Resultado:
+----------+--------------------+ | name | system_type_name | |----------+--------------------| | Cat Id | int | | Cat Name | varchar(60) | | Sex | varchar(6) | | Cats | nvarchar(max) | +----------+--------------------+
Podemos ver que eles combinam perfeitamente com o meu esquema.
Observe que a chave , valor e tipo colunas não estão disponíveis quando você define seu próprio esquema. Essas colunas só estão disponíveis ao usar o esquema padrão.
Inserir o JSON analisado em uma tabela
Até agora você pode estar pensando que poderíamos facilmente inserir nosso JSON analisado em uma tabela de banco de dados.
E você estaria certo.
Já o preparamos com colunas e linhas, e até nomeamos as colunas e demos a elas tipos de dados.
Agora é hora de inseri-lo em uma tabela.
DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT * INTO JsonCats
FROM OPENJSON(@json, '$.pets.cats')
WITH (
[Cat Id] int '$.id',
[Cat Name] varchar(60) '$.name',
[Sex] varchar(6) '$.sex',
[Cats] nvarchar(max) '$' AS JSON
);
Tudo o que fiz foi adicionar
INTO JsonCats
à minha consulta, para criar uma tabela chamada JsonCats
e insira os resultados da consulta nele. Agora vamos selecionar o conteúdo dessa tabela.
SELECT * FROM JsonCats;
Resultado:
+----------+------------+--------+------------------------------------------------------+ | Cat Id | Cat Name | Sex | Cats | |----------+------------+--------+------------------------------------------------------| | 1 | Fluffy | Female | { "id" : 1, "name" : "Fluffy", "sex" : "Female" } | | 2 | Long Tail | Female | { "id" : 2, "name" : "Long Tail", "sex" : "Female" } | | 3 | Scratch | Male | { "id" : 3, "name" : "Scratch", "sex" : "Male" } | +----------+------------+--------+------------------------------------------------------+
O conteúdo é exatamente como vimos no exemplo anterior.
E só para ter certeza, agora podemos usar o
sys.column
visualização do catálogo do sistema verifique os nomes e tipos de coluna da tabela. SELECT
name AS [Column],
TYPE_NAME(system_type_id) AS [Type],
max_length
FROM sys.columns
WHERE OBJECT_ID('JsonCats') = object_id;
Resultado:
+----------+----------+--------------+ | Column | Type | max_length | |----------+----------+--------------| | Cat Id | int | 4 | | Cat Name | varchar | 60 | | Sex | varchar | 6 | | Cats | nvarchar | -1 | +----------+----------+--------------+
Mais uma vez, exatamente como especificamos.
Observe que
sys.columns
sempre retorna um max_length
de -1
quando o tipo de dados da coluna for varchar(max) , nvarchar(max) , varbinary(max) , ou xml . Especificamos nvarchar(max) e assim o valor de -1
é exatamente como esperado. Modo de caminho:relaxado vs estrito
O caminho fornecido no segundo argumento ou no
WITH
cláusula pode (opcionalmente) começar com o lax
ou strict
palavra-chave. - Em
lax
modo,OPENJSON()
não gera um erro se o objeto ou valor no caminho especificado não puder ser encontrado. Se o caminho não puder ser encontrado,OPENJSON()
retorna um conjunto de resultados vazio ou umNULL
valor. - Em
strict
modo,OPENJSON()
retorna um erro se o caminho não puder ser encontrado.
O valor padrão é
lax
, portanto, se você não especificar um modo de caminho, lax
modo será usado. Aqui estão alguns exemplos para demonstrar o que acontece com cada modo quando o caminho não pode ser encontrado.
Segundo argumento
Nos próximos dois exemplos, forneço um caminho inexistente no segundo argumento ao chamar
OPENJSON()
. O primeiro exemplo mostra o que acontece ao usar o modo lax, o segundo exemplo mostra o que acontece ao usar o modo estrito. Modo relaxado
Veja o que acontece em
lax
modo quando o caminho não pode ser encontrado. DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT * FROM OPENJSON(@json, 'lax $.pets.cows');
Resultado:
(0 rows affected)
Nenhum erro. Apenas zero resultados retornados.
Modo Estrito
Agora aqui está em
strict
modo. DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}'
SELECT * FROM OPENJSON(@json, 'strict $.pets.cows');
Resultado:
Msg 13608, Level 16, State 3, Line 15 Property cannot be found on the specified JSON path.
Como esperado, o modo estrito resultou em um erro.
Na cláusula WITH
Nos próximos dois exemplos, testamos novamente o modo lax vs modo estrito, exceto que desta vez o especificamos no
WITH
cláusula ao definir o esquema. Modo relaxado
Veja o que acontece em
lax
modo. DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.cats')
WITH (
[Cat Id] int '$.id',
[Cat Name] varchar(60) '$.name',
[Born] date 'lax $.born',
[Cats] nvarchar(max) '$' AS JSON
);
Resultado:
+----------+------------+--------+------------------------------------------------------+ | Cat Id | Cat Name | Born | Cats | |----------+------------+--------+------------------------------------------------------| | 1 | Fluffy | NULL | { "id" : 1, "name" : "Fluffy", "sex" : "Female" } | | 2 | Long Tail | NULL | { "id" : 2, "name" : "Long Tail", "sex" : "Female" } | | 3 | Scratch | NULL | { "id" : 3, "name" : "Scratch", "sex" : "Male" } | +----------+------------+--------+------------------------------------------------------+
Neste caso eu uso
'lax $.born'
porque estou tentando fazer referência a uma chave chamada born
, mas essa chave não existe no JSON. Desta vez, a coluna que não pode ser encontrada resulta em um
NULL
valor. Modo Estrito
Agora aqui está em
strict
modo. DECLARE @json NVARCHAR(4000) = N'{
"pets" : {
"cats" : [
{ "id" : 1, "name" : "Fluffy", "sex" : "Female" },
{ "id" : 2, "name" : "Long Tail", "sex" : "Female" },
{ "id" : 3, "name" : "Scratch", "sex" : "Male" }
],
"dogs" : [
{ "name" : "Fetch", "sex" : "Male" },
{ "name" : "Fluffy", "sex" : "Male" },
{ "name" : "Wag", "sex" : "Female" }
]
}
}';
SELECT *
FROM OPENJSON(@json, '$.pets.cats')
WITH (
[Cat Id] int '$.id',
[Cat Name] varchar(60) '$.name',
[Born] date 'strict $.born',
[Cats] nvarchar(max) '$' AS JSON
);
Resultado:
Msg 13608, Level 16, State 6, Line 16 Property cannot be found on the specified JSON path.
Desta vez eu usei
'strict $.born'
. Como esperado, o modo estrito resultou em um erro.
Nível de compatibilidade
O
OPENJSON()
A função está disponível apenas no nível de compatibilidade 130 ou superior. Se o nível de compatibilidade do banco de dados for inferior a 130, o SQL Server não poderá encontrar e executar
OPENJSON()
, e você receberá um erro. Você pode verificar o nível de compatibilidade do seu banco de dados através do
sys.databases
visualização do catálogo. Você pode alterar seu nível de compatibilidade assim:
ALTER DATABASE DatabaseName
SET COMPATIBILITY_LEVEL = 150;
Novo no JSON?
Se você não está tão familiarizado com JSON, confira meu tutorial JSON no Quackit.