Skip to main content
Embora a inferência de esquema possa ser usada para definir um esquema inicial para dados JSON e consultar arquivos de dados JSON diretamente onde estão armazenados, por exemplo, no S3, você deve buscar definir um esquema versionado e otimizado para seus dados. A seguir, discutimos a abordagem recomendada para modelar estruturas JSON.

JSON estático vs. dinâmico

A principal tarefa ao definir um esquema para JSON é determinar o tipo adequado para o valor de cada chave. Recomendamos que os usuários apliquem recursivamente as regras a seguir a cada chave na hierarquia do JSON para determinar o tipo apropriado para cada uma delas.
  1. Tipos primitivos - Se o valor da chave for um tipo primitivo, independentemente de fazer parte de um subobjeto ou de estar na raiz, selecione o tipo de acordo com as práticas recomendadas gerais de design de esquema e as regras de otimização de tipos. Arrays de primitivos, como phone_numbers abaixo, podem ser modelados como Array(<type>), por exemplo, Array(String).
  2. Estático vs. dinâmico - Se o valor da chave for um objeto complexo, ou seja, um objeto ou um array de objetos, determine se ele está sujeito a mudanças. Objetos que raramente recebem novas chaves, em que a adição de uma nova chave pode ser prevista e tratada com uma alteração de esquema por meio de ALTER TABLE ADD COLUMN, podem ser considerados estáticos. Isso inclui objetos em que apenas um subconjunto das chaves pode estar presente em alguns documentos JSON. Objetos em que novas chaves são adicionadas com frequência e/ou não são previsíveis devem ser considerados dinâmicos. A exceção aqui são estruturas com centenas ou milhares de subchaves, que podem ser consideradas dinâmicas por conveniência.
Para determinar se um valor é estático ou dinâmico, consulte as seções relevantes Como lidar com objetos estáticos e Como lidar com objetos dinâmicos abaixo.

Importante: As regras acima devem ser aplicadas recursivamente. Se o valor de uma chave for classificado como dinâmico, nenhuma avaliação adicional será necessária, e as diretrizes em Como lidar com objetos dinâmicos poderão ser seguidas. Se o objeto for estático, continue avaliando as subchaves até que os valores das chaves sejam primitivos ou sejam encontradas chaves dinâmicas. Para ilustrar essas regras, usamos o exemplo JSON a seguir, que representa uma pessoa:
Aplicando estas regras:
  • As chaves de nível raiz name, username, email e website podem ser representadas como do tipo String. A coluna phone_numbers é um Array primitivo do tipo Array(String), enquanto dob e id são dos tipos Date e UInt32, respectivamente.
  • Nenhuma chave nova será adicionada ao objeto address (apenas novos objetos de endereço) e, portanto, ele pode ser considerado estático. Se fizermos a recursão, todas as subcolunas podem ser consideradas primitivas (e do tipo String), exceto geo. Esta também é uma estrutura estática com duas colunas Float32, lat e lon.
  • A coluna tags é dinâmica. Assumimos que novas tags arbitrárias, de qualquer tipo e estrutura, podem ser adicionadas a esse objeto.
  • O objeto company é estático e sempre conterá, no máximo, as 3 chaves especificadas. As subchaves name e catchPhrase são do tipo String. A chave labels é dinâmica. Assumimos que novas tags arbitrárias podem ser adicionadas a esse objeto. Os valores sempre serão pares chave-valor do tipo string.
Estruturas com centenas ou milhares de chaves estáticas podem ser consideradas dinâmicas, já que raramente é viável declarar estaticamente suas colunas. No entanto, sempre que possível, omita caminhos desnecessários para reduzir o uso de armazenamento e a sobrecarga de inferência.

Lidando com estruturas estáticas

