Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Introdução ao OPENJSON com exemplos (SQL Server)


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 um NULL 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.