> ## 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.

# Utilisation du type Map dans ClickHouse

> Découvrez comment utiliser le type Map dans ClickHouse pour stocker, interroger et agréger des données clé-valeur dynamiques, en prenant comme exemple pratique les attributs de ressource OTel.

export const e_1 = undefined

export const e_0 = undefined

<a href="/docs/get-started/quickstarts/home" onClick={(e_0) => { e_0.preventDefault(); window.location.href = (window.location.pathname.startsWith('/docs') ? '/docs' : '') + '/get-started/quickstarts/home'; }} className="inline-flex items-center gap-1.5 text-sm text-gray-500 dark:text-zinc-500 hover:text-gray-900 dark:hover:text-[#fdff75] transition-colors font-normal no-underline"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="shrink-0"><path d="M19 12H5" /><path d="M12 19l-7-7 7-7" /></svg>All quickstarts</a>

<div className="mt-2 flex flex-wrap gap-2">
  <Badge size="lg" color="blue">Observability</Badge>
  <Badge size="lg" color="orange">OSS</Badge>
</div>

<div id="prerequisites">
  ## Prérequis
</div>

* **clickhouse-local** installé sur votre machine. Consultez le [guide d’installation de clickhouse-local](/docs/fr/concepts/features/tools-and-utilities/clickhouse-local) pour démarrer.

<div id="what-youll-build">
  ## Ce que vous allez créer
</div>

Dans OpenTelemetry, chaque span de trace contient un ensemble d’**attributs de ressource** — des métadonnées clé-valeur qui décrivent l’entité ayant produit la télémétrie (nom du service, hôte, région cloud, pod Kubernetes, etc.). L’ensemble des clés varie selon les services et les environnements, ce qui en fait un cas d’usage naturel pour le type `Map` de ClickHouse : les clés sont dynamiques et spécifiques à l’application, mais une ligne donnée n’en contient généralement qu’une poignée.

Dans ce guide de démarrage rapide, vous utiliserez `clickhouse-local` pour charger de vraies données de trace OTel à partir d’un fichier CSV dans une table comportant des colonnes `Map(LowCardinality(String), String)`, et vous apprendrez à interroger, filtrer, agréger et optimiser des données de type map.