Recomendamos tratar estruturas estáticas usando tuplas nomeadas, ou seja, Tuple. Arrays de objetos podem ser armazenados usando arrays de tuplas, ou seja, Array(Tuple). Dentro das próprias tuplas, as colunas e seus respectivos tipos devem ser definidos seguindo as mesmas regras. Isso pode resultar em Tuple aninhadas para representar objetos aninhados, como mostrado abaixo. Para ilustrar isso, usamos o exemplo anterior da pessoa em JSON, omitindo os objetos dinâmicos:
O esquema desta tabela é mostrado abaixo:
Observe como a coluna company é definida como Tuple(catchPhrase String, name String). A chave address usa um Array(Tuple), com um Tuple aninhado para representar a coluna geo. É possível inserir JSON nesta tabela na estrutura atual:
No nosso exemplo acima, temos o mínimo de dados, mas, como mostrado abaixo, podemos consultar as colunas da tupla pelos nomes separados por ponto.
Observe que a coluna address.street é retornada como um Array. Para consultar um objeto específico dentro de um array pela posição, o índice do array deve ser especificado após o nome da coluna. Por exemplo, para acessar a rua do primeiro endereço:
Subcolunas também podem ser usadas em chaves de ordenação desde a versão 24.12:

Tratando valores padrão

Embora os objetos JSON sejam estruturados, eles geralmente são esparsos, com apenas um subconjunto das chaves conhecidas presente. Felizmente, o tipo Tuple não exige todas as colunas no payload JSON. Se não forem fornecidas, serão usados valores padrão. Considere a tabela people apresentada anteriormente e o JSON esparso a seguir, sem as chaves suite, geo, phone_numbers e catchPhrase.
Como podemos ver abaixo, esta linha pode ser inserida com sucesso:
Ao consultar esta única linha, podemos ver que os valores padrão são usados nas colunas (incluindo sub-objetos) que foram omitidas:
Diferenciando vazio de nuloSe você precisar diferenciar entre um valor vazio e um valor não fornecido, o tipo Nullable pode ser usado. Isso deve ser evitado, a menos que seja absolutamente necessário, pois afeta negativamente o armazenamento e o desempenho das consultas nessas colunas.

Como lidar com novas colunas

Embora uma abordagem estruturada seja mais simples quando as chaves do JSON são estáticas, ela ainda pode ser usada se as alterações no esquema puderem ser planejadas, ou seja, se as novas chaves forem conhecidas com antecedência e o esquema puder ser modificado de acordo. Observe que o ClickHouse, por padrão, ignora chaves do JSON fornecidas no payload que não estejam presentes no esquema. Considere o payload JSON modificado a seguir, com a adição da chave nickname:
Este JSON pode ser inserido com sucesso, ignorando a chave nickname:
Colunas podem ser adicionadas a um esquema usando o comando ALTER TABLE ADD COLUMN. Um valor padrão pode ser especificado por meio da cláusula DEFAULT, que será usada caso ele não seja especificado nas inserções subsequentes. As linhas em que esse valor não estiver presente (por terem sido inseridas antes de sua criação) também retornarão esse valor padrão. Se nenhum valor DEFAULT for especificado, será usado o valor padrão do tipo. Por exemplo:

Lidando com estruturas semiestruturadas/dinâmicas

Se os dados JSON forem semiestruturados, com chaves que podem ser adicionadas dinamicamente e/ou ter vários tipos, o tipo JSON é recomendado. Mais especificamente, use o tipo JSON quando seus dados:
  • Têm chaves imprevisíveis que podem mudar ao longo do tempo.
  • Contêm valores com tipos variados (por exemplo, um caminho pode às vezes conter uma string e, em outras, um número).
  • Exigem flexibilidade de esquema quando a tipagem estrita não é viável.
  • Você tem centenas ou até milhares de caminhos que são estáticos, mas que simplesmente não é realista declarar explicitamente. Isso tende a ser raro.
Considere o JSON de pessoa visto anteriormente, em que o objeto company.labels foi considerado dinâmico. Vamos supor que company.labels contenha chaves arbitrárias. Além disso, o tipo de qualquer chave nessa estrutura pode não ser consistente entre linhas. Por exemplo:
Dada a natureza dinâmica da coluna company.labels entre os objetos, no que diz respeito a chaves e tipos, temos várias opções para modelar esses dados:
  • Coluna JSON única - representa todo o esquema como uma única coluna JSON, permitindo que todas as estruturas abaixo dela sejam dinâmicas.
  • Coluna JSON direcionada - usa o tipo JSON apenas para a coluna company.labels, mantendo o esquema estruturado usado acima para todas as outras colunas.
