JSON estático vs. dinâmico
- 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_numbersabaixo, podem ser modelados comoArray(<type>), por exemplo,Array(String). - 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.
- As chaves de nível raiz
name,username,emailewebsitepodem ser representadas como do tipoString. A colunaphone_numbersé um Array primitivo do tipoArray(String), enquantodobeidsão dos tiposDateeUInt32, 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 tipoString), excetogeo. Esta também é uma estrutura estática com duas colunasFloat32,latelon. - 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 subchavesnameecatchPhrasesão do tipoString. A chavelabelsé 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
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:
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:
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:
24.12:
Tratando valores padrão
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.
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
nickname:
nickname:
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
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.
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:
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
JSONapenas para a colunacompany.labels, mantendo o esquema estruturado usado acima para todas as outras colunas.
- 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
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.
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).JSONAsObject:
.; por exemplo:
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 .::
^ é 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
JSON para a coluna company.labels.
JSONEachRow:
company.labels.
Usando type hints e ignorando caminhos
dissolved, employees e founded dentro da coluna JSON company.labels
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:
Otimizando o desempenho com type hints
Configurando caminhos dinâmicos
max_dynamic_paths na declaração do tipo JSON.
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”.