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

# Uso del tipo Map en ClickHouse

> Aprenda a usar el tipo Map en ClickHouse para almacenar, consultar y agregar datos dinámicos de clave-valor, usando atributos de recurso de OTel como ejemplo práctico.

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">Observabilidad</Badge>
  <Badge size="lg" color="orange">OSS</Badge>
</div>

<div id="prerequisites">
  ## Prerrequisitos
</div>

* **clickhouse-local** instalado en su equipo. Consulte la [guía de configuración de clickhouse-local](/docs/es/concepts/features/tools-and-utilities/clickhouse-local) para empezar.

<div id="what-youll-build">
  ## Lo que crearás
</div>

En OpenTelemetry, cada span de traza lleva un conjunto de **atributos de recurso**: metadatos clave-valor que describen la entidad que produjo la telemetría (nombre del servicio, host, región de Cloud, pod de Kubernetes, etc.). El conjunto de claves varía entre servicios y entornos, por lo que encaja de forma natural con el tipo `Map` de ClickHouse: las claves son dinámicas y específicas de la aplicación, pero una fila cualquiera normalmente solo tiene unas pocas.

En esta guía de inicio rápido usarás `clickhouse-local` para cargar datos reales de trazas de OTel desde un archivo CSV a una tabla con columnas `Map(LowCardinality(String), String)`, y aprenderás a consultar, filtrar, agregar y optimizar datos de tipo `Map`.

