¿Buscas una guía?
Consulta nuestra guía de buenas prácticas para JSON para ver ejemplos, funciones avanzadas y aspectos a tener en cuenta al usar el tipo JSON.
JSON almacena documentos JavaScript Object Notation (JSON) en una única columna.
En ClickHouse Open-Source, el tipo de datos JSON se considera apto para producción a partir de la versión 25.3. No se recomienda usar este tipo en producción en versiones anteriores.
JSON, puedes usar la siguiente sintaxis:
Cuándo usar el tipo JSON
JSON está diseñado para consultar, filtrar y agregar campos específicos dentro de objetos JSON con estructuras dinámicas o impredecibles. Lo consigue dividiendo los objetos JSON en sub-columnas independientes, lo que reduce drásticamente la cantidad de datos leídos y acelera las consultas sobre los campos seleccionados en comparación con alternativas como Map o analizar cadenas.
Sin embargo, esto conlleva desventajas importantes:
INSERTs más lentos - Dividir el JSON en sub-columnas, realizar la inferencia de tipos y gestionar estructuras de almacenamiento flexibles hace que las inserciones sean más lentas que almacenar el JSON como una simple columnaString.- Más lento al leer objetos completos - Si necesita recuperar documentos JSON completos (en lugar de campos específicos), el tipo
JSONes más lento que leer desde una columnaString. La sobrecarga de reconstruir objetos a partir de sub-columnas independientes no aporta ningún beneficio cuando no se realizan consultas a nivel de campo. - Sobrecarga de almacenamiento - Mantener sub-columnas independientes añade sobrecarga estructural en comparación con almacenar JSON como un único valor de cadena.
Usa el tipo JSON cuando:
- Tus datos tienen una estructura dinámica o impredecible, con claves que varían entre documentos
- Los tipos de los campos o los esquemas cambian con el tiempo o varían entre registros
- Necesitas consultar, filtrar o agregar sobre rutas específicas dentro de objetos JSON cuya estructura no puedes predecir de antemano
- Tu caso de uso incluye datos semiestructurados, como logs, eventos o contenido generado por los usuarios, con esquemas inconsistentes
Usa una columna String (o tipos estructurados) cuando:
- La estructura de tus datos es conocida y consistente; en este caso, usa columnas normales o tipos
Tuple,Array,DynamicoVariant - Los documentos
JSONse tratan como blobs opacos que solo se almacenan y recuperan completos, sin análisis a nivel de campo - No necesitas consultar ni filtrar campos individuales de
JSONdentro de la base de datos JSONes simplemente un formato de transporte/almacenamiento y no se analiza dentro de ClickHouse
Crear JSON
JSON.
Uso de JSON en la definición de una columna de una tabla
Query (Example 1)
Response (Example 1)
Query (Example 2)
Response (Example 2)
Uso de CAST con ::JSON
::JSON.
CAST de String a JSON
Query
Response
CAST de Tuple a JSON
Query
Response
CAST de Map a JSON
Query
Response
Las rutas JSON se almacenan de forma aplanada. Esto significa que, cuando se da formato a un objeto JSON a partir de una ruta como devolverá:y no:
a.b.c,
no es posible saber si el objeto debe construirse como { "a.b.c" : ... } o como { "a": { "b": { "c": ... } } }.
Nuestra implementación siempre asumirá lo segundo.Por ejemplo:Consulta
Respuesta
Lectura de rutas JSON como subcolumnas
JSON permite leer cada ruta como una subcolumna independiente.
Si no se especifica el tipo de la ruta solicitada en la declaración del tipo JSON,
la subcolumna de esa ruta siempre tendrá el tipo Dynamic.
Por ejemplo:
Query
Response
Query (Reading JSON paths as sub-columns)
Response (Reading JSON paths as sub-columns)
getSubcolumn para leer subcolumnas de tipo JSON:
Query
Response
NULL:
Query
Response
Query
Response
a.b, el tipo es UInt32, tal como especificamos en la declaración del tipo JSON,
y para todas las demás subcolumnas el tipo es Dynamic.
También es posible leer subcolumnas de un tipo Dynamic mediante la sintaxis especial json.some.path.:TypeName:
Query
Response
Dynamic pueden convertirse a cualquier tipo de dato. En este caso, se lanzará una excepción si el tipo interno de Dynamic no puede convertirse al tipo solicitado:
Query
Response
Query
Response
Para leer subcolumnas de forma eficiente en partes Compact de MergeTree, asegúrese de que esté habilitada la configuración de MergeTree write_marks_for_substreams_in_compact_parts.
Lectura de subobjetos JSON como subcolumnas
JSON admite la lectura de objetos anidados como subcolumnas de tipo JSON mediante la sintaxis especial json.^some.path:
Query
Response
Query
Response
Cuando las rutas se almacenan en datos compartidos básicos (
map), la lectura de subcolumnas de subobjetos puede ser ineficiente, ya que requiere recorrer toda la estructura de datos compartidos. Con la serialización de datos compartidos map_with_buckets o advanced, la lectura de subcolumnas desde datos compartidos está muy optimizada.Lectura de subcolumnas combinadas de JSON
JSON permite leer una ruta como una subcolumna combinada mediante la sintaxis especial json.@some.path.
Una subcolumna combinada para una ruta determinada devuelve:
- El valor literal almacenado en esa ruta como
Dynamic, si la ruta tiene un valor literal. - Un subobjeto JSON en esa ruta como
Dynamic, si la ruta no tiene un valor literal pero sí tiene subrutas anidadas. NULL, si en esa ruta no existe ni un valor literal ni ninguna subruta.
json.a) y la subcolumna de subobjeto (json.^a).
El siguiente ejemplo compara los tres tipos de subcolumna para la ruta a:
Query
Response
Query
Response
- Fila 1:
acontiene un literal42.json.alo devuelve comoDynamic(Int64),json.^adevuelve un subobjeto vacío{}(sin claves anidadas ena) yjson.@adevuelve el literal42. - Fila 2:
acontiene un objeto anidado.json.adevuelveNULL(no hay ningún literal en esa ruta),json.^adevuelve el subobjeto comoJSONyjson.@atambién devuelve el subobjeto comoDynamic(JSON). - Fila 3:
ano está presente en absoluto. Tantojson.acomojson.@adevuelvenNULL, mientras quejson.^adevuelve un{}vacío.
Cuando las rutas se almacenan en datos compartidos básicos (
map) shared data, la lectura de subcolumnas combinadas puede ser ineficiente, ya que requiere recorrer toda la estructura de datos compartidos. Con la serialización de datos compartidos map_with_buckets o advanced, la lectura de subcolumnas desde los datos compartidos está muy optimizada.Inferencia de tipos para rutas
JSON, ClickHouse intenta detectar el tipo de datos más adecuado para cada ruta JSON.
Funciona de forma similar a la inferencia automática del esquema a partir de los datos de entrada
y se controla con los mismos ajustes:
- input_format_try_infer_dates
- input_format_try_infer_datetimes
- schema_inference_make_columns_nullable
- input_format_json_try_infer_numbers_from_strings
- input_format_json_infer_incomplete_types_as_strings
- input_format_json_read_numbers_as_strings
- input_format_json_read_bools_as_strings
- input_format_json_read_bools_as_numbers
- input_format_json_read_arrays_as_strings
- input_format_json_infer_array_of_dynamic_from_array_of_different_types
Query
Response
Query
Response
Query
Response
Query
Response
Manejo de arrays de objetos JSON
Array(JSON) y se insertan en una columna Dynamic correspondiente a la ruta.
Para leer un array de objetos, puedes extraerlo de la columna Dynamic como una subcolumna:
Query
Response
Query
Response
max_dynamic_types/max_dynamic_paths del tipo JSON anidado se redujeron con respecto a los valores predeterminados.
Esto es necesario para evitar que el número de subcolumnas crezca sin control en arrays anidados de objetos JSON.
Intentemos leer subcolumnas de una columna JSON anidada:
Query
Response
Array(JSON) usando una sintaxis especial:
Query
Response
[] después de la ruta indica el nivel del array. Por ejemplo, json.path[][] se transformará en json.path.:Array(Array(JSON))
Veamos las rutas y los tipos dentro de nuestro Array(JSON):
Query
Response
Array(JSON):
Query
Response
JSON anidada:
Query
Response
Manejo de claves JSON con NULL
null y la ausencia de un valor se consideran equivalentes:
Query
Response
Gestión de claves JSON con puntos
a.b y valor 42. Durante el formateo de JSON, siempre formamos objetos anidados a partir de las partes de la ruta separadas por puntos:
Query
Response
{"a.b" : 42} ahora aparece con el formato {"a" : {"b" : 42}}.
Esta limitación también hace que falle el análisis de objetos JSON válidos como este:
Query
Response
25.8). En este caso, durante el análisis, todos los puntos de las claves JSON se
escaparán como %2E y se restaurarán durante el formateo.
Query
Response
Query
Response
Query
Response
json.`a.b` es equivalente a la subcolumna json.a.b y no leerá la ruta con el punto escapado:
Query
Response
SKIP/SKIP REGEX), tienes que usar puntos escapados en el hint:
Query
Response
Query
Response
Lectura del tipo JSON a partir de datos
JSONEachRow,
TSV,
CSV,
CustomSeparated,
Values, etc.) permiten leer el tipo JSON.
Ejemplos:
Query
Response
CSV/TSV/etc., JSON se analiza a partir de una cadena que contiene el objeto JSON:
Query
Response
Alcanzar el límite de las rutas dinámicas dentro de JSON
JSON solo puede almacenar internamente un número limitado de rutas como subcolumnas independientes.
De forma predeterminada, este límite es 1024, pero puedes cambiarlo en la declaración del tipo mediante el parámetro max_dynamic_paths.
Cuando se alcanza el límite, todas las rutas nuevas insertadas en una columna JSON se almacenan en una única estructura de datos compartida.
Sigue siendo posible leer esas rutas como subcolumnas,
pero puede ser menos eficiente (consulta la sección sobre datos compartidos).
Este límite es necesario para evitar una cantidad enorme de subcolumnas distintas que pueda hacer que la tabla quede inutilizable.
Veamos qué ocurre cuando se alcanza el límite en varios escenarios distintos.
Al alcanzar el límite durante el análisis de datos
JSON en los datos, cuando se alcanza el límite para el bloque de datos actual,
todas las rutas nuevas se almacenarán en una estructura de datos compartida. Podemos usar las dos funciones de introspección siguientes: JSONDynamicPaths, JSONSharedDataPaths:
Query
Response
e y f.g, se alcanzó el límite
y se insertaron en una estructura de datos compartida.
Durante las fusiones de partes de datos en motores de tabla MergeTree
MergeTree, la columna JSON de la parte de datos resultante puede alcanzar el límite de rutas dinámicas
y no ser capaz de almacenar todas las rutas de las partes de origen como subcolumnas.
En este caso, ClickHouse decide qué rutas permanecerán como subcolumnas después de la fusión y cuáles se almacenarán en la estructura de datos compartida.
En la mayoría de los casos, ClickHouse intenta conservar las rutas que contienen
el mayor número de valores no nulos y mover las rutas menos frecuentes a la estructura de datos compartida. Sin embargo, esto depende de la implementación.
Veamos un ejemplo de este tipo de fusión.
Primero, creemos una tabla con una columna JSON, establezcamos el límite de rutas dinámicas en 3 y luego insertemos valores con 5 rutas diferentes:
Query
JSON que contiene una única ruta:
Query
Response
Query
Response
a, b y c y pasó las rutas d y e a una estructura de datos compartida.
Como se describió en la sección anterior, cuando se alcanza el límite de max_dynamic_paths, todas las rutas nuevas se almacenan en una única estructura de datos compartida.
En esta sección analizaremos en detalle la estructura de datos compartida y cómo leemos de ella las subcolumnas de las rutas.
Consulte la sección “funciones de introspección” para obtener más información sobre las funciones que se utilizan para inspeccionar el contenido de una columna JSON.
En memoria, la estructura de datos compartida no es más que una subcolumna de tipo Map(String, String) que almacena la correspondencia entre una ruta de JSON aplanado y un valor codificado en binario.
Para extraer de ella la subcolumna correspondiente a una ruta, simplemente iteramos por todas las filas de esta columna Map e intentamos encontrar la ruta solicitada y sus valores.
En las tablas MergeTree, almacenamos los datos en partes de datos que guardan todo en disco (local o remoto). Los datos en disco pueden almacenarse de forma diferente a como se almacenan en memoria.
Actualmente, hay 3 serializaciones distintas de la estructura de datos compartida en las partes de datos de MergeTree: map, map_with_buckets
y advanced.
La versión de serialización se controla mediante los ajustes de MergeTree
object_shared_data_serialization_version
y object_shared_data_serialization_version_for_zero_level_parts
(la parte de nivel cero es la que se crea al insertar datos en la tabla; durante las fusiones, las partes tienen un nivel superior).
Nota: cambiar la serialización de la estructura de datos compartida solo se admite
para la versión de serialización de objetos v3
En la versión de serialización map, los datos compartidos se serializan como una única columna de tipo Map(String, String), igual que se almacenan en
memoria. Para leer una subcolumna de ruta de este tipo de serialización, ClickHouse lee toda la columna Map y
extrae en memoria la ruta solicitada.
Esta serialización es eficiente para escribir datos y leer toda la columna JSON, pero no lo es para leer subcolumnas de ruta.
En la versión de serialización map_with_buckets, los datos compartidos se serializan como N columnas (“buckets”) de tipo Map(String, String).
Cada uno de esos buckets contiene solo un subconjunto de rutas. Para leer una sub-columna de ruta de este tipo de serialización, ClickHouse
lee toda la columna Map de un único bucket y extrae en memoria la ruta solicitada.
Esta serialización es menos eficiente para escribir datos y leer toda la columna JSON, pero es más eficiente para leer sub-columnas de rutas
porque solo lee datos de los buckets necesarios.
La cantidad de buckets N se controla mediante las configuraciones de MergeTree object_shared_data_buckets_for_compact_part (8 de forma predeterminada)
y object_shared_data_buckets_for_wide_part (32 de forma predeterminada).
El valor máximo permitido para ambas configuraciones es 256.
En la versión de serialización advanced, los datos compartidos se serializan en una estructura de datos especial que maximiza el rendimiento
de la lectura de las subcolumnas de rutas al almacenar información adicional que permite leer solo los datos de las rutas solicitadas.
Esta serialización también admite buckets, por lo que cada bucket contiene solo un subconjunto de rutas.
Esta serialización es bastante ineficiente para la escritura de datos (por lo que no se recomienda usarla para partes de nivel cero); leer toda la columna JSON es ligeramente menos eficiente en comparación con la serialización map, pero resulta muy eficiente para leer las subcolumnas de rutas.
Nota: debido a que almacena información adicional dentro de la estructura de datos, el tamaño de almacenamiento en disco es mayor con esta serialización en comparación con
las serializaciones map y map_with_buckets.
Para obtener una descripción más detallada de las nuevas serializaciones de datos compartidos y de sus detalles de implementación, consulta la entrada del blog.
Control del número de rutas dinámicas dentro de JSON en partes de MergeTree
max_dynamic_paths dentro de la declaración del tipo JSON.
Pero cambiar max_dynamic_paths para columnas existentes requiere ejecutar ALTER TABLE <table> MODIFY COLUMN <column> JSON(max_dynamic_paths=K), lo que iniciará una mutación en segundo plano que reescribirá todas las partes existentes.
Esa mutación puede ser bastante costosa y puede afectar al rendimiento del servidor hasta que finalice. Para evitarlo, puede usar estas 3 configuraciones que pueden ayudarle a cambiar el límite de rutas dinámicas en tablas MergeTree para las nuevas partes de datos:
merge_max_dynamic_subcolumns_in_wide_part- una configuración de MergeTree que limita el número de subcolumnas dinámicas para cada columna JSON durante la fusión en una parte de datos Wide.merge_max_dynamic_subcolumns_in_compact_part- una configuración de MergeTree que limita el número de subcolumnas dinámicas para cada columna JSON durante la fusión en una parte de datos Compact.max_dynamic_subcolumns_in_json_type_parsing- una configuración de sesión que limita el número de subcolumnas dinámicas para cada columna JSON durante el análisis de datos JSON en una columna JSON.
max_dynamic_paths, incluso si los valores de las configuraciones descritas son mayores.
Funciones de introspección
JSONAllPathsJSONAllPathsWithTypesJSONAllValuesJSONDynamicPathsJSONDynamicPathsWithTypesJSONSharedDataPathsJSONSharedDataPathsWithTypesdistinctDynamicTypesdistinctJSONPathsydistinctJSONPathsAndTypes
2020-01-01:
Query
Response
Query
Response
ALTER MODIFY COLUMN al tipo JSON
JSON. Actualmente, solo se admite ALTER desde el tipo String.
Ejemplo
Query
Response
Indicaciones de tipo diferidas (Experimental)
Esta funcionalidad es experimental y requiere que la configuración
allow_experimental_json_lazy_type_hints esté habilitada.ALTER TABLE ... MODIFY COLUMN, ClickHouse normalmente reescribe todas las partes de datos para materializar las nuevas indicaciones de tipo. En tablas con grandes volúmenes de datos históricos (cientos de terabytes), esto puede resultar extremadamente costoso.
Las indicaciones de tipo diferidas permiten añadir indicaciones de tipo como una operación de solo metadatos, sin reescribir los datos existentes:
- Partes antiguas: las indicaciones de tipo se aplican en tiempo de consulta convirtiendo de
Dynamical tipo indicado - Partes nuevas: las indicaciones de tipo se materializan durante las operaciones
INSERT - Fusiones: las indicaciones de tipo se materializan cuando se fusionan las partes
Habilitación de indicación de tipo diferida
Ejemplo
Query
Response
Verificar que no se haya producido ninguna mutación
ALTER se completó sin ninguna mutación consultando la tabla system.mutations:
Materialización de indicaciones de tipo
- Esperar a las fusiones en segundo plano: ClickHouse materializará automáticamente las indicaciones de tipo cuando se fusionen las partes
- Forzar la fusión: Usa
OPTIMIZE TABLE test_lazy FINALpara fusionar todas las partes de inmediato - Reescribir las partes: Usa
ALTER TABLE test_lazy REWRITE PARTSpara reescribir las partes con los metadatos nuevos
Limitaciones
- Esta funcionalidad es experimental y puede cambiar en futuras versiones
- La conversión de tipos en tiempo de consulta puede suponer una sobrecarga de rendimiento significativa en comparación con los tipos materializados previamente, especialmente en objetos JSON grandes
- Esta funcionalidad solo se aplica al modificar
typed_paths(indicaciones de tipo); otros parámetros de JSON, comomax_dynamic_paths,SKIPoSKIP REGEXP, siguen requiriendo mutaciones
Comparación entre valores del tipo JSON
Query
Response
Variant.
Índices de omisión de datos para JSON
JSON de tres maneras:
- Índices en subcolumnas específicas — crea un índice de omisión estándar en una ruta JSON conocida, igual que en una columna normal. Esto indexa los valores de esa ruta.
- Índices basados en rutas con
JSONAllPaths— indexa el conjunto de rutas presentes en cada gránulo para omitir los gránulos que no puedan contener la ruta consultada. - Índices basados en valores con
JSONAllValues— indexa todos los valores de todas las rutas JSON mediante un índice de texto para acelerar la búsqueda de texto completo en cualquier subcolumna JSON con un solo índice.
Índices sobre subcolumnas específicas
minmax, set, bloom_filter, tokenbf_v1, ngrambf_v1, etc.).
Hay dos formas de hacer referencia a una subcolumna JSON en una expresión de índice:
- Ruta tipada declarada en la indicación de tipo JSON: acceda directamente por nombre:
json.a. - Ruta dinámica con conversión explícita: use la sintaxis de conversión
:::json.b::String.
json.a || json.b::String.
Ejemplo
Query
minmax en la subcolumna tipada data.sensor_id acota el escaneo a los gránulos coincidentes:
Query
Response
bloom_filter sobre la subcolumna convertida con CAST data.location::String también funciona:
Query
Response
Índices basados en rutas con JSONAllPaths
JSON con la función JSONAllPaths.
Esto funciona de forma similar a crear índices de omisión en columnas Map con mapKeys: el índice almacena el conjunto de rutas JSON presentes en cada gránulo y lo utiliza para omitir los gránulos que no pueden contener la ruta consultada.
Tipos de índice compatibles
JSONAllPaths se puede usar con los siguientes tipos de índices de omisión:
bloom_filter— admiteequals,ineIS NOT NULL.tokenbf_v1— admiteequalseIS NOT NULL.ngrambf_v1— admiteequalseIS NOT NULL.text(índice invertido) — admiteequals,ineIS NOT NULL.
Ejemplo
Query
EXPLAIN indexes = 1 para comprobar que se está utilizando el índice de omisión de datos. Cuando una ruta existe solo en una parte, el índice omite la otra parte:
Query
Response
Query
Response
IS NOT NULL también usa el índice: omite los gránulos donde la ruta no existe (ya que el valor sería NULL):
Query
Response
Cómo funciona
JSONAllPaths(json_column) produce un Array(String) que contiene todas las rutas presentes en un valor JSON.
El índice de omisión almacena estas cadenas de ruta en su estructura de datos (bloom filter o índice invertido).
Cuando una consulta filtra por json.some.path, el índice comprueba si la cadena "some.path" está presente en el índice de cada gránulo y omite los gránulos en los que no está presente.
Seguridad con rutas ausentes
NULLpara el tipoDynamic(p. ej.,json.path) y las subcolumnas de tipoNullable(p. ej.,json.path.:Int64) — las comparaciones conNULLsiempre devuelven false, por lo que omitir bloques es seguro.- El valor predeterminado del tipo para expresiones
CASTnoNullable(p. ej.,json.path::Int64produce0cuando falta la ruta) — omitir bloques es seguro solo cuando el valor comparado difiere del valor predeterminado. El índice gestiona esta distinción automáticamente.
Búsqueda de texto completo con JSONAllValues
JSONAllValues.
JSONAllValues devuelve todos los valores de una columna JSON como Array(String), que puede indexarse con un índice de texto.
Un solo índice sobre JSONAllValues(json_column) cubre todas las rutas JSON, lo que permite realizar búsquedas de texto completo en cualquier subcolumna sin crear índices independientes para cada ruta.
Consulta Índices basados en valores con JSONAllValues en la documentación de índices de texto para obtener más detalles y ejemplos.
Consejos para aprovechar mejor el tipo JSON
JSON y cargar datos en ella, tenga en cuenta los siguientes consejos:
- Analice sus datos y especifique tantas rutas con tipos como le sea posible. Esto hará que el almacenamiento y la lectura sean mucho más eficientes.
- Piense qué rutas necesitará y cuáles no necesitará nunca. Especifique en la sección
SKIPlas rutas que no vaya a necesitar y, si hace falta, en la secciónSKIP REGEXP. Esto mejorará el almacenamiento. - No establezca el parámetro
max_dynamic_pathsen valores demasiado altos, ya que esto puede hacer que el almacenamiento y la lectura sean menos eficientes. Aunque depende en gran medida de parámetros del sistema como la memoria, la CPU, etc., como regla general no debería establecermax_dynamic_pathspor encima de 10 000 para el almacenamiento en el sistema de archivos local ni por encima de 1024 para el almacenamiento en el sistema de archivos remoto.