Aplique un enfoque a nivel de objetoSe pueden aplicar distintas técnicas a diferentes objetos dentro del mismo esquema. Por ejemplo, algunos objetos pueden resolverse mejor con un tipo
String y otros con un tipo Map. Tenga en cuenta que, una vez que se usa un tipo String, ya no es necesario tomar más decisiones sobre el esquema. Por el contrario, es posible anidar subobjetos dentro de una clave Map, incluido un String que represente JSON, como se muestra a continuación:Uso del tipo String
String. Los valores pueden extraerse en tiempo de consulta mediante funciones JSON, como se muestra a continuación.
Gestionar datos con el enfoque estructurado descrito anteriormente a menudo no es viable para aquellos usuarios que trabajan con JSON dinámico, ya sea porque está sujeto a cambios o porque su esquema no se comprende bien. Para disponer de la máxima flexibilidad, puede simplemente almacenar el JSON como String y después usar funciones para extraer los campos según sea necesario. Esto representa el extremo opuesto de tratar el JSON como un objeto estructurado. Esta flexibilidad tiene un coste y conlleva desventajas importantes, principalmente un aumento de la complejidad de la sintaxis de las consultas, así como una degradación del rendimiento.
Como se señaló antes, para el objeto person original, no podemos garantizar la estructura de la columna tags. Insertamos la fila original (incluyendo company.labels, que por ahora ignoramos), declarando la columna Tags como un String:
tags y ver que el JSON se insertó como una cadena:
JSONExtract pueden usarse para extraer valores de este JSON. Considere el ejemplo sencillo que aparece a continuación:
tags de tipo String como una ruta dentro del JSON de la que extraer el valor. Las rutas anidadas requieren anidar funciones; p. ej., JSONExtractUInt(JSONExtractString(tags, 'car'), 'year'), que extrae tags.car.year. La extracción de rutas anidadas puede simplificarse mediante las funciones JSON_QUERY y JSON_VALUE.
Considere el caso extremo del conjunto de datos arxiv, donde consideramos todo el cuerpo como un String.
JSONAsString:
JSON_VALUE(body, '$.versions[0].created').
Las funciones de String son considerablemente más lentas (> 10x) que las conversiones explícitas de tipos con índices. Las consultas anteriores siempre requieren un escaneo completo de la tabla y procesar cada fila. Aunque estas consultas seguirán siendo rápidas en un conjunto de datos pequeño como este, el rendimiento se degradará en conjuntos de datos más grandes.
La flexibilidad de este enfoque tiene un claro coste en términos de rendimiento y sintaxis, y solo debe utilizarse para objetos muy dinámicos en el esquema.
Funciones JSON simples
simpleJSON* ofrecen un rendimiento potencialmente superior, principalmente porque asumen de forma estricta la estructura y el formato del JSON. En concreto:
- Los nombres de los campos deben ser constantes
-
Codificación coherente de los nombres de los campos; por ejemplo,
simpleJSONHas('{"abc":"def"}', 'abc') = 1, perovisitParamHas('{"\\u0061\\u0062\\u0063":"def"}', 'abc') = 0 - Los nombres de los campos son únicos en todas las estructuras anidadas. No se distingue entre niveles de anidamiento y la coincidencia se realiza de forma indiscriminada. Si hay varios campos coincidentes, se usa la primera aparición.
-
No se permiten caracteres especiales fuera de los literales de cadena. Esto incluye los espacios. Lo siguiente no es válido y no se podrá parsear.
simpleJSONExtractString para extraer la clave created, aprovechando que para la fecha de publicación solo nos interesa el primer valor. En este caso, las limitaciones de las funciones simpleJSON* son aceptables a cambio de la mejora del rendimiento.
Uso del tipo Map
Si el objeto se usa para almacenar claves arbitrarias, en su mayoría de un mismo tipo, considere usar el tipoMap. Idealmente, el número de claves únicas no debería superar unos pocos cientos. El tipo Map también puede considerarse para objetos con subobjetos, siempre que estos tengan tipos uniformes. En general, recomendamos usar el tipo Map para labels y tags; por ejemplo, labels de pods de Kubernetes en datos de logs.
Aunque los Map ofrecen una forma sencilla de representar estructuras anidadas, tienen algunas limitaciones importantes:
- Todos los campos deben ser del mismo tipo.
- El acceso a subcolumnas requiere una sintaxis especial para mapas, ya que los campos no existen como columnas. El objeto completo es una columna.
- Acceder a una subcolumna carga el valor completo del
Map, es decir, todos los elementos del mismo nivel y sus respectivos valores. En mapas más grandes, esto puede tener un impacto significativo en el rendimiento.
Claves StringAl modelar objetos como
Map, se usa una clave String para almacenar el nombre de la clave JSON. Por lo tanto, el mapa siempre será Map(String, T), donde T depende de los datos.Valores primitivos
La aplicación más sencilla de unMap se da cuando el objeto contiene valores del mismo tipo primitivo. En la mayoría de los casos, esto implica usar el tipo String para el valor T.
Considera nuestro JSON anterior de una persona, donde se determinó que el objeto company.labels era dinámico. Es importante destacar que solo esperamos que se agreguen a este objeto pares clave-valor de tipo String. Por lo tanto, podemos declararlo como Map(String, String):
map, por ejemplo:
Map para consultar este tipo, descritas aquí. Si sus datos no son de un tipo consistente, existen funciones para realizar la coerción de tipos necesaria.
Valores de objetos
El tipoMap también puede considerarse para objetos que tienen subobjetos, siempre que estos últimos mantengan consistencia en sus tipos.
Supóngase que la clave tags de nuestro objeto persons requiere una estructura consistente, en la que el subobjeto de cada tag tenga las columnas name y time. Un ejemplo simplificado de un documento JSON de este tipo podría verse así:
Map(String, Tuple(name String, time DateTime)), como se muestra a continuación:
Array(Tuple(key String, name String, time DateTime)).
Uso del tipo Nested
El tipo Nested puede usarse para modelar objetos estáticos que rara vez cambian, como alternativa aTuple y Array(Tuple). En general, recomendamos evitar el uso de este tipo para JSON, ya que su comportamiento suele ser confuso. La principal ventaja de Nested es que las subcolumnas pueden utilizarse en las claves de ordenación.
A continuación, mostramos un ejemplo de cómo usar el tipo Nested para modelar un objeto estático. Considere la siguiente entrada de registro sencilla en JSON:
request como Nested. Al igual que con Tuple, es necesario especificar las subcolumnas.
flatten_nested
La configuraciónflatten_nested controla el comportamiento del tipo Nested.
flatten_nested=1
Un valor de1 (el valor predeterminado) no admite un nivel arbitrario de anidamiento. Con este valor, la forma más sencilla de entender una estructura de datos anidada es como varias columnas Array de la misma longitud. En la práctica, los campos method, path y version son columnas Array(Type) independientes, con una restricción fundamental: la longitud de los campos method, path y version debe ser la misma. Esto se ilustra con SHOW CREATE TABLE:
-
Debemos usar la configuración
input_format_import_nested_jsonpara insertar el JSON como una estructura anidada. Sin esto, tendríamos que aplanar el JSON, es decir: -
Los campos anidados
method,pathyversiondeben pasarse como arrays de JSON, es decir:
Array para las subcolumnas significa que puede aprovecharse potencialmente todo el abanico de funciones de arrays, incluida la cláusula ARRAY JOIN, lo cual resulta útil si sus columnas tienen varios valores.
flatten_nested=0
Esto permite un nivel arbitrario de anidamiento y significa que las columnas anidadas se mantienen como un único array deTuples; en la práctica, pasan a ser lo mismo que Array(Tuple).
Esta es la forma preferida y, a menudo, la más sencilla de usar JSON con Nested. Como mostramos a continuación, solo requiere que todos los objetos sean una lista.
A continuación, volvemos a crear nuestra tabla y volvemos a insertar una fila:
-
input_format_import_nested_jsonno es necesario para insertar datos. -
El tipo
Nestedse conserva enSHOW CREATE TABLE. En la práctica, esta columna es en realidad unArray(Tuple(Nested(method LowCardinality(String), path String, version LowCardinality(String)))) -
Como resultado, debemos insertar
requestcomo un array, es decir:
Ejemplo
Hay un ejemplo más amplio de los datos anteriores disponible en un bucket público de S3 en:s3://datasets-documentation/http/.
flatten_nested=0.
La siguiente sentencia inserta 10 millones de filas, por lo que puede tardar unos minutos en ejecutarse. Aplique un LIMIT si es necesario:
Uso de arrays por pares
Los arrays por pares ofrecen un equilibrio entre la flexibilidad de representar JSON como Strings y el rendimiento de un enfoque más estructurado. El esquema es flexible, ya que se puede añadir potencialmente cualquier campo nuevo en la raíz. Sin embargo, esto requiere una sintaxis de consulta considerablemente más compleja y no es compatible con estructuras anidadas. Como ejemplo, considere la siguiente tabla:JSONExtractKeysAndValues para lograrlo:
indexOf para identificar el índice de la clave requerida (que debe ser coherente con el orden de los valores). Esto permite acceder a la columna de Array values, es decir, values[indexOf(keys, 'status')]. Seguimos necesitando un método de análisis de JSON para la columna request; en este caso, simpleJSONExtractString.