> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> أدلة استخدام dbt مع ClickHouse

# الأدلة

export const ClickHouseSupportedBadge = () => {
  return <div className="ClickHouseSupportedBadge">
            <div className="ClickHouseSupportedIcon">
                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M1.30762 1.39073C1.30762 1.3103 1.37465 1.22986 1.46849 1.22986H2.64824C2.72868 1.22986 2.80912 1.29689 2.80912 1.39073V14.4886C2.80912 14.5691 2.74209 14.6495 2.64824 14.6495H1.46849C1.38805 14.6495 1.30762 14.5825 1.30762 14.4886V1.39073Z" fill="currentColor" />
                    <path d="M4.2832 1.39073C4.2832 1.3103 4.35023 1.22986 4.44408 1.22986H5.62383C5.70427 1.22986 5.7847 1.29689 5.7847 1.39073V14.4886C5.7847 14.5691 5.71767 14.6495 5.62383 14.6495H4.44408C4.36364 14.6495 4.2832 14.5825 4.2832 14.4886V1.39073Z" fill="currentColor" />
                    <path d="M7.25977 1.39073C7.25977 1.3103 7.3268 1.22986 7.42064 1.22986H8.60039C8.68083 1.22986 8.76127 1.29689 8.76127 1.39073V14.4886C8.76127 14.5691 8.69423 14.6495 8.60039 14.6495H7.42064C7.3402 14.6495 7.25977 14.5825 7.25977 14.4886V1.39073Z" fill="currentColor" />
                    <path d="M10.2354 1.39073C10.2354 1.3103 10.3024 1.22986 10.3962 1.22986H11.576C11.6564 1.22986 11.7369 1.29689 11.7369 1.39073V14.4886C11.7369 14.5691 11.6698 14.6495 11.576 14.6495H10.3962C10.3158 14.6495 10.2354 14.5825 10.2354 14.4886V1.39073Z" fill="currentColor" />
                    <path d="M13.2256 6.6057C13.2256 6.52526 13.2926 6.44482 13.3865 6.44482H14.5662C14.6466 6.44482 14.7271 6.51186 14.7271 6.6057V9.27354C14.7271 9.35398 14.6601 9.43442 14.5662 9.43442H13.3865C13.306 9.43442 13.2256 9.36739 13.2256 9.27354V6.6057Z" fill="currentColor" />
                </svg>
            </div>
            متوافق مع ClickHouse
        </div>;
};

<ClickHouseSupportedBadge />