<Steps titleSize="h3">
  <Step title="Télécharger les données d’exemple" id="download-the-sample-data">
    Le jeu de données contient 6 120 spans de traces OTel exportés à partir d’une application de microservices de démonstration. Chaque ligne comprend une colonne `ResourceAttributes` et une colonne `SpanAttributes` contenant des paires clé-valeur dynamiques sous forme de mappages JSON.
    Enregistrez le fichier dans un répertoire facile à retrouver, par exemple `~/data/data-otel-traces.csv`.

    <a href="https://clickhouse-docs-assets.s3.us-east-1.amazonaws.com/data-otel-traces.csv" download className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-lg border border-gray-300 dark:border-white/20 bg-white dark:bg-[#1B1B18] text-black dark:text-white hover:border-[#FAFF69] transition-all no-underline mb-4">
      <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
        <path d="M8 1v10M8 11L4.5 7.5M8 11l3.5-3.5M2 13h12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
      </svg>

      Télécharger data-otel-traces.csv (2,9 Mo)
    </a>

    Voici à quoi ressemble une ligne :

    ```response theme={null}
    Timestamp:          2025-12-26 00:00:45.759467000
    TraceId:            0da128e6e3c01bc38b6b43a33e5fa522
    SpanId:             3774f759424e4006
    ParentSpanId:       2fdd1e5b66605098
    SpanName:           orders receive
    SpanKind:           SPAN_KIND_CONSUMER
    ServiceName:        accountingservice
    Duration:           5361
    StatusCode:         STATUS_CODE_UNSET
    ResourceAttributes: {"host.name":"f19476836e47","os.type":"linux","process.pid":"1","process.command_args":"[\"./accountingservice\"]","process.executable.path":"...
    SpanAttributes:     {"network.transport":"tcp","messaging.destination.name":"orders","messaging.kafka.message.offset":"232260","messaging.message.body.size":"216"...
    ```
  </Step>

  <Step title="Créer la table et charger les données" id="create-the-table-and-load-the-data">
    Lancez `clickhouse-local` et créez la table suivante avec un schéma correspondant à celui du CSV.
    La colonne de clé est `ResourceAttributes Map(LowCardinality(String), String)` - `LowCardinality` est utilisé pour le type de clé, car les clés d’attribut OTel sont tirées d’un ensemble relativement restreint et répétitif.

    ```sql highlight={12} theme={null}
    CREATE TABLE otel_traces
    (
        Timestamp          DateTime64(9),
        TraceId            String,
        SpanId             String,
        ParentSpanId       String,
        SpanName           LowCardinality(String),
        SpanKind           LowCardinality(String),
        ServiceName        LowCardinality(String),
        Duration           UInt64,
        StatusCode         LowCardinality(String),
        ResourceAttributes Map(LowCardinality(String), String),
        SpanAttributes     Map(LowCardinality(String), String)
    )
    ENGINE = MergeTree()
    ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp));
    ```

    Chargez maintenant le CSV à l’aide du moteur de table `file`. Adaptez le chemin à l’emplacement où vous avez enregistré le fichier :

    ```sql theme={null}
    INSERT INTO otel_traces
    SELECT * FROM file('~/data/data-otel-traces.csv', CSVWithNames);
    ```

    Vérifiez que les données ont bien été chargées :

    ```sql theme={null}
    SELECT count() FROM otel_traces;
    ```

    Vous devriez voir 6 120 lignes.
  </Step>

  <Step title="Interroger les données" id="query-the-data">
    **Accéder à une clé spécifique** — utilisez la syntaxe entre crochets pour récupérer une valeur dans la map. Si la clé n'existe pas pour une ligne donnée, vous obtenez la valeur par défaut du type de valeur (chaîne vide pour `String`) :

    ```sql theme={null}
    SELECT
        ServiceName,
        SpanName,
        ResourceAttributes['host.name']             AS host,
        ResourceAttributes['k8s.pod.name']          AS pod,
        ResourceAttributes['deployment.environment'] AS env
    FROM otel_traces
    LIMIT 10;
    ```

    **Filtrer par valeur de map** — trouver tous les spans associés à un nom de service donné :

    ```sql theme={null}
    SELECT
        Timestamp,
        SpanName,
        Duration / 1e6 AS duration_ms
    FROM otel_traces
    WHERE ResourceAttributes['service.name'] = 'cartservice'
    ORDER BY Timestamp
    LIMIT 10;
    ```

    **Vérifiez si une clé existe** — tous les spans ne contiennent pas de métadonnées Kubernetes. Utilisez `mapContains` pour repérer ceux qui en ont :

    ```sql theme={null}
    SELECT
        ServiceName,
        SpanName,
        mapContains(ResourceAttributes, 'k8s.node.name') AS has_node_info
    FROM otel_traces
    LIMIT 10;
    ```

    **Inspectez toutes les clés présentes dans le jeu de données** — utile pour comprendre ce que produit l’instrumentation :

    ```sql theme={null}
    SELECT DISTINCT arrayJoin(mapKeys(ResourceAttributes)) AS key
    FROM otel_traces
    ORDER BY key;
    ```

    **Décomposer une map en lignes avec ARRAY JOIN** — transformez chaque paire clé-valeur en ligne distincte, ce qui est pratique pour créer des inventaires d’attributs ou alimenter des tableaux de bord :

    ```sql theme={null}
    SELECT
        ServiceName,
        key,
        value
    FROM otel_traces
    ARRAY JOIN
        mapKeys(ResourceAttributes)  AS key,
        mapValues(ResourceAttributes) AS value
    WHERE ServiceName = 'cartservice'
    LIMIT 20;
    ```

    **Filtrer les maps avec mapFilter** — extrayez uniquement les attributs relatifs à Kubernetes de chaque span :

    ```sql theme={null}
    SELECT
        ServiceName,
        mapFilter((k, v) -> k LIKE 'k8s.%', ResourceAttributes) AS k8s_attrs
    FROM otel_traces
    WHERE mapContains(ResourceAttributes, 'k8s.pod.name')
    LIMIT 10;
    ```

    **Repérez les spans d’erreur et leur contexte de ressource** — combinez des filtres classiques sur les colonnes avec l’accès à la map :

    ```sql theme={null}
    SELECT
        Timestamp,
        ServiceName,
        SpanName,
        ResourceAttributes['host.name']    AS host,
        ResourceAttributes['k8s.pod.name'] AS pod,
        SpanAttributes['error.type']       AS error_type,
        SpanAttributes['error.message']    AS error_message
    FROM otel_traces
    WHERE StatusCode = 'STATUS_CODE_ERROR';
    ```
  </Step>

  <Step title="Agréger des maps avec le combinateur -Map" id="aggregate-across-maps-with-the--map-combinator">
    Le combinateur d’agrégation `-Map` de ClickHouse vous permet d’appliquer n’importe quelle fonction d’agrégation à une colonne `Map` afin qu’elle s’applique indépendamment à chaque clé. Le résultat est lui aussi une `Map` — une entrée par clé, avec la valeur agrégée. C’est particulièrement utile pour les métriques OTel, où les compteurs ou les gauges sont stockés dans des valeurs de map.

    Pour le montrer, créez une petite table de métriques dans laquelle chaque ligne enregistre le nombre de codes d’état HTTP sous forme de `Map(String, UInt64)` :

    ```sql theme={null}
    CREATE TABLE otel_http_status_counts
    (
        Timestamp    DateTime,
        ServiceName  LowCardinality(String),
        StatusCounts Map(String, UInt64)
    )
    ENGINE = MergeTree()
    ORDER BY (ServiceName, Timestamp);

    INSERT INTO otel_http_status_counts VALUES
        ('2025-12-26 10:00:00', 'cart-service',      {'2xx': 150, '4xx': 12, '5xx': 3}),
        ('2025-12-26 10:01:00', 'cart-service',      {'2xx': 200, '4xx': 8,  '5xx': 1}),
        ('2025-12-26 10:00:00', 'inventory-service', {'2xx': 90,  '4xx': 5}),
        ('2025-12-26 10:01:00', 'inventory-service', {'2xx': 110, '4xx': 3,  '5xx': 2}),
        ('2025-12-26 10:00:00', 'payment-service',   {'2xx': 50,  '5xx': 10}),
        ('2025-12-26 10:01:00', 'payment-service',   {'2xx': 45,  '4xx': 2,  '5xx': 15});
    ```

    Utilisez maintenant `sumMap` pour obtenir le total des occurrences par code d’état pour chaque service :

    ```sql theme={null}
    SELECT
        ServiceName,
        sumMap(StatusCounts) AS total_by_status
    FROM otel_http_status_counts
    GROUP BY ServiceName;
    ```

    Le suffixe `-Map` fonctionne avec n’importe quelle fonction d’agrégation ; vous pouvez donc utiliser `minMap`, `maxMap` ou `avgMap` tout aussi facilement :

    ```sql theme={null}
    SELECT
        ServiceName,
        avgMap(StatusCounts) AS avg_by_status,
        maxMap(StatusCounts) AS peak_by_status
    FROM otel_http_status_counts
    GROUP BY ServiceName;
    ```

    Vous pouvez également le combiner avec d’autres combinateurs. Par exemple, `sumMapIf` permet d’agréger de manière conditionnelle — ici, en additionnant uniquement les fenêtres d’une minute où le service présentait déjà des erreurs :

    ```sql theme={null}
    SELECT
        ServiceName,
        sumMapIf(StatusCounts, StatusCounts['5xx'] > 0) AS totals_in_error_windows
    FROM otel_http_status_counts
    GROUP BY ServiceName;
    ```

    **Pourquoi c’est important pour OTel :** lorsque votre OTel Collector écrit dans ClickHouse une ventilation par minute des codes de statut, `sumMap` vous permet de les agréger en totaux horaires ou quotidiens dans une seule requête — sans `ARRAY JOIN`, sans dé-pivotage, et sans avoir à connaître à l’avance l’ensemble des clés. Toute clé présente dans n’importe quelle ligne est automatiquement incluse dans le résultat.
  </Step>

  <Step title="Optimisez les clés fréquemment utilisées dans les requêtes" id="optimise-for-frequently-queried-keys">
    Si vous constatez que vous filtrez constamment sur la même clé de map — `host.name` est un cas courant — vous pouvez l’extraire dans une colonne matérialisée. Cela évite de parcourir linéairement la map à chaque requête :

    ```sql theme={null}
    ALTER TABLE otel_traces
        ADD COLUMN HostName String
        MATERIALIZED ResourceAttributes['host.name'];
    ```

    Pour les données existantes, remplissez la colonne rétroactivement :

    ```sql theme={null}
    ALTER TABLE otel_traces MATERIALIZE COLUMN HostName;
    ```

    Désormais, `WHERE HostName = 'prod-cart-01'` ne lit qu’une seule colonne dédiée au lieu de l’intégralité de la Map. C’est l’approche recommandée dans le schéma OTel de ClickHouse pour tout attribut que vous interrogez fréquemment.
  </Step>
</Steps>

<div id="key-takeaways">
  ## Points clés à retenir
</div>

* **`Map(LowCardinality(String), String)`** est le type idiomatique pour les attributs OTel — suffisamment flexible pour gérer des ensembles de clés variables, et `LowCardinality` permet de stocker efficacement les clés.
* **La syntaxe entre crochets** (`map['key']`) est la façon la plus courante d'accéder aux valeurs, mais gardez à l'esprit qu'elle effectue un balayage linéaire — très bien pour des maps contenant quelques dizaines de clés, moins adapté pour des centaines.
* **Les colonnes matérialisées** sont la solution de secours : lorsqu'une clé de map devient un filtre fréquemment utilisé, faites-en une vraie colonne pour un accès indexé et colonnaire.
* **`mapContains`, `mapKeys`, `mapValues`, `mapFilter`** et `ARRAY JOIN` vous offrent une boîte à outils complète pour explorer et transformer des données de type map sans quitter SQL.
* **Le combinateur d'agrégation `-Map`** (`sumMap`, `avgMap`, `maxMap`, etc.) agrège chaque clé indépendamment d'une ligne à l'autre — idéal pour consolider des compteurs de métriques OTel sans avoir à connaître l'ensemble de clés à l'avance. Il se combine aussi avec d'autres combinateurs (par ex. `sumMapIf`).

<div id="next-steps">
  ## Étapes suivantes
</div>

Poursuivez avec les guides de démarrage rapide suivants :

* [Créer votre première table MergeTree](/docs/fr/get-started/quickstarts/create-your-first-mergetree-table)
* [Créer votre première vue matérialisée](/docs/fr/get-started/quickstarts/create-your-first-materialized-view)
* [Problèmes courants de prise en main](/docs/fr/get-started/quickstarts/home)

Ou allez plus loin avec la documentation de référence :

* [Référence du type Map](/docs/fr/reference/data-types/map)
* [Exporteur OTel ClickHouse](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter)
* [Combinateurs de fonctions d'agrégation](/docs/fr/reference/functions/aggregate-functions/combinators)

<Frame caption="Check out the ClickHouse academy for on-demand and live training">
  <a href="https://learn.clickhouse.com/" target="_blank">
    <img src="https://mintcdn.com/private-7c7dfe99/EDr8ydtGBgFPOQea/images/academy.webp?fit=max&auto=format&n=EDr8ydtGBgFPOQea&q=85&s=27e92fc656183cc2f176211907a7aa49" alt="ClickHouse Academy — Master ClickHouse with expert-designed training for every skill level" width="560" noZoom data-path="images/academy.webp" />
  </a>
</Frame>

<div className="mt-8">
  <a href="/docs/get-started/quickstarts/home" onClick={(e_1) => { e_1.preventDefault(); window.location.href = (window.location.pathname.startsWith('/docs') ? '/docs' : '') + '/get-started/quickstarts/home'; }} className="inline-flex items-center gap-1.5 text-sm text-gray-500 dark:text-zinc-500 hover:text-gray-900 dark:hover:text-[#fdff75] transition-colors font-normal no-underline"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="shrink-0"><path d="M19 12H5" /><path d="M12 19l-7-7 7-7" /></svg>All quickstarts</a>
</div>