Embora a primeira abordagem não esteja alinhada com a metodologia anterior, a abordagem de coluna JSON única é útil para prototipagem e tarefas de engenharia de dados. Para implantações de produção do ClickHouse em escala, recomendamos definir a estrutura com precisão e usar o tipo JSON para subestruturas dinâmicas específicas sempre que possível. Um esquema rígido tem vários benefícios:
  • Validação de dados – impor um esquema rígido evita o risco de explosão de colunas, exceto em estruturas específicas.
  • Evita o risco de explosão de colunas - Embora o tipo JSON possa escalar para potencialmente milhares de colunas, em que as subcolunas são armazenadas como colunas dedicadas, isso pode levar a uma explosão de arquivos de coluna, na qual um número excessivo desses arquivos é criado, impactando o desempenho. Para mitigar isso, o tipo Dynamic subjacente usado pelo JSON oferece um parâmetro max_dynamic_paths, que limita o número de caminhos únicos armazenados como arquivos de coluna separados. Quando esse limite é atingido, caminhos adicionais são armazenados em um arquivo de coluna compartilhado usando um formato compacto codificado, mantendo o desempenho e a eficiência de armazenamento, ao mesmo tempo em que oferece suporte à ingestão flexível de dados. No entanto, acessar esse arquivo de coluna compartilhado não oferece o mesmo desempenho. Observe, porém, que a coluna JSON pode ser usada com indicações de tipo. Colunas com indicação de tipo terão o mesmo desempenho que colunas dedicadas.
  • Introspecção mais simples de caminhos e tipos - Embora o tipo JSON ofereça suporte a funções de introspecção para determinar os tipos e caminhos inferidos, estruturas estáticas podem ser mais simples de explorar, por exemplo, com DESCRIBE.

Coluna JSON única

Essa abordagem é útil para prototipagem e tarefas de engenharia de dados. Em produção, tente usar JSON apenas para subestruturas dinâmicas, quando necessário.
Considerações de desempenhoUma única coluna JSON pode ser otimizada ignorando (ou seja, sem armazenar) caminhos JSON que não são necessários e usando type hints. Os type hints permitem que o usuário defina explicitamente o tipo de uma subcoluna, evitando a inferência e o processamento de indireção no momento da consulta. Isso pode ser usado para obter o mesmo desempenho de um esquema explícito. Consulte “Usando type hints e ignorando caminhos” para mais detalhes.
O esquema de uma única coluna JSON aqui é simples:
Fornecemos uma type hint para a coluna username na definição do JSON, pois a usamos na chave de ordenação/chave primária. Isso ajuda o ClickHouse a saber que essa coluna não poderá ser nula e garante que ele saiba qual subcoluna de username usar (pode haver várias para cada tipo, então, caso contrário, isso seria ambíguo).
É possível inserir linhas na tabela acima usando o formato JSONAsObject:
Podemos determinar as subcolunas inferidas e seus tipos usando funções de introspecção. Por exemplo:
Para ver a lista completa das funções de introspecção, consulte “Funções de introspecção” Os subcaminhos podem ser acessados usando a notação .; por exemplo:
Note que as colunas ausentes nas linhas são retornadas como NULL. Além disso, uma subcoluna separada é criada para caminhos do mesmo tipo. Por exemplo, há uma subcoluna para company.labels.type com os tipos String e Array(Nullable(String)). Embora ambas sejam retornadas sempre que possível, podemos direcionar subcolunas específicas usando a sintaxe .::
Para retornar subobjetos aninhados, o ^ é obrigatório. Essa é uma decisão de projeto para evitar a leitura de um grande número de colunas, a menos que isso seja explicitamente solicitado. Objetos acessados sem ^ retornarão NULL, como mostrado abaixo:

Coluna JSON direcionada

Embora seja útil em prototipagem e em desafios de engenharia de dados, recomendamos o uso de um schema explícito em produção sempre que possível. Nosso exemplo anterior pode ser modelado com uma única coluna JSON para a coluna company.labels.
Podemos inserir dados nesta tabela usando o formato JSONEachRow:
Funções de introspecção podem ser usadas para determinar os caminhos e tipos inferidos para a coluna company.labels.