يستعرض هذا الدليل الجانب الخاص بـ ClickHouse من dbt باستخدام مشروع [Jaffle Shop for ClickHouse](https://github.com/ClickHouse/jaffle-shop-clickhouse)، وهو نسخة ClickHouse من المشروع النموذجي الكلاسيكي لـ dbt Labs. وانطلاقًا من مشروع جاهز للبناء بالفعل، يوضّح الدليل كيفية:

1. فهم الطريقة التي تُنشأ بها عروض المشروع وجداوله في ClickHouse.
2. تحميل البيانات باستخدام seeds والتحكم في أنواع ClickHouse وبنية الجدول.
3. تهيئة نموذج جدول بمحرك ClickHouse ومفتاح فرز وتقسيم.
4. تحويل جدول إلى نموذج incremental واختيار strategy تزايدية.
5. إنشاء snapshot.
6. استخدام العروض المجسدة في ClickHouse.

وقد صُمم ليُقرأ إلى جانب بقية [التوثيق](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/index)، وصفحة [الميزات والتهيئات](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/features-and-configurations)، و[مرجع materializations](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations).

<h2 id="before-you-start">
  قبل أن تبدأ
</h2>

اتبع أولاً ملف README الخاص بـ [ClickHouse/jaffle-shop-clickhouse](https://github.com/ClickHouse/jaffle-shop-clickhouse)، فهو يوضح كيفية إعداد المشروع باستخدام dbt Core 1.x أو dbt OSS أو dbt v2 أو منصة dbt، وكيفية توجيهه إلى ClickHouse محلي (docker) أو إلى ClickHouse Cloud، وكيفية تحميل بيانات العينة باستخدام `dbt seed`، وكيفية تشغيل أول `dbt build`. وبعد أن يكتمل `dbt build` بنجاح، عد إلى هنا للاطلاع على الأمثلة والتهيئات الخاصة بـ ClickHouse.

بعد إتمام خطوات README، ينبغي أن تكون لديك قاعدتا بيانات في ClickHouse:

* `raw`: جداول المصدر الستة المحمَّلة من ملفات CSV بواسطة `dbt seed` (`raw_customers`، `raw_orders`، `raw_items`، `raw_products`، `raw_stores`، `raw_supplies`).
* `jaffle_shop` (قيمة `schema` في ملف الـ profile الخاص بك): ستة عروض staging (`stg_*`) وسبعة جداول mart (`customers`، `orders`، `order_items`، `products`، `locations`، `supplies`، `metricflow_time_spine`).

إذا كان الـ profile الخاص بك يستخدم `schema` مختلفاً، فاستبدل `jaffle_shop` في الاستعلامات أدناه بالقيمة الخاصة بك.

<Note>
  **dbt Core 1.x وdbt OSS وdbt v2 ومنصة dbt.** كل أمر وكل نموذج في هذا الدليل هو نفسه في جميعها. اختُبرت الأمثلة باستخدام dbt Core 1.12 مع `dbt-clickhouse` 1.10، وباستخدام dbt OSS 2.0 مع ClickHouse 26.8؛ ويعمل dbt v2 بالـ adapter نفسه، وتعمل منصة dbt بـ dbt v2. مخرجات الـ console المعروضة مأخوذة من dbt Core 1.x، وقد أُشير إلى المواضع القليلة التي يختلف فيها سلوك المحركين. راجع [صفحة dbt OSS وdbt v2 ومنصة dbt](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/dbt-core-v2-fusion-and-platform) لمعرفة الحالة الراهنة لـ adapter الإصدار v2، وراجع [Connect ClickHouse](https://docs.getdbt.com/docs/platform/connect-data-platform/connect-clickhouse) في توثيق dbt للبدء على منصة dbt.
</Note>

جميع عبارات SQL التي ليست أوامر dbt يُقصد تشغيلها مباشرةً على ClickHouse، على سبيل المثال عبر `clickhouse client`، أو SQL console في ClickHouse Cloud، أو عميل SQL الذي تختاره.

<h2 id="views-and-tables">
  كيف يتم تجسيد المشروع
</h2>

يهيّئ Jaffle Shop تجسيداته في `dbt_project.yml`: نماذج staging تكون views، أما marts فتكون tables.

```yaml theme={null}
models:
  jaffle_shop:
    staging:
      +materialized: view
    marts:
      +materialized: table
```

تُعاد بناء نموذج **العرض** (view) باستخدام عبارة `CREATE OR REPLACE VIEW` في كل تشغيل. وهو لا يخزّن أي بيانات، لذا لا يكلّف بناؤه شيئًا، لكن كل استعلام يُنفَّذ عليه يُشغّل SQL الخاص بالنموذج على الجداول المصدر. ويحتفظ ClickHouse بكود SQL المُصرَّف للنموذج في تعريف العرض:

```sql theme={null}
SHOW CREATE VIEW jaffle_shop.stg_orders;
```

```response theme={null}
CREATE VIEW jaffle_shop.stg_orders
(
    `order_id` String,
    `location_id` String,
    `customer_id` String,
    ...
    `ordered_at` DateTime
)
AS WITH
    source AS
    (
        SELECT *
        FROM raw.raw_orders
    ),
    renamed AS
    (
        SELECT
            id AS order_id,
            store_id AS location_id,
            ...
            dateTrunc('day', ordered_at) AS ordered_at
        FROM source
    )
SELECT *
FROM renamed
```

تُعاد بناء model من نوع **table** من الصفر في كل تشغيل: ينشئ adapter جدولًا جديدًا، ثم ينفّذ `INSERT INTO ... SELECT` باستخدام SQL الخاص بالـ model، ثم يبادله بشكل ذرّي مع الإصدار السابق. وأداء الاستعلام هنا أفضل بكثير منه في العرض، لكن على حساب التخزين وإعادة بناء الجدول بالكامل في كل مرة. ألقِ نظرة على الجدول الذي أنشأه dbt لمستودع `orders`:

```sql theme={null}
SHOW CREATE TABLE jaffle_shop.orders;
```

```response theme={null}
CREATE TABLE jaffle_shop.orders
(
    `order_id` String,
    `location_id` String,
    `customer_id` String,
    ...
    `customer_order_number` UInt64
)
ENGINE = MergeTree
ORDER BY tuple()
SETTINGS replicated_deduplication_window = '0', index_granularity = 8192
```

هناك أمران خاصان بـ ClickHouse هنا. فالـ model لا يُعلِن عن table engine، لذا يستخدم الـ adapter المحرك `MergeTree`، كما أنه لا يُعلِن عن sorting key، لذا يستخدم الـ adapter العبارة `ORDER BY tuple()`، أي أن البيانات غير مُرتَّبة على الإطلاق. وهذا مقبول في مشروع تجريبي، أما في جدول حقيقي فستحتاج إلى تحديد كليهما، وهو ما تتناوله الأقسام التالية. وتسرد [صفحة materializations](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations) كل تهيئة جدول يدعمها الـ adapter.

<h2 id="seeds">
  تحميل البيانات باستخدام seeds
</h2>

يستخدم Jaffle Shop [seeds](https://docs.getdbt.com/docs/build/seeds) في dbt لتحميل بياناته الخام من ملفات CSV الموجودة في `seeds/jaffle-data`. صُمّمت seeds للبيانات المرجعية الصغيرة والثابتة (جداول الرموز وعمليات الربط)، وليست مخصصة لتحميل warehouse؛ لكن المشروع يستخدمها لتسهيل الأمر عليك كي تبدأ دون الحاجة إلى أداة ingestion أخرى، ولهذا تبقى seeds معطّلة ما لم تمرّر `--vars '{"load_source_data": true}'`.

ومع ذلك، تظل seeds مكانًا جيدًا لتتعلّم كيف يُنشئ dbt جداول ClickHouse. فهو يستنتج نوع column لكل column في ملف CSV، وتختلف الأنواع المستنتجة بين المحركات:

| قيمة CSV | dbt v1 | محرك v2 |
| - | - | - |
| `700` | `Int32` | `Int64` |
| `0.06` | `Float32` | `Float64` |
| `2024-09-01T15:01:00` | `DateTime` | `DateTime64(6)` |
| `Philadelphia` | `String` | `String` |

وعندما يكون النوع مهمًا، ثبّته باستخدام `column_types`. وقد فعل المشروع ذلك بالفعل مع column المسمى `opened_at` في seed `raw_stores` ضمن `dbt_project.yml`:

```yaml theme={null}
seeds:
  jaffle_shop:
    +schema: raw
    jaffle-data:
      +enabled: "{{ var('load_source_data', false) }}"
      raw_stores:
        +column_types:
          opened_at: DateTime64(3)
```

تقبل الـ seeds أيضاً تهيئات جدول ClickHouse التالية: `engine` و`order_by` و`partition_by`. على سبيل المثال، لفرز الـ seed المسمى `raw_orders` حسب وقت الطلب وتقسيمه شهرياً، أضف ملف properties إلى جانب ملفات CSV، أي `seeds/jaffle-data/_raw_orders.yml`:

```yaml theme={null}
seeds:
  - name: raw_orders
    config:
      order_by: (ordered_at, id)
      partition_by: toYYYYMM(ordered_at)
```

<Note>
  استخدم ملف properties لتهيئة seeds الخاصة بـ ClickHouse بدلاً من المفتاحين `+order_by` أو `+engine` ضمن `seeds:` في `dbt_project.yml`. يقبل dbt Core 1.x كلا الشكلين، أما dbt v2 فلا يتعرّف عليهما إلا في ملف properties ويرفض مفاتيح `dbt_project.yml` مع الرسالة `Unrecognized key ... Custom keys must go under +meta`.
</Note>

أعد تحميل ذلك الـ seed وتحقّق من الجدول الناتج عنه:

```bash theme={null}
dbt seed --select raw_orders --full-refresh --vars '{"load_source_data": true}'
```

```sql theme={null}
SHOW CREATE TABLE raw.raw_orders;
```

```response theme={null}
CREATE TABLE raw.raw_orders
(
    `id` String,
    `customer` String,
    `ordered_at` DateTime,
    `store_id` String,
    `subtotal` Int32,
    `tax_paid` Int32,
    `order_total` Int32
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ordered_at)
ORDER BY (ordered_at, id)
SETTINGS index_granularity = 8192
```

يقوم الأمر `dbt seed --full-refresh` بحذف الجدول وإعادة إنشائه، لذا شغّله قبل بناء أي شيء يعتمد مباشرةً على بيانات الجدول، مثل الـ عرض مُجسَّد الوارد لاحقًا في هذا الدليل.

<h2 id="table-configuration">
  إعداد جدول لـ ClickHouse
</h2>

يُعد الـ mart المسمى `orders` نقطة البداية الطبيعية: فهو الجدول الذي يستعلم منه mart المسمى `customers` ومقاييس المشروع، كما أنه جدول على نمط الأحداث يتضمن timestamp. أضف كتلة `config` في أعلى ملف `models/marts/orders.sql` لتحديد المحرك ومفتاح الفرز ومخطط التقسيم:

```sql theme={null}
{{
    config(
        materialized='table',
        engine='MergeTree()',
        order_by='(ordered_at, order_id)',
        partition_by='toYYYYMM(ordered_at)'
    )
}}

with

orders as (

    select * from {{ ref('stg_orders') }}

),
...
```

تبقى بقية النموذج كما هي. يكرّر `materialized='table'` ما يحدده `dbt_project.yml` أصلاً لطبقة marts، وهو ما يجعل النموذج ذاتي الوصف عند تحويله لاحقاً إلى الوضع التزايدي. أعد بناء هذا النموذج فقط:

```bash theme={null}
dbt run --select orders
```

```response theme={null}
1 of 1 START sql table model `jaffle_shop`.`orders` ............................ [RUN]
1 of 1 OK created sql table model `jaffle_shop`.`orders` ....................... [OK in 0.44s]
```

أصبح للجدول الآن sorting key مناسب وتقسيم واحد لكل شهر:

```sql theme={null}
SHOW CREATE TABLE jaffle_shop.orders;
```

```response theme={null}
CREATE TABLE jaffle_shop.orders
(
    ...
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ordered_at)
ORDER BY (ordered_at, order_id)
SETTINGS replicated_deduplication_window = '0', index_granularity = 8192
```

```sql theme={null}
SELECT partition, sum(rows) AS rows
FROM system.parts
WHERE database = 'jaffle_shop' AND table = 'orders' AND active
GROUP BY partition
ORDER BY partition;
```

```response theme={null}
┌─partition─┬─rows─┐
│ 202409    │ 1497 │
│ 202410    │ 1698 │
│ 202411    │ 2262 │
...
│ 202508    │ 9389 │
└───────────┴──────┘
```

إلى جانب `engine` و`order_by` و`partition_by`، تقبل نماذج الجداول أيضًا `primary_key` و`ttl` و`settings` و`query_settings` و`projections` و`indexes`، ويمكن أن تحمل الأعمدة `codec` و`ttl` عبر عقد النموذج (model contract). وجميعها موضّحة في [صفحة materializations](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations).

<h2 id="incremental">
  إنشاء model تزايدي
</h2>

إعادة بناء `orders` من الصفر في كل تشغيل أمر مقبول مع 62,000 row، لكنه غير مناسب لـ table ينمو بملايين الـ rows يوميًا. أما [التجسيد التزايدي](https://docs.getdbt.com/docs/build/incremental-models) في dbt فلا يعالج سوى الـ rows التي تغيّرت منذ التشغيل الأخير. ويتطلب تحويل model الخاص بـ `orders` إضافتين:

1. **`unique_key`**: الـ column الذي يعرّف الـ row، وهو `order_id` هنا. ويستخدمه الـ adapter لاستبدال الـ rows التي تُعالج مرة أخرى بدلًا من تكرارها.
2. **filter تزايدي**: عبارة `where` مُحاطة بـ `{% if is_incremental() %}` تختار الـ rows المطلوب معالجتها فقط. وتُطبَّق في التشغيلات التزايدية، لا عند بناء الـ table للمرة الأولى (أو عند إعادة بنائه باستخدام `--full-refresh`). وبما أن الطلبات تحمل timestamp، فإن الـ filter يقارن قيمة `ordered_at` بأحدث قيمة موجودة بالفعل في الـ table، والتي يُشار إليها عبر المتغير `{{ this }}`.

حدّث `models/marts/orders.sql` بحيث تصبح كتلة `config` ونهاية الـ model كما يلي:

```sql theme={null}
{{
    config(
        materialized='incremental',
        unique_key='order_id',
        engine='MergeTree()',
        order_by='(ordered_at, order_id)',
        partition_by='toYYYYMM(ordered_at)'
    )
}}

with

orders as (

    select * from {{ ref('stg_orders') }}

),

...

select * from customer_order_count

{% if is_incremental() %}

-- this filter will only be applied on an incremental run
where ordered_at >= (select max(ordered_at) from {{ this }})

{% endif %}
```

يقتطع `stg_orders` القيمة `ordered_at` إلى مستوى اليوم، لذا يستخدم الـ filter المعامل `>=`: ففي كل تشغيل تُعاد معالجة اليوم الأخير بالكامل، وبفضل `unique_key` تُستبدل الـ rows المحمَّلة مسبقًا بدلًا من تكرارها. وهذا ما يجعله آمنًا للطلبات التي تصل لاحقًا في اليوم نفسه.

شغّل الـ model. الـ table موجود بالفعل، لذا فإن هذا التشغيل الأول يُعد تشغيلًا incremental أصلًا: تُعاد معالجة اليوم الأخير فقط.

```bash theme={null}
dbt run --select orders
```

```response theme={null}
1 of 1 START sql incremental model `jaffle_shop`.`orders` ...................... [RUN]
1 of 1 OK created sql incremental model `jaffle_shop`.`orders` ................. [OK in 0.94s]
```

والآن أضف بعض البيانات الجديدة. تنتهي بيانات Jaffle Shop عند أغسطس 2025، لذا سنُدخل عميلًا جديدًا هو Clicky McClickHouse، الذي طلب جافل أمس. أدرج عميلًا وطلبًا وعنصر الطلب الخاص به في الجداول الخام:

```sql theme={null}
INSERT INTO raw.raw_customers VALUES ('clicky-0001', 'Clicky McClickHouse');

INSERT INTO raw.raw_orders VALUES
    ('clicky-order-0001', 'clicky-0001', now() - INTERVAL 1 DAY,
     '4b6c2304-2b9e-41e4-942a-cf11a1819378', 1100, 66, 1166);

INSERT INTO raw.raw_items VALUES ('clicky-item-0001', 'clicky-order-0001', 'JAF-001');
```

معرّف المتجر هو Philadelphia، والعنصر هو جافل `nutellaphone who dis?` بسعر 11.00، والضريبة هي 6% الخاصة بـ Philadelphia، لذا تبقى اختبارات البيانات في المشروع ناجحة. شغّل المشروع بالكامل حتى تتعرّف عروض staging وجدول `order_items` على الصفوف الجديدة قبل أن يتعرّف عليها `orders`:

```bash theme={null}
dbt run
```

```response theme={null}
...
10 of 13 OK created sql table model `jaffle_shop`.`order_items` ................ [OK in 0.30s]
...
12 of 13 START sql incremental model `jaffle_shop`.`orders` .................... [RUN]
12 of 13 OK created sql incremental model `jaffle_shop`.`orders` ............... [OK in 0.73s]
13 of 13 START sql table model `jaffle_shop`.`customers` ....................... [RUN]
13 of 13 OK created sql table model `jaffle_shop`.`customers` .................. [OK in 0.28s]
```

الطلب الجديد موجود في جدول incremental، كما أن mart الخاص بـ `customers`، الذي أُعيد بناؤه منه، يتعرّف على العميل الجديد:

```sql theme={null}
SELECT order_id, customer_id, ordered_at, order_total, customer_order_number
FROM jaffle_shop.orders
WHERE customer_id = 'clicky-0001';
```

```response theme={null}
┌─order_id──────────┬─customer_id─┬──────────ordered_at─┬─order_total─┬─customer_order_number─┐
│ clicky-order-0001 │ clicky-0001 │ 2026-09-14 00:00:00 │       11.66 │                     1 │
└───────────────────┴─────────────┴─────────────────────┴─────────────┴───────────────────────┘
```

```sql theme={null}
SELECT customer_name, count_lifetime_orders, lifetime_spend, customer_type
FROM jaffle_shop.customers
WHERE customer_id = 'clicky-0001';
```

```response theme={null}
┌─customer_name───────┬─count_lifetime_orders─┬─lifetime_spend─┬─customer_type─┐
│ Clicky McClickHouse │                     1 │          11.66 │ new           │
└─────────────────────┴───────────────────────┴────────────────┴───────────────┘
```

<h3 id="internals">
  البنية الداخلية
</h3>

يُظهر سجل الاستعلامات في ClickHouse العبارات التي نفّذها الـ adapter لإجراء التحديث التزايدي:

```sql theme={null}
SELECT event_time, written_rows, tables
FROM system.query_log
WHERE query_kind = 'Insert' AND type = 'QueryFinish'
  AND has(databases, 'jaffle_shop')
  AND event_time > now() - INTERVAL 15 MINUTE
ORDER BY event_time;
```

تعمل الاستراتيجية التزايدية الافتراضية للـ adapter كما يلي. في مخططات هذا القسم، يعني السهم المتجه من جدول إلى عبارة أن العبارة تقرأ ذلك الجدول؛ أما السهم المتجه من عبارة إلى جدول فيعني أنها تكتب إليه أو تُجري عليه تحويلاً (mutate) أو تعيد تسميته أو تحذفه:

1. يُنشأ جدول `orders__dbt_new_data` ويُدرج فيه استعلام SQL الخاص بالـ model، بما في ذلك الـ filter التزايدي. في التشغيل أعلاه، كُتب 378 صفاً: 377 طلباً من آخر يوم تم تحميله مسبقاً، إضافةً إلى الطلب الجديد.
2. يُنشأ جدول `orders__dbt_tmp` بالبنية نفسها لجدول `orders`، وتُنسخ إليه جميع صفوف `orders` التي لا يوجد `order_id` الخاص بها في `orders__dbt_new_data`.
3. تُدرج جميع صفوف `orders__dbt_new_data` في `orders__dbt_tmp`. والخطوتان 2 و3 هما ما يستبدل صفوف آخر يوم بدلاً من تكرارها.
4. يُحذف `orders__dbt_new_data`.
5. يُبدَّل `orders__dbt_tmp` مع `orders` باستخدام عبارة `EXCHANGE TABLES` الذرية (عبر إعادة تسمية وسيطة إلى `orders__dbt_backup`)، وبذلك يحتوي `orders` الآن على النسخة الجديدة.
6. تُحذف النسخة القديمة.

```mermaid theme={null}
flowchart TB
    stg[("stg_orders")]
    items[("order_items")]
    orders[("orders<br/>(current version)")]
    new_data[("orders__dbt_new_data")]
    tmp[("orders__dbt_tmp")]
    new_orders[("orders<br/>(new version, was orders__dbt_tmp)")]
    old_orders[("orders__dbt_tmp<br/>(old version, was orders)")]
    q1["1. INSERT INTO orders__dbt_new_data<br/>SELECT ... model SQL ...<br/>WHERE ordered_at >= (SELECT max(ordered_at) FROM orders)"]
    q2["2. INSERT INTO orders__dbt_tmp<br/>SELECT * FROM orders<br/>WHERE order_id NOT IN (SELECT order_id FROM orders__dbt_new_data)"]
    q3["3. INSERT INTO orders__dbt_tmp<br/>SELECT * FROM orders__dbt_new_data"]
    q4["4. DROP TABLE orders__dbt_new_data"]
    q5["5. EXCHANGE TABLES orders__dbt_tmp AND orders"]
    q6["6. DROP TABLE orders__dbt_tmp"]
    stg -->|reads| q1
    items -->|reads| q1
    q1 -->|inserts the changed rows| new_data
    orders -->|reads| q2
    new_data -->|reads the keys| q2
    new_data -->|reads| q3
    q2 -->|"inserts the rows<br/>that did not change"| tmp
    q3 -->|"inserts the<br/>changed rows"| tmp
    q3 ~~~ q4
    q4 -->|drops| new_data
    tmp ~~~ q5
    q5 -->|"swaps the names"| new_orders
    q5 -->|"swaps the names"| old_orders
    q5 ~~~ q6
    q6 -->|drops| old_orders
    classDef temp fill:#dbeafe,stroke:#1d4ed8;
    classDef target fill:#fef3c7,stroke:#b45309;
    classDef source fill:#dcfce7,stroke:#15803d;
    classDef query fill:#f3f4f6,stroke:#6b7280;
    class new_data,tmp,old_orders temp;
    class orders,new_orders target;
    class stg,items source;
    class q1,q2,q3,q4,q5,q6 query;
```

تنسخ الخطوة 2 الجدول بأكمله، ولذلك تكون تكلفة هذه الاستراتيجية مماثلة لإعادة بناء الجدول في النماذج الضخمة جدًا؛ راجع [القيود](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations). أما الاستراتيجيات الواردة أدناه فهي تتجنب عملية النسخ هذه.

<h3 id="append-strategy">
  Append strategy
</h3>

تُدرج استراتيجية `append` الصفوف التي يحددها الـ model مباشرةً في الـ target table. فلا تُنشأ أي temporary tables ولا يُنسخ أي شيء، ما يجعلها أقل تكلفة ما يمكن أن يكون عليه تشغيل incremental. لكن الثمن هو عدم إجراء أي deduplicate أيضًا: فإذا حدّد الـ filter الخاص بـ incremental صفًا موجودًا بالفعل في الـ table، فستحصل عليه مرتين. استخدمها للبيانات غير القابلة للتغيير ذات النمط الحدثي، وتأكد من أن الـ filter لا يحدد إلا الصفوف الجديدة فعليًا.

ومع `ordered_at` المقتطع على مستوى اليوم، يعني ذلك تغيير الـ filter إلى `>`. عدّل الـ model:

```sql theme={null}
{{
    config(
        materialized='incremental',
        incremental_strategy='append',
        unique_key='order_id',
        engine='MergeTree()',
        order_by='(ordered_at, order_id)',
        partition_by='toYYYYMM(ordered_at)'
    )
}}

...

{% if is_incremental() %}

-- this filter will only be applied on an incremental run
where ordered_at > (select max(ordered_at) from {{ this }})

{% endif %}
```

أضف عميلاً جديداً ثانياً، Danny DeBito، مع طلب تم تقديمه اليوم في Brooklyn (ضريبة 4%) يتضمّن jaffle وقهوة:

```sql theme={null}
INSERT INTO raw.raw_customers VALUES ('danny-0001', 'Danny DeBito');

INSERT INTO raw.raw_orders VALUES
    ('danny-order-0001', 'danny-0001', now(),
     '40e6ddd6-b8f6-4e17-8bd6-5e53966809d2', 1900, 76, 1976);

INSERT INTO raw.raw_items VALUES
    ('danny-item-0001', 'danny-order-0001', 'JAF-003'),
    ('danny-item-0002', 'danny-order-0001', 'BEV-004');
```

```bash theme={null}
dbt run
```

```response theme={null}
...
12 of 13 START sql incremental model `jaffle_shop`.`orders` .................... [RUN]
12 of 13 OK created sql incremental model `jaffle_shop`.`orders` ............... [OK in 0.11s]
...
```

استغرق تشغيل الـ incremental model جزءًا بسيطًا من زمن التشغيل السابق. ولكلٍّ من العميلين الجديدين طلب واحد فقط في الـ table:

```sql theme={null}
SELECT order_id, customer_id, ordered_at, order_total, is_food_order, is_drink_order
FROM jaffle_shop.orders
WHERE customer_id IN ('clicky-0001', 'danny-0001')
ORDER BY ordered_at;
```

```response theme={null}
┌─order_id──────────┬─customer_id─┬──────────ordered_at─┬─order_total─┬─is_food_order─┬─is_drink_order─┐
│ clicky-order-0001 │ clicky-0001 │ 2026-09-14 00:00:00 │       11.66 │             1 │              0 │
│ danny-order-0001  │ danny-0001  │ 2026-09-15 00:00:00 │       19.76 │             1 │              1 │
└───────────────────┴─────────────┴─────────────────────┴─────────────┴───────────────┴────────────────┘
```

يؤكّد سجل الاستعلامات (query log) الفرق: هذه المرة العبارة الوحيدة التي تمسّ `orders` هي عبارة `INSERT INTO jaffle_shop.orders ... SELECT ...` واحدة تتضمّن SQL الخاص بالـ model والـ filter التزايدي، وقد كتبت صفًّا واحدًا.

<Warning>
  عند استخدام `>` مع طابع زمني مقتطع إلى مستوى اليوم، فإن أي طلب يصل لاحقًا في اليوم نفسه الذي وصل فيه آخر طلب محمَّل لن يُلتقط أبدًا. في المشاريع الحقيقية، طبّق الـ filter على طابع زمني بدقة كاملة، أو على وقت ingestion متزايد باستمرار، عند استخدام الـ `append` strategy.
</Warning>

<h3 id="delete-insert-strategy">
  استراتيجية الحذف والإدراج
</h3>

تاريخيًا، لم يقدّم ClickHouse سوى دعم محدود لعمليات التحديث والحذف، على هيئة [mutations](/ar/reference/statements/alter/index) غير متزامنة. وقد تكون هذه العمليات مكثفة جدًا من ناحية IO ويُفضّل تجنّبها عمومًا. وقد أضاف ClickHouse 22.8 ميزة [lightweight deletes](/ar/reference/statements/delete)، وأضاف ClickHouse 25.7 ميزة [lightweight updates](/ar/reference/statements/update). وبفضلهما، يظهر أثر عبارة الحذف أو التحديث الواحدة فورًا من منظور المستخدم، رغم أن تجسيدها يتم بشكل غير متزامن.

تعتمد استراتيجية `delete+insert` على lightweight deletes وتُهيَّأ عبر المعلمة `incremental_strategy`:

```sql theme={null}
{{
    config(
        materialized='incremental',
        incremental_strategy='delete+insert',
        unique_key='order_id',
        engine='MergeTree()',
        order_by='(ordered_at, order_id)',
        partition_by='toYYYYMM(ordered_at)'
    )
}}
```

تعمل هذه الاستراتيجية مباشرةً على الجدول الهدف، لذا إذا فشل شيء في منتصف العملية فمن المرجّح أن تكون بيانات النموذج التزايدي في حالة غير صحيحة، إذ لا يوجد تبديل (swap) ذرّي. وبإيجاز:

1. يُنشأ جدول مؤقت (`orders__dbt_new_data_<run_id>`) وتُدرج فيه الصفوف التي يحددها النموذج.
2. يُنفَّذ أمر `DELETE` على `orders` لكل `order_id` موجود في الجدول المؤقت.
3. تُدرج صفوف الجدول المؤقت في `orders`.
4. يُحذف الجدول المؤقت.

```mermaid theme={null}
flowchart TB
    stg[("stg_orders")]
    items[("order_items")]
    orders[("orders")]
    new_data[("orders__dbt_new_data_#lt;run_id#gt;")]
    q1["1. CREATE TABLE orders__dbt_new_data_#lt;run_id#gt; AS<br/>SELECT ... model SQL ...<br/>WHERE ordered_at >= (SELECT max(ordered_at) FROM orders)"]
    q2["2. DELETE FROM orders<br/>WHERE order_id IN (SELECT order_id FROM orders__dbt_new_data_#lt;run_id#gt;)"]
    q3["3. INSERT INTO orders<br/>SELECT * FROM orders__dbt_new_data_#lt;run_id#gt;"]
    q4["4. DROP TABLE orders__dbt_new_data_#lt;run_id#gt;"]
    stg -->|reads| q1
    items -->|reads| q1
    q1 -->|creates and fills with the changed rows| new_data
    new_data -->|reads the keys| q2
    q2 -->|deletes the matching rows| orders
    new_data -->|reads| q3
    q3 -->|inserts the changed rows| orders
    q4 -->|drops| new_data
    classDef temp fill:#dbeafe,stroke:#1d4ed8;
    classDef target fill:#fef3c7,stroke:#b45309;
    classDef source fill:#dcfce7,stroke:#15803d;
    classDef query fill:#f3f4f6,stroke:#6b7280;
    class new_data temp;
    class orders target;
    class stg,items source;
    class q1,q2,q3,q4 query;
```

<h3 id="insert-overwrite-strategy">
  استراتيجية insert overwrite (تجريبية)
</h3>

تستبدل استراتيجية `insert_overwrite` تقسيمات كاملة، لذا فهي تتطلّب تهيئة `partition_by` مثل التهيئة الشهرية المستخدمة في `orders`. وهي تنفّذ الخطوات التالية:

1. إنشاء staging table (‏`orders__dbt_new_data_<run_id>`) بالبنية نفسها المستخدمة في `orders`.
2. إدخال الصفوف التي حدّدها الـ model فقط في الـ staging table.
3. سرد التقسيمات الموجودة في الـ staging table من `system.parts`.
4. استبدال تلك التقسيمات تحديدًا في `orders` باستخدام `ALTER TABLE ... REPLACE PARTITION ... FROM` من الـ staging table.
5. حذف الـ staging table.

ويوفّر هذا الأسلوب المزايا التالية:

* أسرع من الاستراتيجية الافتراضية لأنه لا ينسخ الجدول بالكامل.
* أكثر أمانًا من الاستراتيجيات الأخرى لأنه لا يعدّل الجدول الأصلي إلى أن تكتمل عملية INSERT بنجاح؛ فإذا حدث فشل في منتصف العملية يبقى الجدول الأصلي كما هو.
* يطبّق أفضل ممارسات هندسة البيانات المتمثلة في "ثبات التقسيمات"، ما يبسّط معالجة البيانات التزايدية والمتوازية وعمليات التراجع وغيرها.

```mermaid theme={null}
flowchart TB
    stg[("stg_orders")]
    items[("order_items")]
    orders[("orders<br/>PARTITION BY toYYYYMM(ordered_at)")]
    staging[("orders__dbt_new_data_#lt;run_id#gt;")]
    parts[("system.parts")]
    q1["1. CREATE TABLE orders__dbt_new_data_#lt;run_id#gt; AS orders"]
    q2["2. INSERT INTO orders__dbt_new_data_#lt;run_id#gt;<br/>SELECT ... model SQL ...<br/>WHERE ordered_at >= (SELECT max(ordered_at) FROM orders)"]
    q3["3. SELECT DISTINCT partition_id FROM system.parts<br/>WHERE table = 'orders__dbt_new_data_#lt;run_id#gt;' AND active"]
    q4["4. ALTER TABLE orders<br/>REPLACE PARTITION ID '202509' FROM orders__dbt_new_data_#lt;run_id#gt;,<br/>REPLACE PARTITION ID ... (one per partition found in step 3)"]
    q5["5. DROP TABLE orders__dbt_new_data_#lt;run_id#gt;"]
    q1 -->|creates empty, same structure as orders| staging
    stg -->|reads| q2
    items -->|reads| q2
    q2 -->|inserts the changed rows| staging
    parts -->|reads the partitions of the staging table| q3
    q3 -->|partition ids| q4
    staging -->|reads| q4
    q4 -->|replaces those partitions| orders
    q5 -->|drops| staging
    classDef temp fill:#dbeafe,stroke:#1d4ed8;
    classDef target fill:#fef3c7,stroke:#b45309;
    classDef source fill:#dcfce7,stroke:#15803d;
    classDef query fill:#f3f4f6,stroke:#6b7280;
    class staging temp;
    class orders target;
    class stg,items,parts source;
    class q1,q2,q3,q4,q5 query;
```

تتناول [صفحة التجسيدات](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations#materialization-incremental) بقية خيارات التجسيد التزايدي، بما في ذلك استراتيجية `microbatch` و`on_schema_change`.

<h2 id="snapshot">
  إنشاء snapshot
</h2>

تسجّل [snapshots](https://docs.getdbt.com/docs/build/snapshots) في dbt كيفية تغيّر صفوف جدول قابل للتعديل بمرور الوقت، بما يتيح للمحللين الاطلاع على حالة البيانات في أي لحظة سابقة. وهي تُطبّق [الأبعاد بطيئة التغيّر من النوع 2](https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row): إذ يُخزَّن كل إصدار من الصف مع الفاصل الزمني الذي كان صالحًا خلاله.

يُعد مستودع البيانات `customers` مرشحًا جيدًا لذلك: فالحقول `count_lifetime_orders` و`lifetime_spend` و`customer_type` تتغير جميعها كلما قدّم العميل طلبًا جديدًا. قبل المتابعة، أعد ضبط الـ model المسمى `orders` على استراتيجية الـ incremental الافتراضية الواردة في [قسم incremental](#incremental) (بإزالة `incremental_strategy='append'` وإعادة الـ filter إلى `>=`)، حتى يتم التقاط الطلبات التي تُقدَّم لاحقًا في اليوم نفسه.

تُعرَّف اللقطات (snapshots) بصيغة YAML اعتبارًا من dbt 1.9. أنشئ الملف `snapshots/customers_snapshot.yml`:

```yaml theme={null}
snapshots:
  - name: customers_snapshot
    relation: ref('customers')
    config:
      unique_key: customer_id
      strategy: check
      check_cols:
        - count_lifetime_orders
        - lifetime_spend
        - customer_type
```

تقارن الـ strategy `check` الـ columns المدرجة بين اللقطة الحالية والـ source في كل تشغيل، وتسجّل version جديدة عند تغيّر أي منها. وإذا كان الـ model يحتوي على column من نوع timestamp موثوق يمثّل "آخر تحديث"، فإن الـ strategy `timestamp` أقل تكلفة: اضبط `strategy: timestamp` و`updated_at: <column>`. أما `last_ordered_at` في Jaffle Shop فهو مقتطع إلى مستوى اليوم، لذا لن يرصد طلبًا ثانيًا في اليوم نفسه، ولهذا يستخدم هذا المثال `check`.

خُذ أول snapshot:

```bash theme={null}
dbt snapshot
```

```response theme={null}
1 of 1 START snapshot `jaffle_shop`.`customers_snapshot` ....................... [RUN]
1 of 1 OK snapshotted `jaffle_shop`.`customers_snapshot` ....................... [OK in 0.17s]
```

يُنشأ جدول الـ snapshot بجوار الـ models. ويضع ماكرو `generate_schema_name` الخاص بالمشروع كل relation في schema الهدف بالنسبة للأهداف غير الإنتاجية، لذا فإن إعداد `schema` على الـ snapshot لا يسري إلا مع الهدف `prod`. ويحتوي الجدول على صف واحد لكل عميل، إلى جانب عمودَي التتبع الخاصين بـ dbt وهما `dbt_valid_from` و`dbt_valid_to`؛ ويكون الأخير `NULL` بالنسبة للإصدار الحالي من الصف:

```sql theme={null}
SELECT customer_id, count_lifetime_orders, lifetime_spend, customer_type, dbt_valid_from, dbt_valid_to
FROM jaffle_shop.customers_snapshot
WHERE customer_id IN ('clicky-0001', 'danny-0001')
ORDER BY customer_id, dbt_valid_from;
```

```response theme={null}
┌─customer_id─┬─count_lifetime_orders─┬─lifetime_spend─┬─customer_type─┬──────dbt_valid_from─┬─dbt_valid_to─┐
│ clicky-0001 │                     1 │          11.66 │ new           │ 2026-09-15 01:15:32 │         ᴺᵁᴸᴸ │
│ danny-0001  │                     1 │          19.76 │ new           │ 2026-09-15 01:15:32 │         ᴺᵁᴸᴸ │
└─────────────┴───────────────────────┴────────────────┴───────────────┴─────────────────────┴──────────────┘
```

يعود Clicky اليوم لاحتساء القهوة:

```sql theme={null}
INSERT INTO raw.raw_orders VALUES
    ('clicky-order-0002', 'clicky-0001', now(),
     '4b6c2304-2b9e-41e4-942a-cf11a1819378', 600, 36, 636);

INSERT INTO raw.raw_items VALUES ('clicky-item-0002', 'clicky-order-0002', 'BEV-001');
```

شغّل الـ models حتى يعكس كل من `orders` و`customers` الطلب الجديد، ثم خُذ snapshot ثانيًا:

```bash theme={null}
dbt run
dbt snapshot
```

```response theme={null}
1 of 1 START snapshot `jaffle_shop`.`customers_snapshot` ....................... [RUN]
1 of 1 OK snapshotted `jaffle_shop`.`customers_snapshot` ....................... [OK in 0.73s]
```

أصبح لدى Clicky الآن صفّان في اللقطة. فقد أُغلقت النسخة الأولى بتعيين قيمة `dbt_valid_to` الخاصة بها، أما النسخة الجديدة، التي باتت تمثّل عميلاً `returning` لديه طلبان، فهي مفتوحة. ولم يطرأ أي تغيير على Danny، لذا بقي صفّه كما هو:

```sql theme={null}
SELECT customer_id, count_lifetime_orders, lifetime_spend, customer_type, dbt_valid_from, dbt_valid_to
FROM jaffle_shop.customers_snapshot
WHERE customer_id IN ('clicky-0001', 'danny-0001')
ORDER BY customer_id, dbt_valid_from;
```

```response theme={null}
┌─customer_id─┬─count_lifetime_orders─┬─lifetime_spend─┬─customer_type─┬──────dbt_valid_from─┬────────dbt_valid_to─┐
│ clicky-0001 │                     1 │          11.66 │ new           │ 2026-09-15 01:15:32 │ 2026-09-15 01:16:16 │
│ clicky-0001 │                     2 │          18.02 │ returning     │ 2026-09-15 01:16:16 │                ᴺᵁᴸᴸ │
│ danny-0001  │                     1 │          19.76 │ new           │ 2026-09-15 01:15:32 │                ᴺᵁᴸᴸ │
└─────────────┴───────────────────────┴────────────────┴───────────────┴─────────────────────┴─────────────────────┘
```

داخليًا، يبني الـ adapter النسخة الجديدة من الـ snapshot في جدول `customers_snapshot__snapshot_upsert` ثم يستبدلها باستخدام `EXCHANGE TABLES` (أو عبر drop وrename في الحالات التي لا يستطيع فيها الخادم تبديل الجداول)، بحيث يرى القُرّاء إمّا النسخة السابقة أو النسخة الجديدة من الـ snapshot. راجع [قسم snapshot في صفحة materializations](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations#snapshot) للاطلاع على مرجع التهيئة.

<h2 id="materialized-views">
  استخدام العروض المُجسَّدة
</h2>

كل ما سبق يتطلّب تنفيذ `dbt run` لإدخال البيانات الجديدة إلى الـ models. أما [العروض المُجسَّدة](/ar/concepts/features/materialized-views/index) في ClickHouse فتعمل بطريقة مختلفة: فهي بمثابة insert triggers. فكل كتلة من الصفوف تُدرَج في الـ source table يحوّلها الـ `SELECT` الخاص بالـ view ثم تُكتب في الـ target table، دون أي scheduling. ويوفّر الـ adapter الوصول إليها عبر الـ materialization المسمّى `materialized_view`.

أنشئ الملف `models/marts/daily_store_revenue.sql` ليحتوي على عدد الطلبات والإيرادات لكل متجر ولكل يوم، بالقراءة مباشرةً من جدول الطلبات الخام:

```sql theme={null}
{{
    config(
        materialized='materialized_view',
        engine='SummingMergeTree()',
        order_by='(order_date, store_id)'
    )
}}

select
    toDate(ordered_at) as order_date,
    store_id,
    count() as orders,
    sum(order_total) as revenue_cents
from {{ source('ecom', 'raw_orders') }}
group by order_date, store_id
```

تُطبَّق قيمتا `engine` و`order_by` على الجدول الهدف. ويجمع `SummingMergeTree` الأعمدة الرقمية للصفوف التي تتشارك نفس مفتاح الفرز (sorting key) عند دمج أجزاء البيانات، وهو تحديدًا ما يتطلبه التجميع لكل يوم ولكل متجر.

```bash theme={null}
dbt run --select daily_store_revenue
```

```response theme={null}
1 of 1 START sql materialized_view model `jaffle_shop`.`daily_store_revenue` ... [RUN]
1 of 1 OK created sql materialized_view model `jaffle_shop`.`daily_store_revenue`  [OK in 0.25s]
```

أنشأ الـ adapter كائنين: الـ target table الذي يحمل اسم الـ model، والـ materialized view نفسها مع الـ suffix `_mv`، والتي تشير إلى الـ target table عبر عبارة `TO`. وافتراضيًا (`catchup=True`)، تم أيضًا إجراء backfill للـ target table بالطلبات الموجودة:

```sql theme={null}
SELECT name, engine
FROM system.tables
WHERE database = 'jaffle_shop' AND name LIKE 'daily_store_revenue%';
```

```response theme={null}
┌─name───────────────────┬─engine───────────┐
│ daily_store_revenue    │ SummingMergeTree │
│ daily_store_revenue_mv │ MaterializedView │
└────────────────────────┴──────────────────┘
```

```sql theme={null}
SHOW CREATE TABLE jaffle_shop.daily_store_revenue_mv;
```

```response theme={null}
CREATE MATERIALIZED VIEW jaffle_shop.daily_store_revenue_mv TO jaffle_shop.daily_store_revenue
(
    `order_date` Date,
    `store_id` String,
    `orders` UInt64,
    `revenue_cents` Int64
)
AS SELECT
    toDate(ordered_at) AS order_date,
    store_id,
    count() AS orders,
    sum(order_total) AS revenue_cents
FROM raw.raw_orders
GROUP BY
    order_date,
    store_id
```

الآن أدرج طلبًا خامًا آخر لـ Danny، دون تشغيل dbt بعد ذلك:

```sql theme={null}
INSERT INTO raw.raw_orders VALUES
    ('danny-order-0002', 'danny-0001', now(),
     '40e6ddd6-b8f6-4e17-8bd6-5e53966809d2', 1400, 56, 1456);

INSERT INTO raw.raw_items VALUES ('danny-item-0003', 'danny-order-0002', 'JAF-004');
```

يعكس الجدول الهدف ذلك بالفعل. فلدى Brooklyn الآن طلبان اليوم:

```sql theme={null}
SELECT order_date, store_id, sum(orders) AS orders, sum(revenue_cents) AS revenue_cents
FROM jaffle_shop.daily_store_revenue
WHERE order_date >= yesterday()
GROUP BY order_date, store_id
ORDER BY order_date, store_id;
```

```response theme={null}
┌─order_date─┬─store_id─────────────────────────────┬─orders─┬─revenue_cents─┐
│ 2026-09-14 │ 4b6c2304-2b9e-41e4-942a-cf11a1819378 │      1 │          1166 │
│ 2026-09-15 │ 40e6ddd6-b8f6-4e17-8bd6-5e53966809d2 │      2 │          3432 │
│ 2026-09-15 │ 4b6c2304-2b9e-41e4-942a-cf11a1819378 │      1 │           636 │
└────────────┴──────────────────────────────────────┴────────┴───────────────┘
```

يُجري الاستعلام التجميع باستخدام `sum()` و`GROUP BY` عن قصد: فمحرك `SummingMergeTree` لا يدمج الصفوف ذات المفتاح نفسه إلا عند دمج أجزاء البيانات في الخلفية، ولذلك يظل طلبا Brooklyn صفّين منفصلين في الجدول حتى ذلك الحين. لذا اجمع البيانات دائمًا عند القراءة (أو استخدم `FINAL`) مع محركات الجمع والتجميع. وفي الوقت نفسه، لا يزال النموذج التزايدي `orders` يحتوي على طلب واحد فقط لـ Danny حتى تنفيذ `dbt run` التالي.

تُبقي عمليات `dbt run` اللاحقة على الجدول الهدف وبياناته، ولا تحدّث سوى تعريف العرض، باستخدام `ALTER TABLE ... MODIFY QUERY` عندما يسمح التغيير بذلك، لذا من الآمن الإبقاء على النموذج ضمن المشروع. أما `dbt run --full-refresh` فيعيد بناء الجدول الهدف ويملؤه بالبيانات التاريخية من جديد (ما لم تكن قيمة `catchup` هي `False`). وتغطي [صفحة العروض المجسّدة](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materialization-materialized-view) ما تبقّى: تغييرات المخطط عبر `on_schema_change`، وتعطيل الملء التاريخي باستخدام `catchup`، والعروض المجسّدة القابلة للتحديث، وتغذية عدة عروض للهدف نفسه، وتعريف الجدول الهدف كنموذج مستقل بذاته.

<h2 id="further-information">
  معلومات إضافية
</h2>

لا يتطرق هذا الدليل إلا إلى أساسيات dbt. وتُعد [وثائق dbt](https://docs.getdbt.com/docs/introduction) المرجع لكل ما هو غير خاص بـ ClickHouse. أما بالنسبة إلى adapter، فراجع صفحة [features and configurations](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/features-and-configurations) للاطلاع على إعدادات profile والميزات العامة، وصفحة [تجسيدات](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/materializations) لكل تهيئة وردت أعلاه، وصفحة [dbt OSS وdbt v2 ومنصة dbt](/ar/integrations/connectors/data-ingestion/etl-tools/dbt/dbt-core-v2-fusion-and-platform) إذا كنت تستخدم dbt OSS أو dbt v2 أو منصة dbt. ونرحّب بالمساهمات بأمثلة جديدة في [Jaffle Shop for ClickHouse](https://github.com/ClickHouse/jaffle-shop-clickhouse).
