> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Gestion des données

> Gestion des données pour l’observabilité

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

Les déploiements de ClickHouse for Observability impliquent invariablement de grands volumes de données qui doivent être gérés. ClickHouse offre un certain nombre de fonctionnalités pour faciliter cette gestion.

<Tip>
  **ClickStack inclut un schéma par défaut optimisé**

  **ClickStack fournit des schémas prêts à l'emploi pour les logs, les traces et les métriques** qui intègrent les dernières fonctionnalités de ClickHouse (index textuels pour la recherche en texte intégral et sur les clés de Map, colonnes matérialisées et arrays ALIAS pour le filtrage en lecture directe, recherches de lignes par numéro de bloc) et ont été benchmarkés pour offrir de solides performances prêtes à l'emploi pour les charges de travail de logs et de traces. Servez-vous-en comme point de référence pour votre propre conception.

  * DDL canonique : [Tables et schémas utilisés par ClickStack](/docs/fr/clickstack/ingesting-data/schemas).
  * Recettes d'optimisation : [Réglage des performances de ClickStack](/docs/fr/clickstack/managing/performance-tuning). Bon nombre des recommandations de cette page (colonnes matérialisées, skip indexes, choix de la clé primaire, projections, vues matérialisées) s'appliquent directement à une configuration personnalisée.
</Tip>

<div id="partitions">
  ## Partitions
</div>

Le partitionnement dans ClickHouse permet de séparer logiquement les données sur le disque selon une colonne ou une expression SQL. Grâce à cette séparation logique, chaque partition peut être manipulée indépendamment, par exemple supprimée. Cela permet de déplacer efficacement des partitions, et donc des sous-ensembles, entre les niveaux de stockage en fonction du temps, ou d’[expirer des données/supprimer efficacement depuis un cluster](/docs/fr/reference/statements/alter/partition).

Le partitionnement est spécifié sur une table lors de sa définition initiale via la clause `PARTITION BY`. Cette clause peut contenir une expression SQL portant sur une ou plusieurs colonnes, dont le résultat détermine vers quelle partition une ligne est envoyée.

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-14.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=291ec5afa51f05bce8e8817d3999b9a5" alt="Partitions" size="md" width="1600" height="1077" data-path="images/use-cases/observability/observability-14.webp" />

Les data parts sont logiquement associées (via un préfixe commun de nom de dossier) à chaque partition sur le disque et peuvent être interrogées séparément. Dans l’exemple ci-dessous, le schéma `otel_logs` par défaut effectue un partitionnement par jour à l’aide de l’expression `toDate(Timestamp)`. À mesure que des lignes sont insérées dans ClickHouse, cette expression est évaluée pour chaque ligne, qui est ensuite dirigée vers la partition correspondante si elle existe (si la ligne est la première d’un jour donné, la partition sera créée).

```sql theme={null}
CREATE TABLE default.otel_logs
(
...
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId)
```