<Steps titleSize="h3">
  <Step title="Descarga los datos de muestra" id="download-the-sample-data">
    El conjunto de datos contiene 6.120 spans de trazas de OTel exportados de una aplicación de microservicios de demostración. Cada fila incluye las columnas `ResourceAttributes` y `SpanAttributes`, que contienen pares clave-valor dinámicos en forma de mapas JSON.
    Guarda el archivo en un directorio que puedas localizar fácilmente, por ejemplo `~/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>

      Descargar data-otel-traces.csv (2.9 MB)
    </a>

    Este es el aspecto de una sola fila:

    ```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="Cree la tabla y cargue los datos" id="create-the-table-and-load-the-data">
    Inicie `clickhouse-local` y cree la siguiente tabla con un esquema que coincida con el del CSV.
    La columna clave es `ResourceAttributes Map(LowCardinality(String), String)`; se usa `LowCardinality` en el tipo de clave porque las claves de los atributos de OTel provienen de un conjunto relativamente pequeño y repetitivo.

    ```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));
    ```

    Ahora carga el CSV con el motor de tabla `file`. Ajusta la ruta a la ubicación donde guardaste el archivo:

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

    Confirma que se hayan cargado los datos:

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

    Deberías ver 6.120 filas.
  </Step>

  <Step title="Consultar los datos" id="query-the-data">
    **Acceder a una clave específica** — usa la sintaxis de corchetes para obtener un valor del mapa. Si esa clave no existe en una fila concreta, obtendrás el valor predeterminado del tipo de valor (cadena vacía para `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;
    ```

    **Filtrar por un valor de Map** — encuentra todos los spans de un nombre de servicio específico:

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

    **Comprueba si existe una clave** — no todos los spans incluyen metadatos de Kubernetes. Usa `mapContains` para identificar cuáles sí los incluyen:

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

    **Inspecciona todas las claves presentes en el conjunto de datos** — útil para entender qué está generando la instrumentación:

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

    **Desglosa un Map en filas con ARRAY JOIN** â convierte cada par clave-valor en una fila independiente, lo que resulta útil para crear inventarios de atributos o usarlos en paneles:

    ```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;
    ```

    **Filtrar maps con mapFilter** — extrae solo los atributos de Kubernetes de cada 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;
    ```

    **Encuentra spans con errores y su contexto de recurso** — combina filtros de columna comunes con acceso a 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="Agregar en mapas con el combinador -Map" id="aggregate-across-maps-with-the--map-combinator">
    El combinador de agregación `-Map` de ClickHouse permite aplicar cualquier aggregate function a una columna `Map` y hacer que opere sobre cada clave de forma independiente. El resultado es también un `Map`: una entrada por clave, con el valor agregado. Esto resulta especialmente útil para las metrics de OTel, donde los counters o gauges se almacenan como valores de mapas.

    Para demostrarlo, crea una pequeña table de metrics en la que cada fila registre los recuentos de códigos de estado HTTP como un `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});
    ```

    Ahora usa `sumMap` para sumar el recuento por código de estado de cada servicio:

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

    El sufijo `-Map` funciona con cualquier función de agregación, por lo que puedes usar `minMap`, `maxMap` o `avgMap` igual de fácilmente:

    ```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;
    ```

    También puedes combinarlo con otros combinadores. Por ejemplo, `sumMapIf` te permite agregar de forma condicional: aquí, solo se suman las ventanas de un minuto en las que el servicio ya tenía errores:

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

    **Por qué esto es importante para OTel:** Cuando tu OTel Collector escribe en ClickHouse desgloses por minuto de códigos de estado, `sumMap` te permite consolidarlos en totales por hora o por día en una sola consulta — sin `ARRAY JOIN`, sin despivotar y sin necesidad de conocer de antemano el conjunto completo de claves. Cualquier clave que aparezca en cualquier fila se incluye automáticamente en el resultado.
  </Step>

  <Step title="Optimiza para las claves que se consultan con frecuencia" id="optimise-for-frequently-queried-keys">
    Si ves que filtras constantemente por la misma clave del `map` — `host.name` es una de las más comunes — puedes extraerla como una columna materializada. Esto evita el escaneo lineal por el `map` en cada consulta:

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

    Para los datos existentes, rellene la columna retroactivamente:

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

    Ahora `WHERE HostName = 'prod-cart-01'` lee una única columna dedicada en lugar del mapa completo. Este es el patrón recomendado en el esquema de OTel de ClickHouse para cualquier atributo que consultes con frecuencia.
  </Step>
</Steps>

<div id="key-takeaways">
  ## Puntos clave
</div>

* **`Map(LowCardinality(String), String)`** es el tipo más idiomático para los atributos de OTel: ofrece la flexibilidad necesaria para manejar conjuntos de claves variables, y `LowCardinality` mantiene eficiente el almacenamiento de las claves.
* **La sintaxis con corchetes** (`map['key']`) es la forma más habitual de acceder a los valores, pero recuerda que hace un escaneo lineal: funciona bien para mapas con decenas de claves, pero no es lo ideal para cientos.
* **Las columnas materializadas** son la vía de escape: cuando una clave de mapa se convierte en un filtro frecuente, promuévela a una columna real para obtener acceso indexado y columnar.
* **`mapContains`, `mapKeys`, `mapValues`, `mapFilter`** y `ARRAY JOIN` te proporcionan un completo conjunto de herramientas para explorar y transformar datos de tipo Map sin salir de SQL.
* **El combinador de agregación `-Map`** (`sumMap`, `avgMap`, `maxMap`, etc.) agrega cada clave de forma independiente en todas las filas: es ideal para consolidar contadores de métricas de OTel sin necesidad de conocer de antemano el conjunto de claves. También se puede combinar con otros combinadores (por ejemplo, `sumMapIf`).

<div id="next-steps">
  ## Próximos pasos
</div>

A continuación, consulta estas guías de inicio rápido:

* [Crea tu primera tabla MergeTree](/docs/es/get-started/quickstarts/create-your-first-mergetree-table)
* [Crea tu primera vista materializada](/docs/es/get-started/quickstarts/create-your-first-materialized-view)
* [Problemas comunes al comenzar](/docs/es/get-started/quickstarts/home)

O profundiza con la documentación de referencia:

* [Referencia del tipo Map](/docs/es/reference/data-types/map)
* [Exportador OTel de ClickHouse](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter)
* [Combinadores de funciones de agregación](/docs/es/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>
