JSON estático vs. dinámico
- Tipos primitivos - Si el valor de la clave es un tipo primitivo, independientemente de si forma parte de un subobjeto o está en la raíz, asegúrese de seleccionar su tipo según las buenas prácticas de diseño de esquemas y las reglas de optimización de tipos. Los arrays de primitivos, como
phone_numbersa continuación, pueden modelarse comoArray(<type>); por ejemplo,Array(String). - Estático vs. dinámico - Si el valor de la clave es un objeto complejo, es decir, un objeto o un array de objetos, determine si está sujeto a cambios. Los objetos que rara vez incorporan claves nuevas, y en los que la adición de una nueva clave puede preverse y gestionarse con un cambio de esquema mediante
ALTER TABLE ADD COLUMN, pueden considerarse estáticos. Esto incluye objetos en los que solo se proporciona un subconjunto de las claves en algunos documentos JSON. Los objetos en los que se añaden claves nuevas con frecuencia o cuya aparición no es predecible deben considerarse dinámicos. La excepción son las estructuras con cientos o miles de subclaves, que por comodidad pueden considerarse dinámicas.
- Las claves raíz
name,username,email,websitepueden representarse con el tipoString. La columnaphone_numberses una primitiva Array de tipoArray(String), mientras quedobeidson de tipoDateyUInt32, respectivamente. - No se agregarán nuevas claves al objeto
address(solo nuevos objetos de dirección), por lo que puede considerarse estático. Si descendemos recursivamente, todas las sub-columnas pueden considerarse primitivas (y de tipoString), exceptogeo. Esta también es una estructura estática con dos columnas de tipoFloat32,latylon. - La columna
tagses dinámica. Suponemos que se pueden añadir nuevas claves arbitrarias a este objeto, de cualquier tipo y estructura. - El objeto
companyes estático y siempre contendrá como máximo las 3 claves especificadas. Las subclavesnameycatchPhraseson de tipoString. La clavelabelses dinámica. Suponemos que se pueden añadir nuevas claves arbitrarias a este objeto. Los valores siempre serán pares clave-valor de tipo string.
Las estructuras con cientos o miles de claves estáticas pueden considerarse dinámicas, ya que rara vez resulta realista declarar estáticamente sus columnas. Sin embargo, cuando sea posible, omita rutas que no sean necesarias para ahorrar tanto almacenamiento como la sobrecarga de inferencia.
Manejo de estructuras estáticas
Tuple. Los arrays de objetos pueden almacenarse como arrays de tuplas, es decir, Array(Tuple). Dentro de las propias tuplas, las columnas y sus respectivos tipos deben definirse siguiendo las mismas reglas. Esto puede dar lugar a Tuple anidados para representar objetos anidados, como se muestra a continuación.
Para ilustrarlo, usaremos el ejemplo anterior de la persona en JSON, omitiendo los objetos dinámicos:
company está definida como Tuple(catchPhrase String, name String). La clave address usa un Array(Tuple), con un Tuple anidado para representar la columna geo.
El JSON puede insertarse en esta tabla con su estructura actual:
address.street se devuelve como un Array. Para consultar un objeto específico dentro de un array por posición, se debe especificar el índice del array después del nombre de la columna. Por ejemplo, para acceder a la calle de la primera dirección:
24.12:
Manejo de valores predeterminados
Tuple no requiere todas las columnas en el payload JSON. Si no se proporcionan, se usarán valores predeterminados.
Consideremos la tabla people anterior y el siguiente JSON disperso, en el que faltan las claves suite, geo, phone_numbers y catchPhrase.
Diferenciar entre vacío y nuloSi necesitas diferenciar entre un valor vacío y uno no proporcionado, puedes usar el tipo Nullable. Esto debe evitarse salvo que sea absolutamente necesario, ya que afectará negativamente al almacenamiento y al rendimiento de las consultas en estas columnas.
Gestión de columnas nuevas
nickname:
nickname:
ALTER TABLE ADD COLUMN. Se puede especificar un valor predeterminado mediante la cláusula DEFAULT, que se utilizará si no se especifica en las inserciones posteriores. Las filas en las que este valor no esté presente (porque se insertaron antes de su creación) también devolverán ese valor predeterminado. Si no se especifica ningún valor DEFAULT, se utilizará el valor predeterminado del tipo.
Por ejemplo:
Manejo de estructuras semiestructuradas/dinámicas
JSON.
Más concretamente, use el tipo JSON cuando sus datos:
- Tengan claves impredecibles que puedan cambiar con el tiempo.
- Contengan valores con tipos variables (p. ej., una ruta puede contener a veces una cadena y otras un número).
- Requieran flexibilidad de esquema cuando el tipado estricto no sea viable.
- Tenga cientos o incluso miles de rutas que son estáticas, pero que simplemente no resulta realista declarar explícitamente. Esto suele ser poco frecuente.
company.labels era dinámico.
Supongamos que company.labels contiene claves arbitrarias. Además, el tipo de cualquier clave de esta estructura puede no ser consistente entre filas. Por ejemplo:
company.labels entre distintos objetos, en cuanto a claves y tipos, tenemos varias opciones para modelar estos datos:
- Una única columna JSON - representa todo el esquema como una sola columna
JSON, lo que permite que todas las estructuras subyacentes sean dinámicas. - Columna JSON específica - usa el tipo
JSONsolo para la columnacompany.labels, manteniendo el esquema estructurado utilizado anteriormente para todas las demás columnas.
- Validación de datos – aplicar un esquema estricto evita el riesgo de explosión de columnas, salvo en estructuras específicas.
- Evita el riesgo de explosión de columnas - Aunque el tipo JSON puede escalar hasta potencialmente miles de columnas, donde las subcolumnas se almacenan como columnas dedicadas, esto puede provocar una explosión de archivos de columnas, en la que se crea un número excesivo de archivos que afecta al rendimiento. Para mitigarlo, el tipo Dynamic subyacente que usa JSON ofrece un parámetro
max_dynamic_paths, que limita el número de rutas únicas almacenadas como archivos de columnas independientes. Una vez alcanzado el umbral, las rutas adicionales se almacenan en un archivo de columna compartido mediante un formato codificado compacto, lo que mantiene el rendimiento y la eficiencia del almacenamiento a la vez que admite una ingestión de datos flexible. Sin embargo, acceder a este archivo de columna compartido no ofrece el mismo rendimiento. Tenga en cuenta, no obstante, que la columna JSON puede usarse con sugerencias de tipo. Las columnas con sugerencias de tipo ofrecerán el mismo rendimiento que las columnas dedicadas. - Introspección más sencilla de rutas y tipos - Aunque el tipo JSON admite funciones de introspección para determinar los tipos y las rutas que se han inferido, las estructuras estáticas pueden ser más fáciles de explorar, por ejemplo, con
DESCRIBE.
Una única columna JSON
JSON solo para subestructuras dinámicas cuando sea necesario.
Consideraciones de rendimientoUna única columna JSON puede optimizarse omitiendo (sin almacenar) las rutas JSON que no se requieren y usando indicaciones de tipo. Las pistas de tipo permiten al usuario definir explícitamente el tipo de una subcolumna, evitando así la inferencia y el procesamiento de indirección en tiempo de consulta. Esto puede utilizarse para ofrecer el mismo rendimiento que si se usara un esquema explícito. Consulte “Uso de pistas de tipo y omisión de rutas” para obtener más detalles.
Proporcionamos una indicación de tipo para la columna
username en la definición JSON, ya que la usamos en la clave de ordenación/clave primaria. Esto ayuda a ClickHouse a saber que esta columna no será NULL y garantiza que sepa qué subcolumna username debe usar (puede haber varias para cada tipo, por lo que, de lo contrario, sería ambiguo).JSONAsObject:
.; p. ej.
NULL.
Además, se crea una subcolumna independiente para las rutas del mismo tipo. Por ejemplo, existe una subcolumna para company.labels.type tanto para String como para Array(Nullable(String)). Aunque se devolverán ambas siempre que sea posible, podemos apuntar a subcolumnas específicas usando la sintaxis .::
^. Esta es una decisión de diseño para evitar leer un gran número de columnas, a menos que se solicite explícitamente. Los objetos a los que se acceda sin ^ devolverán NULL, como se muestra a continuación:
Columna JSON dirigida
JSON para la columna company.labels.
JSONEachRow:
company.labels.
Uso de indicaciones de tipo y rutas excluidas
dissolved, employees y founded dentro de la columna JSON company.labels
SKIP and SKIP REGEXP, para minimizar el almacenamiento y evitar inferencias innecesarias en rutas que no necesitamos. Por ejemplo, supongamos que usamos una única columna JSON para los datos anteriores. Podemos omitir las rutas address y company:”
Optimización del rendimiento con indicaciones de tipo
Configuración de rutas dinámicas
max_dynamic_paths en la declaración del tipo JSON.
SKIP para limitar lo que se almacena.
Para quienes quieran profundizar en la implementación de este nuevo tipo de columna, recomendamos leer nuestra detallada entrada del blog “Un nuevo y potente tipo de datos JSON para ClickHouse”.