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

# ClickHouse での Map 型の利用

> OTel の リソース属性 を実例に、ClickHouse で動的なキー・バリュー データを保存、クエリ、集計するための Map 型の使い方を学びます。

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">オブザーバビリティ</Badge>
  <Badge size="lg" color="orange">OSS</Badge>
</div>

<div id="prerequisites">
  ## 前提条件
</div>

* **clickhouse-local** がローカル環境にインストールされていること。開始方法については、[clickhouse-local セットアップガイド](/docs/ja/concepts/features/tools-and-utilities/clickhouse-local)を参照してください。

<div id="what-youll-build">
  ## 作成するもの
</div>

OpenTelemetry では、すべてのトレーススパンに **リソース属性** のセットが含まれます。これは、テレメトリーを生成した対象 (サービス名、ホスト、クラウドリージョン、Kubernetes ポッドなど) を表すキー・バリューのメタデータです。キーのセットはサービスや環境によって異なるため、これは ClickHouse の `Map` 型に適しています。キーは動的でアプリケーション固有ですが、通常は 1 行あたり数個しか含まれません。

このクイックスタートでは、`clickhouse-local` を使って CSVファイル から実際の OTel トレースデータを `Map(LowCardinality(String), String)` カラムを持つテーブルに読み込み、Map データのクエリ、フィルタリング、集計、最適化の方法を学びます。

