sql >> Base de Datos >  >> RDS >> Sqlserver

Introducción a OPENJSON con ejemplos (SQL Server)

SQL Server tiene una función con valores de tabla llamada OPENJSON() que crea una vista relacional de datos JSON.

Cuando lo llama, pasa un documento JSON como argumento y OPENJSON() luego lo analiza y devuelve los objetos y las propiedades del documento JSON en un formato tabular, como filas y columnas.

Ejemplo

Aquí hay un ejemplo simple para demostrarlo.

SELECT * FROM OPENJSON('["Cat","Dog","Bird"]');

Resultado:

+-------+---------+--------+
| key   | value   | type   |
|-------+---------+--------|
| 0     | Cat     | 1      |
| 1     | Dog     | 1      |
| 2     | Bird    | 1      |
+-------+---------+--------+

De forma predeterminada, OPENJSON() devuelve una tabla con tres columnas; clave , valor y tipo .

También tiene la opción de especificar su propio esquema (lo que significa que puede definir sus propias columnas). En mi ejemplo simple, utilicé el esquema predeterminado y, por lo tanto, se devolvieron las tres columnas predeterminadas.

Estas columnas se definen de la siguiente manera:

Columna Descripción
clave Contiene el nombre de la propiedad especificada o el índice del elemento en la matriz especificada. Este es un nvarchar(4000) y la columna tiene una intercalación BIN2.
valor Contiene el valor de la propiedad. Este es un nvarchar(max) y la columna hereda su intercalación del JSON proporcionado.
tipo Contiene el tipo JSON del valor. Esto se representa como un int valor (desde 0 a 5 ). Esta columna solo se devuelve cuando utiliza el esquema predeterminado.

Tipos predeterminados

En el mundo de JSON, hay seis tipos de datos. Estos son cadena , número , verdadero/falso (booleano), nulo , objeto y matriz .

Cuando analiza algo de JSON a través de OPENJSON() utilizando el esquema predeterminado, OPENJSON() determina cuál es el tipo JSON y luego completa el tipo columna con un int valor que representa ese tipo.

El int por lo tanto, el valor puede oscilar entre 0 a 5 . Cada int El valor representa un tipo JSON como se describe en la siguiente tabla.

Valor en la columna "tipo" Tipo de datos JSON
0 nulo
1 cadena
2 número
3 verdadero/falso
4 matriz
5 objeto

El siguiente ejemplo devuelve los seis tipos de 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)

Devolver JSON anidado

Puede devolver un objeto anidado o una matriz especificando su ruta como un segundo argumento opcional.

En otras palabras, no tiene que analizar todo el documento JSON; puede optar por analizar solo la parte que le interesa.

He aquí un ejemplo.

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      |
+-------+------------------------------------------------------+--------+

En este caso, especifiqué una ruta de $.pets.cats , lo que dio como resultado solo el valor de gatos siendo devuelto. El valor de los gatos es una matriz, por lo que se devolvió la matriz completa.

Para devolver solo un gato (es decir, un elemento de matriz), podemos usar la sintaxis de corchetes para devolver valores de matriz (como este $.pets.cats[1] ).

Aquí está el mismo ejemplo modificado para devolver solo un elemento de matriz:

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      |
+-------+-----------+--------+

Los índices de matriz JSON están basados ​​en cero, por lo que este ejemplo devolvió el segundo valor de matriz (porque especifiqué $.pets.cats[1] ).

Si hubiera especificado $.pets.cats[0] , se habría devuelto el primer valor (es decir, el gato llamado "Fluffy").

Definir un esquema

Como se mencionó, puede especificar su propio esquema (es decir, definir sus propias columnas y tipos).

Este es un ejemplo de cómo hacerlo.

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 los nombres de las columnas reflejan los que especifiqué en el WITH cláusula. En esa cláusula, asigné cada clave JSON a mis propios nombres de columna preferidos. También especifiqué el tipo de datos de SQL Server que quiero para cada columna.

También usé AS JSON en la última columna para devolver esa columna como un fragmento JSON. Cuando usa AS JSON, el tipo de datos debe ser nvarchar(max) .

Verificar los tipos de datos

Podemos usar la siguiente consulta para verificar los tipos de datos de cada columna.

