JSON statique ou dynamique
- Types primitifs - Si la valeur de la clé est d’un type primitif, qu’elle fasse partie d’un sous-objet ou qu’elle se trouve à la racine, veillez à choisir son type conformément aux bonnes pratiques générales de conception de schéma et aux règles d’optimisation des types. Les tableaux de primitives, comme
phone_numbersci-dessous, peuvent être modélisés sous la formeArray(<type>), par exempleArray(String). - Statique ou dynamique - Si la valeur de la clé est un objet complexe, c.-à-d. soit un objet, soit un tableau d’objets, déterminez si elle est susceptible d’évoluer. Les objets qui reçoivent rarement de nouvelles clés, pour lesquels l’ajout d’une nouvelle clé peut être anticipé et géré par une modification du schéma via
ALTER TABLE ADD COLUMN, peuvent être considérés comme statiques. Cela inclut les objets pour lesquels seul un sous-ensemble des clés peut être fourni dans certains documents JSON. Les objets auxquels de nouvelles clés sont ajoutées fréquemment et/ou de manière imprévisible doivent être considérés comme dynamiques. L’exception concerne ici les structures comportant des centaines ou des milliers de sous-clés, qui peuvent être considérées comme dynamiques par souci de simplicité.
- Les clés racines
name,username,email,websitepeuvent être représentées par le typeString. La colonnephone_numbersest une primitiveArrayde typeArray(String), tandis quedobetidsont de typeDateetUInt32, respectivement. - Aucune nouvelle clé ne sera ajoutée à l’objet
address(seuls de nouveaux objets d’adresse pourront l’être) ; il peut donc être considéré comme statique. Si l’on descend récursivement, toutes les sous-colonnes peuvent être considérées comme des primitives (et de typeString), à l’exception degeo. Il s’agit également d’une structure statique avec deux colonnesFloat32,latetlon. - La colonne
tagsest dynamique. Nous supposons que de nouveaux tags arbitraires, de tout type et de toute structure, peuvent être ajoutés à cet objet. - L’objet
companyest statique et contiendra toujours au plus les 3 clés indiquées. Les sous-clésnameetcatchPhrasesont de typeString. La clélabelsest dynamique. Nous supposons que de nouveaux tags arbitraires peuvent être ajoutés à cet objet. Les valeurs seront toujours des paires clé-valeur de type chaîne.
Les structures comportant des centaines ou des milliers de clés statiques peuvent être considérées comme dynamiques, car il est rarement envisageable d’en déclarer statiquement les colonnes. Cependant, lorsque c’est possible, ignorez les chemins inutiles afin de réduire à la fois le stockage et le surcoût lié à l’inférence.
Gestion des structures statiques
Tuple. Les tableaux d’objets peuvent être stockés sous forme de tableaux de tuples, c’est-à-dire Array(Tuple). Au sein même des tuples, les colonnes et leurs types respectifs doivent être définis selon les mêmes règles. Cela peut conduire à des Tuples imbriqués pour représenter des objets imbriqués, comme illustré ci-dessous.
Pour l’illustrer, nous reprenons l’exemple JSON de personne présenté précédemment, en omettant les objets dynamiques :
company est définie comme un Tuple(catchPhrase String, name String). La clé address utilise un Array(Tuple), avec un Tuple imbriqué pour représenter la colonne geo.
Le JSON peut être inséré dans cette table avec sa structure actuelle :
address.street est renvoyée en tant qu’Array. Pour interroger un objet précis dans un tableau en fonction de sa position, il faut indiquer l’indice du tableau après le nom de la colonne. Par exemple, pour accéder à la rue de la première adresse :
24.12 :
Gestion des valeurs par défaut
Tuple n’exige pas que toutes les colonnes soient présentes dans la charge utile JSON. Si elles ne sont pas fournies, des valeurs par défaut seront utilisées.
Prenons notre table people précédente et le JSON partiel suivant, dans lequel les clés suite, geo, phone_numbers et catchPhrase sont absentes.
Différencier les valeurs vides et nullesSi vous devez distinguer une valeur vide d’une valeur non fournie, vous pouvez utiliser le type Nullable. Cette approche est à éviter sauf en cas de nécessité absolue, car elle a un impact négatif sur le stockage et les performances des requêtes pour ces colonnes.
Gestion des nouvelles colonnes
nickname :
nickname :
ALTER TABLE ADD COLUMN. Une valeur par défaut peut être spécifiée via la clause DEFAULT ; elle sera utilisée si elle n’est pas indiquée lors des insertions suivantes. Les lignes pour lesquelles cette valeur n’est pas présente (car elles ont été insérées avant sa création) renverront également cette valeur par défaut. Si aucune valeur DEFAULT n’est spécifiée, la valeur par défaut du type sera utilisée.
Par exemple :
Gestion des structures semi-structurées/dynamiques
JSON est recommandé.
Plus précisément, utilisez le type JSON lorsque vos données :
- ont des clés imprévisibles susceptibles d’évoluer au fil du temps ;
- contiennent des valeurs de types variables (par exemple, un chemin peut parfois contenir une chaîne, parfois un nombre) ;
- nécessitent une flexibilité de schéma lorsqu’un typage strict n’est pas envisageable ;
- comportent des centaines, voire des milliers de chemins statiques qu’il n’est tout simplement pas réaliste de déclarer explicitement. Ce cas reste toutefois rare.
company.labels a été identifié comme dynamique.
Supposons que company.labels contienne des clés arbitraires. De plus, le type de n’importe quelle clé de cette structure peut ne pas être cohérent d’une ligne à l’autre. Par exemple :
company.labels d’un objet à l’autre, tant du point de vue des clés que des types, nous avons plusieurs options pour modéliser ces données :
- Colonne JSON unique - représente l’ensemble du schéma dans une seule colonne
JSON, ce qui permet à toutes les structures qu’elle contient d’être dynamiques. - Colonne JSON ciblée - utilise uniquement le type
JSONpour la colonnecompany.labels, tout en conservant le schéma structuré présenté ci-dessus pour toutes les autres colonnes.
- Validation des données – l’application d’un schéma strict évite le risque d’explosion du nombre de colonnes, sauf pour certaines structures spécifiques.
- Évite le risque d’explosion du nombre de colonnes - Bien que le type JSON puisse monter jusqu’à des milliers de colonnes, les sous-colonnes étant stockées comme des colonnes dédiées, cela peut entraîner une explosion du nombre de fichiers de colonnes, avec la création d’un nombre excessif de fichiers qui nuit aux performances. Pour atténuer ce problème, le type Dynamic sous-jacent utilisé par JSON propose un paramètre
max_dynamic_paths, qui limite le nombre de chemins uniques stockés dans des fichiers de colonnes distincts. Une fois le seuil atteint, les chemins supplémentaires sont stockés dans un fichier de colonnes partagé à l’aide d’un format compact encodé, ce qui préserve les performances et l’efficacité du stockage tout en prenant en charge une ingestion de données flexible. L’accès à ce fichier de colonnes partagé est toutefois moins performant. Notez toutefois que la colonne JSON peut être utilisée avec des indications de type. Les colonnes « indiquées » offriront les mêmes performances que les colonnes dédiées. - Introspection plus simple des chemins et des types - Bien que le type JSON prenne en charge des fonctions d’introspection pour déterminer les types et les chemins qui ont été inférés, les structures statiques peuvent être plus simples à explorer, par exemple avec
DESCRIBE.
Colonne JSON unique
JSON que pour les sous-structures dynamiques, lorsque c’est nécessaire.
Considérations relatives aux performancesUne colonne JSON unique peut être optimisée en ignorant (c’est-à-dire sans stocker) les chemins JSON qui ne sont pas nécessaires et en utilisant des indications de type. Les indications de type permettent à l’utilisateur de définir explicitement le type d’une sous-colonne, évitant ainsi l’inférence et le traitement de l’indirection au moment de la requête. On peut ainsi obtenir les mêmes performances qu’avec un schéma explicite. Voir « Using type hints and skipping paths » pour plus de détails.
Nous fournissons une indication de type pour la colonne
username dans la définition JSON, car nous l’utilisons dans la clé de tri / clé primaire. Cela permet à ClickHouse de savoir que cette colonne ne sera pas NULL et de déterminer quelle sous-colonne username utiliser (il peut y en avoir plusieurs pour chaque type, ce qui serait sinon ambigu).JSONAsObject :
. p. ex.
NULL.
De plus, une sous-colonne distincte est créée pour les chemins de même type. Par exemple, il existe une sous-colonne pour company.labels.type en String et une autre en Array(Nullable(String)). Bien que les deux soient renvoyées lorsque c’est possible, nous pouvons cibler des sous-colonnes spécifiques à l’aide de la syntaxe .: :
^ est nécessaire. Il s’agit d’un choix de conception destiné à éviter de lire un grand nombre de colonnes, sauf demande explicite. Les objets auxquels on accède sans ^ renverront NULL, comme indiqué ci-dessous :
Colonne JSON ciblée
JSON pour la colonne company.labels.
JSONEachRow :
company.labels.
Utilisation des indications de type et des chemins à ignorer
dissolved, employees et founded dans la colonne JSON company.labels
SKIP et SKIP REGEXP, afin de réduire le stockage et d’éviter une inférence inutile sur des chemins dont nous n’avons pas besoin. Par exemple, supposons que nous utilisions une seule colonne JSON pour les données ci-dessus. Nous pouvons ignorer les chemins address et company :
Optimiser les performances avec des indications de type
Configuration des chemins dynamiques
max_dynamic_paths dans la déclaration du type JSON.
SKIP pour limiter ce qui est stocké.
Pour les utilisateurs qui souhaitent en savoir plus sur l’implémentation de ce nouveau type de colonne, nous recommandons la lecture de notre article de blog détaillé “A New Powerful JSON Data Type for ClickHouse”.