Un [certain nombre d’opérations](/docs/fr/reference/statements/alter/partition) peuvent être effectuées sur les partitions, notamment des [sauvegardes](/docs/fr/reference/statements/alter/partition#freeze-partition), des [opérations sur les colonnes](/docs/fr/reference/statements/alter/partition#clear-column-in-partition), des mutations [modifiant](/docs/fr/reference/statements/alter/partition#update-in-partition)/[supprimant](/docs/fr/reference/statements/alter/partition#delete-in-partition) des données ligne par ligne) et la [suppression d’index (p. ex. d’index secondaires)](/docs/fr/reference/statements/alter/partition#clear-index-in-partition).

À titre d’exemple, supposons que notre table `otel_logs` soit partitionnée par jour. Si elle est alimentée avec le jeu de données de logs structurés, elle contiendra plusieurs jours de données :

```sql theme={null}
SELECT Timestamp::Date AS day,
         count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 2333977 │
│ 2019-01-23 │ 2326694 │
│ 2019-01-26 │ 1986456 │
│ 2019-01-24 │ 1896255 │
│ 2019-01-25 │ 1821770 │
└────────────┴─────────┘

5 rows in set. Elapsed: 0.058 sec. Processed 10.37 million rows, 82.92 MB (177.96 million rows/s., 1.42 GB/s.)
Peak memory usage: 4.41 MiB.
```

Vous pouvez obtenir la liste des partitions actuelles à l’aide d’une simple requête sur une table système :

```sql theme={null}
SELECT DISTINCT partition
FROM system.parts
WHERE `table` = 'otel_logs'
```

```response theme={null}
┌─partition──┐
│ 2019-01-22 │
│ 2019-01-23 │
│ 2019-01-24 │
│ 2019-01-25 │
│ 2019-01-26 │
└────────────┘

5 rows in set. Elapsed: 0.005 sec.
```

Nous pouvons avoir une autre table, `otel_logs_archive`, utilisée pour stocker les données plus anciennes. Les données peuvent être déplacées efficacement vers cette table au niveau de la partition (il s’agit simplement d’une modification des métadonnées).

```sql theme={null}
CREATE TABLE otel_logs_archive AS otel_logs
--move data to archive table
ALTER TABLE otel_logs
        (MOVE PARTITION tuple('2019-01-26') TO TABLE otel_logs_archive
--confirm data has been moved
SELECT
        Timestamp::Date AS day,
        count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 2333977 │
│ 2019-01-23 │ 2326694 │
│ 2019-01-24 │ 1896255 │
│ 2019-01-25 │ 1821770 │
└────────────┴─────────┘

4 rows in set. Elapsed: 0.051 sec. Processed 8.38 million rows, 67.03 MB (163.52 million rows/s., 1.31 GB/s.)
Peak memory usage: 4.40 MiB.
```

```sql theme={null}
SELECT Timestamp::Date AS day,
        count() AS c
FROM otel_logs_archive
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-26 │ 1986456 │
└────────────┴─────────┘

1 row in set. Elapsed: 0.024 sec. Processed 1.99 million rows, 15.89 MB (83.86 million rows/s., 670.87 MB/s.)
Peak memory usage: 4.99 MiB.
```

À l’inverse d’autres techniques, celle-ci nécessiterait l’utilisation d’un `INSERT INTO SELECT` ainsi qu’une réécriture des données dans la nouvelle table cible.

<Info>
  **Déplacement de partitions**

  [Le déplacement de partitions entre tables](/docs/fr/reference/statements/alter/partition#move-partition-to-table) impose que plusieurs conditions soient remplies, notamment que les tables aient la même structure, la même clé de partition, la même clé primaire et les mêmes index/projections. Des notes détaillées sur la façon de spécifier les partitions dans le DDL `ALTER` sont disponibles [ici](/docs/fr/reference/statements/alter/partition#how-to-set-partition-expression).
</Info>

En outre, les données peuvent être supprimées efficacement par partition. Cette méthode est bien plus économe en ressources que les techniques alternatives (mutations ou lightweight deletes) et doit être privilégiée.

```sql theme={null}
ALTER TABLE otel_logs
        (DROP PARTITION tuple('2019-01-25'))

SELECT
        Timestamp::Date AS day,
        count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 4667954 │
│ 2019-01-23 │ 4653388 │
│ 2019-01-24 │ 3792510 │
└────────────┴─────────┘
```

<Note>
  Cette fonctionnalité est utilisée par TTL lorsque le paramètre [`ttl_only_drop_parts=1`](/docs/fr/reference/settings/merge-tree-settings#ttl_only_drop_parts) est activé. Voir [Gestion des données avec TTL](#data-management-with-ttl-time-to-live) pour en savoir plus.
</Note>

<div id="applications">
  ### Applications
</div>

Ce qui précède illustre comment les données peuvent être déplacées et manipulées efficacement au niveau des partitions. En pratique, c’est le plus souvent dans des cas d’usage d’observabilité que vous exploiterez les opérations sur les partitions, selon deux scénarios :

* **Architectures à niveaux** - déplacement des données entre les niveaux de stockage (voir [Niveaux de stockage](#storage-tiers)), ce qui permet de mettre en place des architectures hot-cold.
* **Suppression efficace** - lorsque les données atteignent le TTL défini (voir [Gestion des données avec TTL](#data-management-with-ttl-time-to-live))

Nous examinons ces deux scénarios en détail ci-dessous.

<div id="query-performance">
  ### Performances des requêtes
</div>

Bien que les partitions puissent contribuer aux performances des requêtes, cela dépend fortement des modes d’accès aux données. Si les requêtes ne ciblent qu’un petit nombre de partitions (idéalement une seule), les performances peuvent s’en trouver améliorées. Cela n’est généralement utile que si la clé de partitionnement ne fait pas partie de la clé primaire et que vous l’utilisez comme filtre. En revanche, les requêtes qui doivent couvrir un grand nombre de partitions peuvent être moins performantes qu’en l’absence de partitionnement (car il peut alors y avoir davantage de parts). L’intérêt de cibler une seule partition sera encore plus limité, voire nul, si la clé de partitionnement figure déjà au début de la clé primaire. Le partitionnement peut également être utilisé pour [optimiser les requêtes GROUP BY](/docs/fr/reference/engines/table-engines/mergetree-family/custom-partitioning-key#group-by-optimisation-using-partition-key) si les valeurs de chaque partition sont uniques. Cependant, de façon générale, vous devez vous assurer que la clé primaire est optimisée et n’envisager le partitionnement comme technique d’optimisation des requêtes que dans des cas exceptionnels, lorsque les modes d’accès ne concernent qu’un sous-ensemble spécifique et prévisible des données, par exemple un partitionnement par jour lorsque la plupart des requêtes portent sur le dernier jour. Voir [ici](https://medium.com/datadenys/using-partitions-in-clickhouse-3ea0decb89c4) un exemple de ce comportement.

<div id="data-management-with-ttl-time-to-live">
  ## Gestion des données avec TTL (Time-to-live)
</div>

Le Time-to-Live (TTL) est une fonctionnalité clé des solutions d’observabilité basées sur ClickHouse pour assurer une rétention et une gestion efficaces des données, en particulier compte tenu des volumes considérables de données générés en continu. La mise en œuvre du TTL dans ClickHouse permet l’expiration et la suppression automatiques des données les plus anciennes, garantissant ainsi une utilisation optimale du stockage et le maintien des performances sans intervention manuelle. Cette fonctionnalité est essentielle pour alléger la base de données, réduire les coûts de stockage et garantir que les requêtes restent rapides et efficaces en se concentrant sur les données les plus pertinentes et les plus récentes. En outre, elle contribue au respect des politiques de rétention des données en gérant systématiquement le cycle de vie des données, renforçant ainsi la durabilité et la capacité de passage à l’échelle globales de la solution d’observabilité.

Le TTL peut être spécifié au niveau de la table ou de la colonne dans ClickHouse.

<div id="table-level-ttl">
  ### TTL au niveau de la table
</div>

Le schéma par défaut des logs et des traces inclut un TTL pour faire expirer les données après une période donnée. Ce paramètre est défini dans le ClickHouse exporter sous une clé `ttl`, par exemple.

```yaml theme={null}
exporters:
 clickhouse:
   endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
   ttl: 72h
```

Cette syntaxe prend actuellement en charge la [syntaxe Duration de Go](https://pkg.go.dev/time#ParseDuration). **Nous recommandons d’utiliser `h` et de veiller à ce que cette valeur corresponde à la période de partitionnement. Par exemple, si vous partitionnez par jour, assurez-vous qu’il s’agit d’un multiple de jours, p. ex. 24h, 48h, 72h.** Cela garantit automatiquement l’ajout d’une clause TTL à la table, p. ex. avec `ttl: 96h`.

```sql theme={null}
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId)
TTL toDateTime(Timestamp) + toIntervalDay(4)
SETTINGS ttl_only_drop_parts = 1
```

Par défaut, les données dont le TTL a expiré sont supprimées lorsque ClickHouse [fusionne des data parts](/docs/fr/reference/engines/table-engines/mergetree-family/mergetree#mergetree-data-storage). Lorsque ClickHouse détecte que des données ont expiré, il effectue une fusion hors cycle.

<Info>
  **TTLs planifiés**

  Les TTL ne sont pas appliqués immédiatement, mais selon une planification, comme indiqué ci-dessus. Le paramètre de table MergeTree `merge_with_ttl_timeout` définit le délai minimal, en secondes, avant de relancer une fusion avec un TTL de suppression. La valeur par défaut est de 14 400 secondes (4 heures). Mais il ne s'agit que du délai minimal : il peut s'écouler plus de temps avant qu'une fusion TTL ne soit déclenchée. Si la valeur est trop basse, de nombreuses fusions hors cycle pourront être effectuées, ce qui peut consommer beaucoup de ressources. Il est possible de forcer l'expiration d'un TTL à l'aide de la commande `ALTER TABLE my_table MATERIALIZE TTL`.
</Info>

\*\*Important : nous recommandons d'utiliser le paramètre [`ttl_only_drop_parts=1`](/docs/fr/reference/settings/merge-tree-settings#ttl_only_drop_parts) \*\* (appliqué par le schéma par défaut). Lorsque ce paramètre est activé, ClickHouse supprime une part entière lorsque toutes les lignes qu'elle contient ont expiré. Supprimer des parts entières au lieu de nettoyer partiellement les lignes expirées par le TTL (au moyen de mutations gourmandes en ressources lorsque `ttl_only_drop_parts=0`) permet d'utiliser des valeurs `merge_with_ttl_timeout` plus courtes et de réduire l'impact sur les performances du système. Si les données sont partitionnées selon la même unité que celle utilisée pour l'expiration TTL, par exemple le jour, les parts ne contiendront naturellement que des données de l'intervalle défini. Cela garantit que `ttl_only_drop_parts=1` puisse être appliqué efficacement.

<div id="column-level-ttl">
  ### TTL au niveau de la colonne
</div>

L’exemple ci-dessus fait expirer les données au niveau de la table. Vous pouvez également faire expirer les données au niveau de la colonne. À mesure que les données vieillissent, cela permet de supprimer les colonnes dont la valeur pour les investigations ne justifie plus le surcoût en ressources lié à leur conservation. Par exemple, nous recommandons de conserver la colonne `Body` au cas où de nouvelles métadonnées dynamiques seraient ajoutées sans avoir été extraites au moment de l’insertion, par exemple un nouveau label Kubernetes. Après un certain temps, par exemple 1 mois, il peut devenir évident que ces métadonnées supplémentaires ne sont pas utiles, ce qui réduit l’intérêt de conserver la colonne `Body`.

Ci-dessous, nous montrons comment supprimer la colonne `Body` après 30 jours.

```sql theme={null}
CREATE TABLE otel_logs_v2
(
        `Body` String TTL Timestamp + INTERVAL 30 DAY,
        `Timestamp` DateTime,
        ...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
```

<Note>
  Pour spécifier un TTL au niveau d’une colonne, les utilisateurs doivent définir leur propre schéma. Cela ne peut pas être configuré dans l’OTel collector.
</Note>

<div id="recompressing-data">
  ## Recompression des données
</div>

Bien que nous recommandions généralement `ZSTD(1)` pour les jeux de données d'observability, vous pouvez tester différents algorithmes de compression ou des niveaux de compression plus élevés, par exemple `ZSTD(3)`. Outre la possibilité de le spécifier lors de la création du schéma, la compression peut être configurée pour changer après une période définie. Cela peut être pertinent si un codec ou un algorithme de compression améliore la compression, mais dégrade les performances des requêtes. Ce compromis peut être acceptable pour des données plus anciennes, qui sont interrogées moins fréquemment, mais pas pour des données récentes, davantage utilisées lors des investigations.

Un exemple est présenté ci-dessous, où nous compressons les données avec `ZSTD(3)` après 4 jours au lieu de les supprimer.

```sql theme={null}
CREATE TABLE default.otel_logs_v2
(
        `Body` String,
        `Timestamp` DateTime,
        `ServiceName` LowCardinality(String),
        `Status` UInt16,
        `RequestProtocol` LowCardinality(String),
        `RunTime` UInt32,
        `Size` UInt32,
        `UserAgent` String,
        `Referer` String,
        `RemoteUser` String,
        `RequestType` LowCardinality(String),
        `RequestPath` String,
        `RemoteAddress` IPv4,
        `RefererDomain` String,
        `RequestPage` String,
        `SeverityText` LowCardinality(String),
        `SeverityNumber` UInt8,
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
TTL Timestamp + INTERVAL 4 DAY RECOMPRESS CODEC(ZSTD(3))
```

<Info>
  **Évaluer les performances**

  Nous recommandons aux utilisateurs d’évaluer toujours l’impact des différents niveaux et algorithmes de compression sur les performances d’insertion et de requête. Par exemple, les codecs delta peuvent être utiles pour compresser les horodatages. Toutefois, si ces horodatages font partie de la clé primaire, les performances de filtrage peuvent en pâtir.
</Info>

Vous trouverez [ici](/docs/fr/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-multiple-volumes) plus de détails et des exemples sur la configuration du TTL. Des exemples montrant comment ajouter et modifier des TTL pour des tables et des colonnes sont disponibles [ici](/docs/fr/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-ttl). Pour savoir comment les TTL permettent de mettre en place des hiérarchies de stockage, comme les architectures hot-warm, consultez [niveau de stockage](#storage-tiers).

<div id="storage-tiers">
  ## Niveaux de stockage
</div>

Dans ClickHouse, vous pouvez créer des niveaux de stockage sur différents disques, par exemple avec les données chaudes/récentes sur SSD et les données plus anciennes sur S3. Cette architecture permet d'utiliser un stockage moins coûteux pour les données anciennes, qui peuvent avoir des SLA de requête plus souples en raison de leur utilisation moins fréquente lors des investigations.

<Info>
  **Ne s'applique pas à ClickHouse Cloud**

  ClickHouse Cloud utilise une copie unique des données stockée sur S3, avec des caches de nœud sur SSD. Les niveaux de stockage ne sont donc pas nécessaires dans ClickHouse Cloud.
</Info>

La création de niveaux de stockage nécessite que les utilisateurs créent des disques, qui servent ensuite à définir des politiques de stockage, avec des volumes pouvant être spécifiés lors de la création de la table. Les données peuvent être déplacées automatiquement entre les disques en fonction des taux de remplissage, de la taille des parts et des priorités des volumes. Vous trouverez plus de détails [ici](/docs/fr/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-multiple-volumes).

Bien que les données puissent être déplacées manuellement entre les disques à l'aide de la commande `ALTER TABLE MOVE PARTITION`, le déplacement des données entre les volumes peut également être contrôlé à l'aide des TTL. Un exemple complet est disponible [ici](/docs/fr/concepts/features/operations/delete/ttl#implementing-a-hotwarmcold-architecture).

<div id="managing-schema-changes">
  ## Gérer les changements de schéma
</div>

Les schémas des logs et des traces évolueront inévitablement au cours de la durée de vie d’un système, par exemple lorsque les utilisateurs surveillent de nouveaux systèmes avec des métadonnées ou des libellés de pod différents. En produisant les données à l’aide du schéma OTel et en capturant les données d’événement d’origine dans un format structuré, les schémas ClickHouse resteront robustes face à ces changements. Cependant, à mesure que de nouvelles métadonnées deviennent disponibles et que les modes d’accès des requêtes évoluent, vous souhaiterez mettre à jour les schémas pour tenir compte de ces évolutions.

Afin d’éviter toute interruption de service lors des changements de schéma, les utilisateurs disposent de plusieurs options, que nous présentons ci-dessous.

<div id="use-default-values">
  ### Utiliser des valeurs par défaut
</div>

Des colonnes peuvent être ajoutées au schéma à l’aide de [`valeurs `DEFAULT\`\`](/docs/fr/reference/statements/create/table#default). La valeur par défaut spécifiée sera utilisée si elle n’est pas fournie lors de l’INSERT.

Les modifications du schéma peuvent être effectuées avant de modifier la logique de transformation d’une vue matérialisée ou la configuration d’un OTel collector, afin que ces nouvelles colonnes soient envoyées.

Une fois le schéma modifié, vous pouvez reconfigurer les OTel collectors. En supposant que les utilisateurs suivent le processus recommandé décrit dans ["Extraction de la structure avec SQL"](/docs/fr/guides/use-cases/observability/build-your-own/schema-design#extracting-structure-with-sql), dans lequel les OTel collectors envoient leurs données vers un moteur de table Null, avec une vue matérialisée chargée d’extraire le schéma cible et d’envoyer les résultats vers une table cible pour le stockage, la vue peut être modifiée à l’aide de la syntaxe [`ALTER TABLE ... MODIFY QUERY` syntax](/docs/fr/reference/statements/alter/view). Supposons que nous ayons la table cible ci-dessous avec la vue matérialisée correspondante (semblable à celle utilisée dans "Extraction de la structure avec SQL") pour extraire le schéma cible à partir des logs structurés OTel :

```sql theme={null}
CREATE TABLE default.otel_logs_v2
(
        `Body` String,
        `Timestamp` DateTime,
        `ServiceName` LowCardinality(String),
        `Status` UInt16,
        `RequestProtocol` LowCardinality(String),
        `RunTime` UInt32,
        `UserAgent` String,
        `Referer` String,
        `RemoteUser` String,
        `RequestType` LowCardinality(String),
        `RequestPath` String,
        `RemoteAddress` IPv4,
        `RefererDomain` String,
        `RequestPage` String,
        `SeverityText` LowCardinality(String),
        `SeverityNumber` UInt8
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)

CREATE MATERIALIZED VIEW otel_logs_mv TO otel_logs_v2 AS
SELECT
        Body,
        Timestamp::DateTime AS Timestamp,
        ServiceName,
        LogAttributes['status']::UInt16 AS Status,
        LogAttributes['request_protocol'] AS RequestProtocol,
        LogAttributes['run_time'] AS RunTime,
        LogAttributes['user_agent'] AS UserAgent,
        LogAttributes['referer'] AS Referer,
        LogAttributes['remote_user'] AS RemoteUser,
        LogAttributes['request_type'] AS RequestType,
        LogAttributes['request_path'] AS RequestPath,
        LogAttributes['remote_addr'] AS RemoteAddress,
        domain(LogAttributes['referer']) AS RefererDomain,
        path(LogAttributes['request_path']) AS RequestPage,
        multiIf(Status::UInt64 > 500, 'CRITICAL', Status::UInt64 > 400, 'ERROR', Status::UInt64 > 300, 'WARNING', 'INFO') AS SeverityText,
        multiIf(Status::UInt64 > 500, 20, Status::UInt64 > 400, 17, Status::UInt64 > 300, 13, 9) AS SeverityNumber
FROM otel_logs
```

Supposons que nous souhaitions extraire une nouvelle colonne `Size` à partir de `LogAttributes`. Nous pouvons l’ajouter à notre schéma avec un `ALTER TABLE`, en indiquant la valeur par défaut :

```sql theme={null}
ALTER TABLE otel_logs_v2
        (ADD COLUMN `Size` UInt64 DEFAULT JSONExtractUInt(Body, 'size'))
```

Dans l’exemple ci-dessus, nous définissons la valeur par défaut sur la clé `size` dans `LogAttributes` (elle vaudra 0 si elle n’existe pas). Cela signifie que les requêtes qui accèdent à cette colonne pour les lignes où la valeur n’a pas été insérée doivent accéder à la Map et seront donc plus lentes. Nous pourrions tout aussi bien la définir comme une constante, par exemple 0, ce qui réduirait le coût des requêtes ultérieures sur les lignes qui n’ont pas cette valeur. En interrogeant cette table, on constate que la valeur est renseignée comme prévu à partir de la Map :

```sql theme={null}
SELECT Size
FROM otel_logs_v2
LIMIT 5
```

```response theme={null}
┌──Size─┐
│ 30577 │
│  5667 │
│  5379 │
│  1696 │
│ 41483 │
└───────┘

5 rows in set. Elapsed: 0.012 sec.
```

Pour garantir que cette valeur soit insérée dans toutes les nouvelles données, nous pouvons modifier notre vue matérialisée à l’aide de la syntaxe `ALTER TABLE`, comme indiqué ci-dessous :

```sql theme={null}
ALTER TABLE otel_logs_mv
        MODIFY QUERY
SELECT
        Body,
        Timestamp::DateTime AS Timestamp,
        ServiceName,
        LogAttributes['status']::UInt16 AS Status,
        LogAttributes['request_protocol'] AS RequestProtocol,
        LogAttributes['run_time'] AS RunTime,
        LogAttributes['size'] AS Size,
        LogAttributes['user_agent'] AS UserAgent,
        LogAttributes['referer'] AS Referer,
        LogAttributes['remote_user'] AS RemoteUser,
        LogAttributes['request_type'] AS RequestType,
        LogAttributes['request_path'] AS RequestPath,
        LogAttributes['remote_addr'] AS RemoteAddress,
        domain(LogAttributes['referer']) AS RefererDomain,
        path(LogAttributes['request_path']) AS RequestPage,
        multiIf(Status::UInt64 > 500, 'CRITICAL', Status::UInt64 > 400, 'ERROR', Status::UInt64 > 300,                 'WARNING', 'INFO') AS SeverityText,
        multiIf(Status::UInt64 > 500, 20, Status::UInt64 > 400, 17, Status::UInt64 > 300, 13, 9) AS SeverityNumber
FROM otel_logs
```

Les lignes suivantes auront la colonne `Size` renseignée lors de l’insertion.

<div id="create-new-tables">
  ### Créer de nouvelles tables
</div>

Comme alternative au processus ci-dessus, vous pouvez simplement créer une nouvelle table cible avec le nouveau schéma. Toute vue matérialisée peut ensuite être modifiée pour utiliser la nouvelle table à l’aide de l’instruction `ALTER TABLE MODIFY QUERY` ci-dessus. Avec cette approche, vous pouvez versionner vos tables, par exemple `otel_logs_v3`.

Cette approche laisse toutefois aux utilisateurs plusieurs tables à interroger. Pour interroger plusieurs tables, vous pouvez utiliser la [fonction `merge`](/docs/fr/reference/functions/table-functions/merge), qui accepte des motifs génériques pour le nom de la table. Nous le montrons ci-dessous en interrogeant les versions v2 et v3 de la table `otel_logs` :

```sql theme={null}
SELECT Status, count() AS c
FROM merge('otel_logs_v[2|3]')
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 38319300 │
│   304  │  1360912 │
│   302  │   799340 │
│   404  │   420044 │
│   301  │   270212 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.137 sec. Processed 41.46 million rows, 82.92 MB (302.43 million rows/s., 604.85 MB/s.)
```

Si les utilisateurs souhaitent éviter d’utiliser la fonction `merge` et exposer aux utilisateurs finaux une table combinant plusieurs tables, le [moteur de table Merge](/docs/fr/reference/engines/table-engines/special/merge) peut être utilisé. Nous l’illustrons ci-dessous :

```sql theme={null}
CREATE TABLE otel_logs_merged
ENGINE = Merge('default', 'otel_logs_v[2|3]')

SELECT Status, count() AS c
FROM otel_logs_merged
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 38319300 │
│   304  │  1360912 │
│   302  │   799340 │
│   404  │   420044 │
│   301  │   270212 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.073 sec. Processed 41.46 million rows, 82.92 MB (565.43 million rows/s., 1.13 GB/s.)
```

Cela peut être mis à jour chaque fois qu’une nouvelle table est ajoutée à l’aide de la syntaxe de table `EXCHANGE`. Par exemple, pour ajouter une table v4, nous pouvons créer une nouvelle table et l’échanger de façon atomique avec la version précédente.

```sql theme={null}
CREATE TABLE otel_logs_merged_temp
ENGINE = Merge('default', 'otel_logs_v[2|3|4]')

EXCHANGE TABLE otel_logs_merged_temp AND otel_logs_merged

SELECT Status, count() AS c
FROM otel_logs_merged
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 39259996 │
│   304  │  1378564 │
│   302  │   820118 │
│   404  │   429220 │
│   301  │   276960 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.068 sec. Processed 42.46 million rows, 84.92 MB (620.45 million rows/s., 1.24 GB/s.)
```