Esta consulta utiliza el sys.dm_exec_describe_first_result_set vista de gestión dinámica del sistema, que devuelve metadatos sobre el primer conjunto de resultados de una 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 coinciden perfectamente con mi esquema.

Tenga en cuenta que la clave , valor y tipo las columnas no están disponibles cuando define su propio esquema. Esas columnas solo están disponibles cuando se usa el esquema predeterminado.

Insertar el JSON analizado en una tabla

A estas alturas, podría estar pensando que podríamos insertar fácilmente nuestro JSON analizado en una tabla de base de datos.

Y tendrías razón.

Ya lo hemos preparado con columnas y filas, e incluso hemos nombrado las columnas y les hemos asignado tipos de datos.

Ahora es el momento de insertarlo en una tabla.

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   
    );

Todo lo que hice fue agregar INTO JsonCats a mi consulta, para crear una tabla llamada JsonCats e inserte los resultados de la consulta en él.

Ahora seleccionemos el contenido de esa tabla.

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" }     |
+----------+------------+--------+------------------------------------------------------+

Los contenidos son exactamente como los vimos en el ejemplo anterior.

Y para estar absolutamente seguros, ahora podemos usar sys.column vista de catálogo del sistema verifique los nombres y tipos de columnas de la tabla.

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           |
+----------+----------+--------------+

Nuevamente, exactamente como lo especificamos.

Tenga en cuenta que sys.columns siempre devuelve un max_length de -1 cuando el tipo de datos de la columna es varchar(max) , nvarchar(máximo) , varbinario(máximo) o xml . Especificamos nvarchar(max) y entonces el valor de -1 es exactamente como se esperaba.

Modo de ruta:laxo frente a estricto

La ruta provista en el segundo argumento o en el WITH La cláusula puede (opcionalmente) comenzar con lax o strict palabra clave.

  • En lax modo, OPENJSON() no genera un error si no se puede encontrar el objeto o el valor en la ruta especificada. Si no se puede encontrar la ruta, OPENJSON() devuelve un conjunto de resultados vacío o un NULL valor.
  • En strict modo, OPENJSON() devuelve un error si no se puede encontrar la ruta.

El valor predeterminado es lax , por lo que si no especifica un modo de ruta, lax se utilizará el modo.

Estos son algunos ejemplos para demostrar lo que sucede con cada modo cuando no se puede encontrar la ruta.

Segundo Argumento

En los siguientes dos ejemplos, proporciono una ruta inexistente en el segundo argumento al llamar a OPENJSON() . El primer ejemplo muestra lo que sucede cuando se usa el modo laxo, el segundo ejemplo muestra lo que sucede cuando se usa el modo estricto.

Modo relajado

Esto es lo que sucede en lax modo cuando no se puede encontrar la ruta.

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)

No hay error. Solo se devolvieron cero resultados.

Modo estricto

Ahora aquí está en 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 era de esperar, el modo estricto resultó en un error.

En la Cláusula CON

En los siguientes dos ejemplos, probamos de nuevo el modo laxo frente al modo estricto, excepto que esta vez lo especificamos en el WITH cláusula al definir el esquema.

Modo relajado

Esto es lo que sucede en 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" }     |
+----------+------------+--------+------------------------------------------------------+

En este caso uso 'lax $.born' porque estoy tratando de hacer referencia a una clave llamada born , pero dicha clave no existe en el JSON.

Esta vez, la columna que no se puede encontrar da como resultado un NULL valor.

Modo estricto

Ahora aquí está en 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.

Esta vez usé 'strict $.born' .

Como era de esperar, el modo estricto resultó en un error.

Nivel de compatibilidad

El OPENJSON() La función solo está disponible con el nivel de compatibilidad 130 o superior.

Si el nivel de compatibilidad de su base de datos es inferior a 130, SQL Server no podrá encontrar ni ejecutar OPENJSON() y obtendrá un error.

Puede verificar el nivel de compatibilidad de su base de datos a través de sys.databases vista de catálogo.

Puedes cambiar su nivel de compatibilidad así:

ALTER DATABASE DatabaseName 
SET COMPATIBILITY_LEVEL = 150;

¿Nuevo en JSON?

Si no está tan familiarizado con JSON, consulte mi tutorial de JSON en Quackit.