<Steps titleSize="h3">
  <Step title="サンプルデータをダウンロード" id="download-the-sample-data">
    このデータセットには、デモ用マイクロサービスアプリケーションからエクスポートされた 6,120 件の OTel トレーススパンが含まれています。各行には、動的なキー・バリューのペアを JSON map として格納した `ResourceAttributes` カラムと `SpanAttributes` カラムが含まれます。
    ファイルは、たとえば `~/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>

      data-otel-traces.csv をダウンロード (2.9 MB)
    </a>

    1 行分のデータは次のようになります。

    ```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="テーブルを作成してデータを読み込む" id="create-the-table-and-load-the-data">
    `clickhouse-local` を起動し、CSV に対応するスキーマで次のテーブルを作成します。
    重要なカラムは `ResourceAttributes Map(LowCardinality(String), String)` です。OTel の属性キーは、比較的少数の同じ値が繰り返し現れるため、キー型に `LowCardinality` を使用しています。

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

    次に、`file` テーブルエンジンを使って CSV を読み込みます。パスは、ファイルを保存した場所に合わせて調整してください。

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

    データが読み込まれていることを確認します:

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

    6,120行が表示されるはずです。
  </Step>

  <Step title="データをクエリする" id="query-the-data">
    **特定のキーにアクセスする** — 角括弧構文を使って、map から値を取り出します。行にそのキーが存在しない場合は、値の型の既定値が返されます (`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;
    ```

    **Mapの値でフィルタリング** — 特定のサービス名に一致するすべてのスパンを見つけます:

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

    **キーが存在するか確認する** — すべてのスパンに Kubernetes のメタデータが付いているわけではありません。`mapContains` を使うと、どのスパンに含まれているかを確認できます:

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

    **データセット全体で使われているすべてのキーを確認する** — どのインストルメンテーションがデータを生成しているかを把握するのに役立ちます。

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

    **ARRAY JOINでMapを行に展開** — 各キー・バリューのペアを1行ずつに展開します。属性の一覧を作成したり、ダッシュボードにデータを渡したりするのに便利です。

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

    **mapFilter で Map をフィルタリング** — 各スパンから Kubernetes 関連の属性のみを抽出します:

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

    **エラースパンとそのリソースのコンテキストを特定する** — 通常のカラムフィルターとマップアクセスを組み合わせます:

    ```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="-Map combinator を使って Map をキーごとに集計する" id="aggregate-across-maps-with-the--map-combinator">
    ClickHouse の `-Map` 集約コンビネータを使うと、任意の集約関数を `Map` カラムに適用し、各キーごとに独立して集計できます。結果も `Map` となり、キーごとに 1 つのエントリと、その集計済みの値が格納されます。これは、カウンターや Gauge が `Map` の値として保存される OTel メトリクスで特に有用です。

    これを確認するために、各行に HTTP ステータスコードごとの件数を `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});
    ```

    次に、`sumMap` を使って、各サービスについてステータスコードごとの件数を合計します。

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

    `-Map` 接尾辞は任意の集約関数と組み合わせて使えるため、`minMap`、`maxMap`、`avgMap` も同様に簡単に使えます：

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

    これを他の combinator と組み合わせることもできます。たとえば、`sumMapIf` を使うと条件付きで集計できます。ここでは、サービスですでにエラーが発生していた分単位のウィンドウだけを合計します。

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

    **OTel でこれが重要な理由:** OTel collector が 1 分ごとのステータスコードの内訳を ClickHouse に書き込む場合、`sumMap` を使えば、それらを 1 回のクエリで時間単位や日単位の totals に集計できます。`ARRAY JOIN` も unpivot も不要で、キーの全体集合を事前に把握しておく必要もありません。どの行に現れたキーも、自動的に結果に含まれます。
  </Step>

  <Step title="頻繁にクエリするキー向けに最適化する" id="optimise-for-frequently-queried-keys">
    同じ Map キーで繰り返し絞り込みを行う場合 — `host.name` はその代表例です — それをマテリアライズドカラムとして抽出できます。こうすることで、クエリのたびに Map 全体を線形走査せずに済みます。

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

    既存データに対しては、カラムをバックフィルします：

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

    これで `WHERE HostName = 'prod-cart-01'` は、マップ全体ではなく、そのために用意された単一のカラムだけを読み取るようになります。これは、頻繁にクエリするあらゆる attribute について、OTel の ClickHouse スキーマで推奨されるパターンです。
  </Step>
</Steps>

<div id="key-takeaways">
  ## 要点
</div>

* **`Map(LowCardinality(String), String)`** は OTel の属性に適した定番の型です。キー集合が変化しても柔軟に扱え、`LowCardinality` によってキーの保存効率も高く保てます。
* **ブラケット構文** (`map['key']`) は値にアクセスする最も一般的な方法ですが、線形走査になる点には注意してください。キーが数十個の map であれば問題ありませんが、数百個になると最適とはいえません。
* **マテリアライズドカラム** は有効な手段です。map のキーが頻繁にフィルタ条件の対象になる場合は、それを実カラムに昇格させることで、索引付きの列指向アクセスが可能になります。
* **`mapContains`, `mapKeys`, `mapValues`, `mapFilter`** と `ARRAY JOIN` を使えば、SQL の中だけで map データを調べたり変換したりできる、豊富な手段が得られます。
* **`-Map` aggregate combinator** (`sumMap`, `avgMap`, `maxMap` など) は、行をまたいで各キーを個別に集計します。キー集合を事前に把握していなくても OTel メトリクスのカウンターを集約するのに最適で、ほかの集約関数コンビネータと組み合わせることもできます (たとえば `sumMapIf`) 。

<div id="next-steps">
  ## 次のステップ
</div>

次は、以下のクイックスタートをご覧ください。

* [最初のMergeTreeテーブルを作成する](/docs/ja/get-started/quickstarts/create-your-first-mergetree-table)
* [最初のmaterialized viewを作成する](/docs/ja/get-started/quickstarts/create-your-first-materialized-view)
* [使い始めによくある問題](/docs/ja/get-started/quickstarts/home)

また、リファレンスドキュメントでさらに詳しく確認することもできます。

* [Map 型 のリファレンス](/docs/ja/reference/data-types/map)
* [ClickHouse OTel エクスポーター](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter)
* [集約関数コンビネータ](/docs/ja/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>
