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

# العمل مع نوع Map في ClickHouse

> تعرّف على كيفية استخدام نوع Map في ClickHouse لتخزين بيانات مفتاح-قيمة ديناميكية والاستعلام عنها وتجميعها، باستخدام سمات الموارد في 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">
  ## المتطلبات الأساسية
</div>

* يجب تثبيت **clickhouse-local** على جهازك. راجع [دليل إعداد clickhouse-local](/docs/ar/concepts/features/tools-and-utilities/clickhouse-local) للبدء.

<div id="what-youll-build">
  ## ما الذي ستبنيه
</div>

في OpenTelemetry، يحمل كل `trace span` مجموعة من **سمات الموارد** — وهي بيانات وصفية بتنسيق مفتاح-قيمة تصف الكيان الذي أنتج بيانات telemetry (مثل اسم الخدمة، والمضيف، والمنطقة السحابية، وpod في Kubernetes، وغير ذلك). وتختلف مجموعة المفاتيح بين الخدمات والبيئات، مما يجعل نوع `Map` في ClickHouse مناسبًا لهذا الاستخدام بطبيعته: فالمفاتيح ديناميكية وخاصّة بالتطبيق، لكن أي صف معيّن لا يحتوي عادةً إلا على عدد قليل منها.

في هذا الدليل السريع، ستستخدم `clickhouse-local` لتحميل بيانات trace حقيقية من OTel من ملف CSV إلى جدول يحتوي على أعمدة `Map(LowCardinality(String), String)`، وستتعلّم كيفية الاستعلام عن بيانات Map وتصفيتها وتجميعها وتحسينها.

<Steps titleSize="h3">
  <Step title="نزّل بيانات العيّنة" id="download-the-sample-data">
    تتضمن مجموعة البيانات 6,120 امتداد تتبّع OTel مُصدَّرًا من تطبيق تجريبي للخدمات المصّغرة. ويتضمن كل صف العمودين `ResourceAttributes` و`SpanAttributes`، ويحتويان على أزواج مفتاح-قيمة ديناميكية على شكل خرائط JSON.
    احفظ الملف في دليل يسهل الرجوع إليه، على سبيل المثال `~/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>

    إليك شكل صف واحد:

    ```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)` — ويُستخدم `LowCardinality` مع نوع المفتاح لأن مفاتيح سمات OTel مأخوذة من مجموعة صغيرة نسبيًا ومتكررة.

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

    الآن حمّل ملف CSV باستخدام محرّك الجدول `file`. عدّل المسار ليتوافق مع المكان الذي حفظت فيه الملف:

    ```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** — اعثر على جميع spans المرتبطة باسم خدمة محدد:

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

    **تحقّق مما إذا كان المفتاح موجودًا** â لا يحتوي كل `span` على بيانات 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;
    ```

    **فكِّك Map إلى صفوف باستخدام ARRAY JOIN** — حوِّل كل زوج مفتاح-قيمة إلى صف مستقل، مما يفيد في إنشاء فهرس للسمات أو في تعبئة لوحات المعلومات:

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

    **رشّح قيم Map باستخدام mapFilter** — استخرج فقط السمات المرتبطة بـ Kubernetes من كل 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;
    ```

    **اعثر على مقاطع التتبّع الخاصة بالأخطاء وسياق المورد المرتبط بها** â اجمع بين مرشحات الأعمدة العادية والوصول إلى `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="التجميع عبر خرائط `Map` باستخدام المبدِّل `-Map`" id="aggregate-across-maps-with-the--map-combinator">
    يتيح المبدِّل التجميعي `-Map` في ClickHouse تطبيق أي دالة aggregate على عمود `Map` بحيث تعمل على كل مفتاح بشكل مستقل. وتكون النتيجة أيضًا `Map` — مع مُدخل واحد لكل مفتاح، يتضمن القيمة المُجمَّعة. ويكون هذا مفيدًا بشكل خاص مع metrics في OTel، حيث تُخزَّن counters أو gauges كقيم في map.

    للتوضيح، أنشئ table صغيرًا للـ metrics حيث يسجّل كل row أعداد رموز حالة 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;
    ```

    يمكنك أيضًا دمجه مع مُركِّبات أخرى. على سبيل المثال، يتيح لك `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 تفاصيل رموز الحالة لكل دقيقة في ClickHouse، تتيح لك `sumMap` تجميعها في إجماليات لكل ساعة أو لكل يوم ضمن استعلام واحد — من دون `ARRAY JOIN`، ومن دون إلغاء التمحور، ومن دون الحاجة إلى معرفة المجموعة الكاملة للمفاتيح مسبقًا. ويُدرَج تلقائيًا في النتيجة أي مفتاح يظهر في أي صف.
  </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'` عمودًا مخصصًا واحدًا بدلًا من قراءة بنية Map بالكامل. وهذا هو النمط الموصى به في مخطط OTel في ClickHouse لأي سمة تستعلم عنها بشكل متكرر.
  </Step>
</Steps>

<div id="key-takeaways">
  ## النقاط الرئيسية
</div>

* **`Map(LowCardinality(String), String)`** هو النوع الأكثر شيوعًا لسمات OTel — مرن بما يكفي للتعامل مع مجموعات مفاتيح متغيرة، كما يحافظ `LowCardinality` على كفاءة تخزين المفاتيح.
* **صياغة الأقواس** (`map['key']`) هي الطريقة الأكثر شيوعًا للوصول إلى القيم، لكن تذكّر أنها تفحص خطيًا — وهذا مناسب للخرائط التي تحتوي على عشرات المفاتيح، لكنه ليس مثاليًا عند وجود المئات.
* **الأعمدة المُجسَّدة** هي الحل العملي هنا: عندما يصبح مفتاح في الخريطة هدفًا شائعًا للتصفية، رقِّه إلى عمود فعلي للوصول المفهرس والعمودي.
* **`mapContains`, `mapKeys`, `mapValues`, `mapFilter`** و `ARRAY JOIN` توفّر لك مجموعة أدوات قوية لاستكشاف بيانات الخرائط وتحويلها دون مغادرة SQL.
* **مُركِّب التجميع `-Map`** (`sumMap`, `avgMap`, `maxMap`, etc.) يجمّع كل مفتاح على نحو مستقل عبر الصفوف — وهو مثالي لتجميع عدّادات مقاييس OTel دون الحاجة إلى معرفة مجموعة المفاتيح مسبقًا. كما يمكن تركيبه مع مُركِّبات أخرى أيضًا (مثل `sumMapIf`).

<div id="next-steps">
  ## الخطوات التالية
</div>

اطّلع بعد ذلك على أدلة البدء السريع التالية:

* [أنشئ أول جدول MergeTree](/docs/ar/get-started/quickstarts/create-your-first-mergetree-table)
* [أنشئ أول عرض مادي](/docs/ar/get-started/quickstarts/create-your-first-materialized-view)
* [المشكلات الشائعة عند البدء](/docs/ar/get-started/quickstarts/home)

أو تعمّق أكثر بالرجوع إلى الوثائق المرجعية:

* [مرجع نوع Map](/docs/ar/reference/data-types/map)
* [مُصدِّر ClickHouse OTel](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/clickhouseexporter)
* [مُعدِّلات الدوال التجميعية](/docs/ar/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>