Usando type hints e ignorando caminhos

Os type hints permitem especificar o tipo de um caminho e de sua subcoluna, evitando inferências de tipo desnecessárias. Considere o exemplo a seguir, em que especificamos os tipos para as chaves JSON dissolved, employees e founded dentro da coluna JSON company.labels
Observe como essas colunas agora têm tipos explícitos:
Além disso, podemos ignorar caminhos no JSON que não queremos armazenar usando os parâmetros SKIP e SKIP REGEXP, a fim de minimizar o armazenamento e evitar inferência desnecessária em caminhos que não precisamos. Por exemplo, suponha que usemos uma única coluna JSON para os dados acima. Podemos ignorar os caminhos address e company:
Veja como nossas colunas foram excluídas dos dados:

Otimizando o desempenho com type hints

Type hints oferecem mais do que apenas uma forma de evitar a inferência desnecessária de tipos — eles eliminam por completo a indireção de armazenamento e processamento, além de permitir a especificação de tipos primitivos ideais. Caminhos JSON com type hints são sempre armazenados como colunas tradicionais, dispensando a necessidade de colunas discriminadoras ou de resolução dinâmica em tempo de consulta. Isso significa que, com type hints bem definidos, chaves JSON aninhadas alcançam o mesmo desempenho e a mesma eficiência que teriam se fossem modeladas como colunas de nível superior desde o início. Como resultado, para conjuntos de dados que são em grande parte consistentes, mas ainda se beneficiam da flexibilidade do JSON, type hints oferecem uma maneira prática de preservar o desempenho sem a necessidade de reestruturar seu esquema ou pipeline de ingestão.

Configurando caminhos dinâmicos

O ClickHouse armazena cada caminho JSON como uma subcoluna em um layout verdadeiramente colunar, o que oferece os mesmos benefícios de desempenho observados em colunas tradicionais, como compactação, processamento acelerado por SIMD e I/O mínimo de disco. Cada combinação única de caminho e tipo nos seus dados JSON pode se tornar seu próprio arquivo de coluna em disco. Por exemplo, quando dois caminhos JSON são inseridos com tipos diferentes, o ClickHouse armazena os valores de cada tipo concreto em subcolunas distintas. Essas subcolunas podem ser acessadas de forma independente, minimizando I/O desnecessário. Observe que, ao consultar uma coluna com vários tipos, os valores ainda são retornados como uma única resposta colunar. Além disso, ao usar offsets, o ClickHouse garante que essas subcolunas permaneçam densas, sem armazenar valores padrão para caminhos JSON ausentes. Essa abordagem maximiza a compactação e reduz ainda mais o I/O. No entanto, em cenários com estruturas JSON de alta cardinalidade ou muito variáveis, como pipelines de telemetria, logs ou feature stores de machine learning, esse comportamento pode levar a uma explosão no número de arquivos de coluna. Cada novo caminho JSON único resulta em um novo arquivo de coluna, e cada variante de tipo nesse caminho resulta em um arquivo de coluna adicional. Embora isso seja ideal para o desempenho de leitura, também introduz desafios operacionais: esgotamento de descritores de arquivo, aumento do uso de memória e merges mais lentos devido ao grande número de arquivos pequenos. Para mitigar isso, o ClickHouse introduz o conceito de subcoluna de overflow: quando o número de caminhos JSON distintos ultrapassa um limite, os caminhos adicionais são armazenados em um único arquivo compartilhado usando um formato compacto codificado. Esse arquivo ainda pode ser consultado, mas não se beneficia das mesmas características de desempenho das subcolunas dedicadas. Esse limite é controlado pelo parâmetro max_dynamic_paths na declaração do tipo JSON.
Evite definir esse parâmetro com um valor alto demais - valores altos aumentam o consumo de recursos e reduzem a eficiência. Como regra geral, mantenha-o abaixo de 10.000. Para cargas de trabalho com estruturas muito dinâmicas, use type hints e parâmetros SKIP para restringir o que é armazenado. Para quem tiver curiosidade sobre a implementação desse novo tipo de coluna, recomendamos a leitura do nosso post detalhado no blog “A New Powerful JSON Data Type for ClickHouse”.
Última modificação em 3 de julho de 2026