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

> Руководства по работе с 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>;
};

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

<ClickHouseSupportedBadge />

В этом разделе приведены руководства по настройке dbt и адаптера ClickHouse, а также пример использования dbt с ClickHouse на общедоступном наборе данных IMDB. Пример включает следующие шаги:

1. Создание проекта dbt и настройка адаптера ClickHouse.
2. Определение модели.
3. Обновление модели.
4. Создание инкрементальной модели.
5. Создание модели-снимка.
6. Использование materialized views.

Эти руководства предназначены для использования вместе с остальной [документацией](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/index), разделом [возможностей и конфигураций](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/features-and-configurations) и [справочником по материализациям](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/materializations).

<div id="setup">
  ## Настройка
</div>

Следуйте инструкциям в разделе [Настройка dbt и адаптера ClickHouse](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/index), чтобы подготовить окружение.

**Важно: приведённые ниже инструкции протестированы с Python 3.9.**

<div id="prepare-clickhouse">
  ### Подготовьте ClickHouse
</div>

dbt особенно хорошо подходит для моделирования сильно связанных реляционных данных. В качестве примера мы используем небольшой набор данных IMDB со следующей реляционной схемой. Этот набор данных взят из [репозитория реляционных наборов данных](https://relational.fit.cvut.cz/dataset/IMDb). По сравнению с типичными схемами, используемыми в dbt, он весьма прост, но при этом представляет собой удобный небольшой образец:

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_01.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=966119520059d8223dac8c84e5908794" size="lg" alt="Схема таблиц IMDB" width="2623" height="921" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_01.webp" />

Как показано ниже, мы используем подмножество этих таблиц.

Создайте следующие таблицы:

```sql theme={null}
CREATE DATABASE imdb;

CREATE TABLE imdb.actors
(
    id         UInt32,
    first_name String,
    last_name  String,
    gender     FixedString(1)
) ENGINE = MergeTree ORDER BY (id, first_name, last_name, gender);

CREATE TABLE imdb.directors
(
    id         UInt32,
    first_name String,
    last_name  String
) ENGINE = MergeTree ORDER BY (id, first_name, last_name);

CREATE TABLE imdb.genres
(
    movie_id UInt32,
    genre    String
) ENGINE = MergeTree ORDER BY (movie_id, genre);

CREATE TABLE imdb.movie_directors
(
    director_id UInt32,
    movie_id    UInt64
) ENGINE = MergeTree ORDER BY (director_id, movie_id);

CREATE TABLE imdb.movies
(
    id   UInt32,
    name String,
    year UInt32,
    rank Float32 DEFAULT 0
) ENGINE = MergeTree ORDER BY (id, name, year);

CREATE TABLE imdb.roles
(
    actor_id   UInt32,
    movie_id   UInt32,
    role       String,
    created_at DateTime DEFAULT now()
) ENGINE = MergeTree ORDER BY (actor_id, movie_id);
```

<Note>
  Столбец `created_at` в таблице `roles`; по умолчанию для него задано значение `now()`. Позже мы используем его, чтобы определять инкрементальные обновления наших моделей — см. [Инкрементальные модели](#creating-an-incremental-materialization).
</Note>

Мы используем функцию `s3`, чтобы читать исходные данные из общедоступных конечных точек и выполнять вставку данных. Выполните следующие команды, чтобы заполнить таблицы:

```sql theme={null}
INSERT INTO imdb.actors
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_actors.tsv.gz',
'TSVWithNames');

INSERT INTO imdb.directors
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_directors.tsv.gz',
'TSVWithNames');

INSERT INTO imdb.genres
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_movies_genres.tsv.gz',
'TSVWithNames');

INSERT INTO imdb.movie_directors
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_movies_directors.tsv.gz',
        'TSVWithNames');

INSERT INTO imdb.movies
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_movies.tsv.gz',
'TSVWithNames');

INSERT INTO imdb.roles(actor_id, movie_id, role)
SELECT actor_id, movie_id, role
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/imdb/imdb_ijs_roles.tsv.gz',
'TSVWithNames');
```

Время выполнения этих действий может различаться в зависимости от пропускной способности вашего соединения, но каждое из них должно занимать всего несколько секунд. Выполните следующий запрос, чтобы получить сводку по каждому актёру, отсортированную по количеству появлений в фильмах, и убедиться, что данные были успешно загружены:

```sql theme={null}
SELECT id,
       any(actor_name)          AS name,
       uniqExact(movie_id)    AS num_movies,
       avg(rank)                AS avg_rank,
       uniqExact(genre)         AS unique_genres,
       uniqExact(director_name) AS uniq_directors,
       max(created_at)          AS updated_at
FROM (
         SELECT imdb.actors.id  AS id,
                concat(imdb.actors.first_name, ' ', imdb.actors.last_name)  AS actor_name,
                imdb.movies.id AS movie_id,
                imdb.movies.rank AS rank,
                genre,
                concat(imdb.directors.first_name, ' ', imdb.directors.last_name) AS director_name,
                created_at
         FROM imdb.actors
                  JOIN imdb.roles ON imdb.roles.actor_id = imdb.actors.id
                  LEFT OUTER JOIN imdb.movies ON imdb.movies.id = imdb.roles.movie_id
                  LEFT OUTER JOIN imdb.genres ON imdb.genres.movie_id = imdb.movies.id
                  LEFT OUTER JOIN imdb.movie_directors ON imdb.movie_directors.movie_id = imdb.movies.id
                  LEFT OUTER JOIN imdb.directors ON imdb.directors.id = imdb.movie_directors.director_id
         )
GROUP BY id
ORDER BY num_movies DESC
LIMIT 5;
```

Ответ должен выглядеть следующим образом:

```response theme={null}
+------+------------+----------+------------------+-------------+--------------+-------------------+
|id    |name        |num_movies|avg_rank          |unique_genres|uniq_directors|updated_at         |
+------+------------+----------+------------------+-------------+--------------+-------------------+
|45332 |Mel Blanc   |832       |6.175853582979779 |18           |84            |2022-04-26 14:01:45|
|621468|Bess Flowers|659       |5.57727638854796  |19           |293           |2022-04-26 14:01:46|
|372839|Lee Phelps  |527       |5.032976449684617 |18           |261           |2022-04-26 14:01:46|
|283127|Tom London  |525       |2.8721716524875673|17           |203           |2022-04-26 14:01:46|
|356804|Bud Osborne |515       |2.0389507108727773|15           |149           |2022-04-26 14:01:46|
+------+------------+----------+------------------+-------------+--------------+-------------------+
```

В последующих руководствах мы преобразуем этот запрос в модель и материализуем её в ClickHouse как dbt-представление и таблицу.

<div id="connecting-to-clickhouse">
  ## Подключение к ClickHouse
</div>

1. Создайте проект dbt. В этом случае мы назовём его по имени нашего источника `imdb`. Когда появится запрос, выберите `clickhouse` в качестве источника базы данных.

   ```bash theme={null}
   clickhouse-user@clickhouse:~$ dbt init imdb

   16:52:40  Running with dbt=1.1.0
   Which database would you like to use?
   [1] clickhouse

   (Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)

   Enter a number: 1
   16:53:21  No sample profile found for clickhouse.
   16:53:21
   Your new dbt project "imdb" was created!

   For more information on how to configure the profiles.yml file,
   please consult the dbt documentation here:

   https://docs.getdbt.com/docs/configure-your-profile
   ```

2. Перейдите в каталог проекта с помощью `cd`:

   ```bash theme={null}
   cd imdb
   ```

3. На этом этапе вам понадобится любой текстовый редактор. В примерах ниже мы используем популярный VS Code. Открыв каталог IMDB, вы должны увидеть набор файлов yml и sql:

   <Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_02.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=1e758dcd7fabd633f1250792a0d866ed" size="lg" alt="Новый проект dbt" width="1113" height="1087" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_02.webp" />

4. Обновите файл `dbt_project.yml`, чтобы указать нашу первую модель — `actor_summary`, и задайте профиль `clickhouse_imdb`.

   <Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_03.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=7ab628f8cf11f9d6c526b0f0f1457d0a" size="lg" alt="Профиль dbt" width="512" height="28" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_03.webp" />

   <Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_04.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=7c3d8306cc50c94bb253dbc8a316870d" size="lg" alt="Профиль dbt" width="512" height="74" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_04.webp" />

5. Далее нужно указать для dbt сведения о подключении к вашему экземпляру ClickHouse. Добавьте следующее в `~/.dbt/profiles.yml`.

   ```yml theme={null}
   clickhouse_imdb:
     target: dev
     outputs:
       dev:
         type: clickhouse
         schema: imdb_dbt
         host: localhost
         port: 8123
         user: default
         password: ''
         secure: False
   ```

   Обратите внимание: нужно изменить имя пользователя и пароль. Дополнительные доступные настройки описаны [здесь](https://github.com/silentsokolov/dbt-clickhouse#example-profile).

6. Находясь в каталоге IMDB, выполните команду `dbt debug`, чтобы проверить, может ли dbt подключиться к ClickHouse.

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt debug
   17:33:53  Running with dbt=1.1.0
   dbt version: 1.1.0
   python version: 3.10.1
   python path: /home/dale/.pyenv/versions/3.10.1/bin/python3.10
   os info: Linux-5.13.0-10039-tuxedo-x86_64-with-glibc2.31
   Using profiles.yml file at /home/dale/.dbt/profiles.yml
   Using dbt_project.yml file at /opt/dbt/imdb/dbt_project.yml

   Configuration:
   profiles.yml file [OK found and valid]
   dbt_project.yml file [OK found and valid]

   Required dependencies:
   - git [OK found]

   Connection:
   host: localhost
   port: 8123
   user: default
   schema: imdb_dbt
   secure: False
   verify: False
   Connection test: [OK connection ok]

   All checks passed!
   ```

   Убедитесь, что в выводе есть строка `Connection test: [OK connection ok]`, которая указывает на успешное подключение.

<div id="creating-a-simple-view-materialization">
  ## Создание простой материализации представления
</div>

При использовании материализации представления модель при каждом запуске заново создаётся как представление с помощью оператора `CREATE VIEW AS` в ClickHouse. Это не требует дополнительного хранения данных, но запросы к такому представлению будут выполняться медленнее, чем при материализации в таблицы.

1. В папке `imdb` удалите каталог `models/example`:

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ rm -rf models/example
   ```

2. Создайте новый файл в каталоге `actors` внутри папки `models`. Здесь мы создаем файлы, каждый из которых соответствует отдельной модели actor:

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ mkdir models/actors
   ```

3. Создайте файлы `schema.yml` и `actor_summary.sql` в папке `models/actors`.

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ touch models/actors/actor_summary.sql
   clickhouse-user@clickhouse:~/imdb$ touch models/actors/schema.yml
   ```

   Файл `schema.yml` определяет наши таблицы. После этого их можно будет использовать в макросах.  Отредактируйте
   `models/actors/schema.yml`, чтобы он содержал следующее содержимое:

   ```yml theme={null}
   version: 2

   sources:
   - name: imdb
     tables:
     - name: directors
     - name: actors
     - name: roles
     - name: movies
     - name: genres
     - name: movie_directors
   ```

   `actors_summary.sql` определяет нашу фактическую модель. Обратите внимание, что в функции `config` мы также указываем, что модель должна быть материализована как представление в ClickHouse. На наши таблицы есть ссылки из файла `schema.yml` через функцию `source`, например `source('imdb', 'movies')` ссылается на таблицу `movies` в базе данных `imdb`. Отредактируйте `models/actors/actors_summary.sql`, чтобы он содержал следующее:

   ```sql theme={null}
   {{ config(materialized='view') }}

   with actor_summary as (
   SELECT id,
       any(actor_name) as name,
       uniqExact(movie_id)    as num_movies,
       avg(rank)                as avg_rank,
       uniqExact(genre)         as genres,
       uniqExact(director_name) as directors,
       max(created_at) as updated_at
   FROM (
           SELECT {{ source('imdb', 'actors') }}.id as id,
                   concat({{ source('imdb', 'actors') }}.first_name, ' ', {{ source('imdb', 'actors') }}.last_name) as actor_name,
                   {{ source('imdb', 'movies') }}.id as movie_id,
                   {{ source('imdb', 'movies') }}.rank as rank,
                   genre,
                   concat({{ source('imdb', 'directors') }}.first_name, ' ', {{ source('imdb', 'directors') }}.last_name) as director_name,
                   created_at
           FROM {{ source('imdb', 'actors') }}
                       JOIN {{ source('imdb', 'roles') }} ON {{ source('imdb', 'roles') }}.actor_id = {{ source('imdb', 'actors') }}.id
                       LEFT OUTER JOIN {{ source('imdb', 'movies') }} ON {{ source('imdb', 'movies') }}.id = {{ source('imdb', 'roles') }}.movie_id
                       LEFT OUTER JOIN {{ source('imdb', 'genres') }} ON {{ source('imdb', 'genres') }}.movie_id = {{ source('imdb', 'movies') }}.id
                       LEFT OUTER JOIN {{ source('imdb', 'movie_directors') }} ON {{ source('imdb', 'movie_directors') }}.movie_id = {{ source('imdb', 'movies') }}.id
                       LEFT OUTER JOIN {{ source('imdb', 'directors') }} ON {{ source('imdb', 'directors') }}.id = {{ source('imdb', 'movie_directors') }}.director_id
           )
   GROUP BY id
   )

   select *
   from actor_summary
   ```

   Обратите внимание, что мы включаем столбец `updated_at` в итоговое actor\_summary. Позже он понадобится для инкрементальных материализаций.

4. В каталоге `imdb` выполните команду `dbt run`.

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt run
   15:05:35  Running with dbt=1.1.0
   15:05:35  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 6 sources, 0 exposures, 0 metrics
   15:05:35
   15:05:36  Concurrency: 1 threads (target='dev')
   15:05:36
   15:05:36  1 of 1 START view model imdb_dbt.actor_summary.................................. [RUN]
   15:05:37  1 of 1 OK created view model imdb_dbt.actor_summary............................. [OK in 1.00s]
   15:05:37
   15:05:37  Finished running 1 view model in 1.97s.
   15:05:37
   15:05:37  Completed successfully
   15:05:37
   15:05:37  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

5. dbt представит модель как представление в ClickHouse, как и было запрошено. Теперь мы можем выполнять запросы к этому представлению напрямую. Это представление будет создано в базе данных `imdb_dbt` — это определяется параметром `schema` в файле `~/.dbt/profiles.yml` в профиле `clickhouse_imdb`.

   ```sql theme={null}
   SHOW DATABASES;
   ```

   ```response theme={null}
   +------------------+
   |name              |
   +------------------+
   |INFORMATION_SCHEMA|
   |default           |
   |imdb              |
   |imdb_dbt          |  <---создано dbt!
   |information_schema|
   |system            |
   +------------------+
   ```

   Выполнив запрос к этому представлению, мы можем получить те же результаты, что и в предыдущем запросе, но с более простым синтаксисом:

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary ORDER BY num_movies DESC LIMIT 5;
   ```

   ```response theme={null}
   +------+------------+----------+------------------+------+---------+-------------------+
   |id    |name        |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+------------+----------+------------------+------+---------+-------------------+
   |45332 |Mel Blanc   |832       |6.175853582979779 |18    |84       |2022-04-26 15:26:55|
   |621468|Bess Flowers|659       |5.57727638854796  |19    |293      |2022-04-26 15:26:57|
   |372839|Lee Phelps  |527       |5.032976449684617 |18    |261      |2022-04-26 15:26:56|
   |283127|Tom London  |525       |2.8721716524875673|17    |203      |2022-04-26 15:26:56|
   |356804|Bud Osborne |515       |2.0389507108727773|15    |149      |2022-04-26 15:26:56|
   +------+------------+----------+------------------+------+---------+-------------------+
   ```

<div id="creating-a-table-materialization">
  ## Создание материализации в таблицу
</div>

В предыдущем примере наша модель была материализована как представление. Хотя для некоторых запросов этого может быть достаточно, более сложные SELECT-запросы или часто выполняемые запросы лучше материализовать в таблицу. Такая материализация полезна для моделей, к которым обращаются BI-инструменты, чтобы обеспечить пользователям более высокую скорость работы. По сути, результаты запроса сохраняются в новой таблице с соответствующими накладными расходами на хранение — фактически выполняется `INSERT TO SELECT`. Обратите внимание, что эта таблица будет пересоздаваться каждый раз, то есть она не является инкрементальной. Поэтому большие результирующие наборы могут приводить к длительному времени выполнения — см. [Ограничения dbt](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations).

1. Измените файл `actors_summary.sql`, чтобы параметр `materialized` был установлен в `table`. Обратите внимание, как задан `ORDER BY`, а также на то, что мы используем движок таблицы `MergeTree`:

   ```sql theme={null}
   {{ config(order_by='(updated_at, id, name)', engine='MergeTree()', materialized='table') }}
   ```

2. Из каталога `imdb` выполните команду `dbt run`. Выполнение может занять немного больше времени — около 10 с на большинстве машин.

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt run
   15:13:27  Running with dbt=1.1.0
   15:13:27  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 6 sources, 0 exposures, 0 metrics
   15:13:27
   15:13:28  Concurrency: 1 threads (target='dev')
   15:13:28
   15:13:28  1 of 1 START table model imdb_dbt.actor_summary................................. [RUN]
   15:13:37  1 of 1 OK created table model imdb_dbt.actor_summary............................ [OK in 9.22s]
   15:13:37
   15:13:37  Finished running 1 table model in 10.20s.
   15:13:37
   15:13:37  Completed successfully
   15:13:37
   15:13:37  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

3. Подтвердите создание таблицы `imdb_dbt.actor_summary`:

   ```sql theme={null}
   SHOW CREATE TABLE imdb_dbt.actor_summary;
   ```

   Вы должны увидеть таблицу с соответствующими типами данных:

   ```response theme={null}
   +----------------------------------------
   |statement
   +----------------------------------------
   |CREATE TABLE imdb_dbt.actor_summary
   |(
   |`id` UInt32,
   |`first_name` String,
   |`last_name` String,
   |`num_movies` UInt64,
   |`updated_at` DateTime
   |)
   |ENGINE = MergeTree
   |ORDER BY (id, first_name, last_name)
   +----------------------------------------
   ```

4. Убедитесь, что результаты из этой таблицы совпадают с предыдущими результатами. Обратите внимание на заметное улучшение времени отклика теперь, когда модель материализована как таблица:

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary ORDER BY num_movies DESC LIMIT 5;
   ```

   ```response theme={null}
   +------+------------+----------+------------------+------+---------+-------------------+
   |id    |name        |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+------------+----------+------------------+------+---------+-------------------+
   |45332 |Mel Blanc   |832       |6.175853582979779 |18    |84       |2022-04-26 15:26:55|
   |621468|Bess Flowers|659       |5.57727638854796  |19    |293      |2022-04-26 15:26:57|
   |372839|Lee Phelps  |527       |5.032976449684617 |18    |261      |2022-04-26 15:26:56|
   |283127|Tom London  |525       |2.8721716524875673|17    |203      |2022-04-26 15:26:56|
   |356804|Bud Osborne |515       |2.0389507108727773|15    |149      |2022-04-26 15:26:56|
   +------+------------+----------+------------------+------+---------+-------------------+
   ```

   При желании можете выполнить и другие запросы к этой модели. Например, у каких актёров самые высоко оценённые фильмы среди тех, кто снялся более чем в 5 фильмах?

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary WHERE num_movies > 5 ORDER BY avg_rank  DESC LIMIT 10;
   ```

<div id="creating-an-incremental-materialization">
  ## Создание инкрементальной материализации
</div>

В предыдущем примере была создана таблица для материализации модели. Эта таблица будет пересоздаваться при каждом запуске dbt. Для больших результирующих наборов или сложных преобразований это может быть непрактично и чрезвычайно затратно. Чтобы решить эту проблему и сократить время сборки, dbt предлагает инкрементальные материализации. Они позволяют dbt выполнять вставку или обновление записей в таблице с момента последнего запуска, что делает такой подход подходящим для данных событийного типа. Внутри создаётся временная таблица со всеми обновлёнными записями, после чего все неизменённые и обновлённые записи вставляются в новую целевую таблицу. В результате для больших результирующих наборов возникают ограничения, аналогичные [ограничениям](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations) модели table.

Чтобы обойти эти ограничения для больших наборов данных, адаптер поддерживает режим 'inserts\_only', при котором все обновления вставляются в целевую таблицу без создания временной таблицы (подробнее об этом ниже).

Чтобы проиллюстрировать этот пример, мы добавим актёра "Clicky McClickHouse", который появится в невероятных 910 фильмах, — это гарантирует, что он снялся в большем числе фильмов, чем даже [Mel Blanc](https://en.wikipedia.org/wiki/Mel_Blanc).

1. Сначала изменим нашу модель, задав для неё тип incremental. Это требует:

   1. **unique\_key** - Чтобы адаптер мог однозначно идентифицировать строки, необходимо указать unique\_key — в данном случае достаточно поля `id` из нашего запроса. Это гарантирует отсутствие дубликатов строк в нашей материализованной таблице. Подробнее об ограничениях уникальности см. [здесь](https://docs.getdbt.com/docs/building-a-dbt-project/building-models/configuring-incremental-models#defining-a-uniqueness-constraint-optional).
   2. **Incremental filter** - Нам также нужно указать dbt, как определять, какие строки изменились при инкрементальном запуске. Для этого задаётся дельта-выражение. Обычно для данных событий используется временная метка, поэтому мы берём поле updated\_at. Этот столбец, которому при вставке строк по умолчанию присваивается значение now(), позволяет выявлять новые роли. Кроме того, нужно учесть альтернативный сценарий, когда добавляются новые акторы. Используя переменную `{{this}}` для обозначения существующей материализованной таблицы, получаем выражение `where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}})`. Мы помещаем его внутрь условия `{% if is_incremental() %}`, чтобы оно применялось только при инкрементальных запусках, а не при первоначальном создании таблицы. Подробнее о фильтрации строк для инкрементальных моделей см. [в этом разделе документации dbt](https://docs.getdbt.com/docs/building-a-dbt-project/building-models/configuring-incremental-models#filtering-rows-on-an-incremental-run).

   Обновите файл `actor_summary.sql` следующим образом:

   ```sql theme={null}
   {{ config(order_by='(updated_at, id, name)', engine='MergeTree()', materialized='incremental', unique_key='id') }}
   with actor_summary as (
       SELECT id,
           any(actor_name) as name,
           uniqExact(movie_id)    as num_movies,
           avg(rank)                as avg_rank,
           uniqExact(genre)         as genres,
           uniqExact(director_name) as directors,
           max(created_at) as updated_at
       FROM (
           SELECT {{ source('imdb', 'actors') }}.id as id,
               concat({{ source('imdb', 'actors') }}.first_name, ' ', {{ source('imdb', 'actors') }}.last_name) as actor_name,
               {{ source('imdb', 'movies') }}.id as movie_id,
               {{ source('imdb', 'movies') }}.rank as rank,
               genre,
               concat({{ source('imdb', 'directors') }}.first_name, ' ', {{ source('imdb', 'directors') }}.last_name) as director_name,
               created_at
       FROM {{ source('imdb', 'actors') }}
           JOIN {{ source('imdb', 'roles') }} ON {{ source('imdb', 'roles') }}.actor_id = {{ source('imdb', 'actors') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'movies') }} ON {{ source('imdb', 'movies') }}.id = {{ source('imdb', 'roles') }}.movie_id
           LEFT OUTER JOIN {{ source('imdb', 'genres') }} ON {{ source('imdb', 'genres') }}.movie_id = {{ source('imdb', 'movies') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'movie_directors') }} ON {{ source('imdb', 'movie_directors') }}.movie_id = {{ source('imdb', 'movies') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'directors') }} ON {{ source('imdb', 'directors') }}.id = {{ source('imdb', 'movie_directors') }}.director_id
       )
       GROUP BY id
   )
   select *
   from actor_summary

   {% if is_incremental() %}

   -- этот фильтр применяется только при инкрементальном запуске
   where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}})

   {% endif %}
   ```

   Обратите внимание, что наша модель будет реагировать только на обновления и добавления в таблицах `roles` и `actors`. Чтобы она реагировала на все таблицы, рекомендуется разделить эту модель на несколько подмоделей, каждая из которых будет иметь собственные критерии инкрементальности. На эти модели, в свою очередь, можно ссылаться и связывать их между собой. Дополнительные сведения о перекрёстных ссылках между моделями см. [здесь](https://docs.getdbt.com/reference/dbt-jinja-functions/ref).

2. Выполните `dbt run` и проверьте результаты в созданной таблице:

   ```response theme={null}
   clickhouse-user@clickhouse:~/imdb$  dbt run
   15:33:34  Running with dbt=1.1.0
   15:33:34  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 6 sources, 0 exposures, 0 metrics
   15:33:34
   15:33:35  Concurrency: 1 threads (target='dev')
   15:33:35
   15:33:35  1 of 1 START incremental model imdb_dbt.actor_summary........................... [RUN]
   15:33:41  1 of 1 OK created incremental model imdb_dbt.actor_summary...................... [OK in 6.33s]
   15:33:41
   15:33:41  Finished running 1 incremental model in 7.30s.
   15:33:41
   15:33:41  Completed successfully
   15:33:41
   15:33:41  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary ORDER BY num_movies DESC LIMIT 5;
   ```

   ```response theme={null}
   +------+------------+----------+------------------+------+---------+-------------------+
   |id    |name        |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+------------+----------+------------------+------+---------+-------------------+
   |45332 |Mel Blanc   |832       |6.175853582979779 |18    |84       |2022-04-26 15:26:55|
   |621468|Bess Flowers|659       |5.57727638854796  |19    |293      |2022-04-26 15:26:57|
   |372839|Lee Phelps  |527       |5.032976449684617 |18    |261      |2022-04-26 15:26:56|
   |283127|Tom London  |525       |2.8721716524875673|17    |203      |2022-04-26 15:26:56|
   |356804|Bud Osborne |515       |2.0389507108727773|15    |149      |2022-04-26 15:26:56|
   +------+------------+----------+------------------+------+---------+-------------------+
   ```

3. Теперь добавим в нашу модель данные, чтобы показать инкрементное обновление. Добавьте актёра "Clicky McClickHouse" в таблицу `actors`:

   ```sql theme={null}
   INSERT INTO imdb.actors VALUES (845466, 'Clicky', 'McClickHouse', 'M');
   ```

4. Пусть «Clicky» появится в 910 случайных фильмах:

   ```sql theme={null}
   INSERT INTO imdb.roles
   SELECT now() as created_at, 845466 as actor_id, id as movie_id, 'Himself' as role
   FROM imdb.movies
   LIMIT 910 OFFSET 10000;
   ```

5. Подтвердите, что теперь именно он — актёр с наибольшим числом появлений, выполнив запрос напрямую к исходной таблице в обход любых моделей dbt:

   ```sql theme={null}
   SELECT id,
       any(actor_name)          as name,
       uniqExact(movie_id)    as num_movies,
       avg(rank)                as avg_rank,
       uniqExact(genre)         as unique_genres,
       uniqExact(director_name) as uniq_directors,
       max(created_at)          as updated_at
   FROM (
           SELECT imdb.actors.id                                                   as id,
                   concat(imdb.actors.first_name, ' ', imdb.actors.last_name)       as actor_name,
                   imdb.movies.id as movie_id,
                   imdb.movies.rank                                                 as rank,
                   genre,
                   concat(imdb.directors.first_name, ' ', imdb.directors.last_name) as director_name,
                   created_at
           FROM imdb.actors
                   JOIN imdb.roles ON imdb.roles.actor_id = imdb.actors.id
                   LEFT OUTER JOIN imdb.movies ON imdb.movies.id = imdb.roles.movie_id
                   LEFT OUTER JOIN imdb.genres ON imdb.genres.movie_id = imdb.movies.id
                   LEFT OUTER JOIN imdb.movie_directors ON imdb.movie_directors.movie_id = imdb.movies.id
                   LEFT OUTER JOIN imdb.directors ON imdb.directors.id = imdb.movie_directors.director_id
           )
   GROUP BY id
   ORDER BY num_movies DESC
   LIMIT 2;
   ```

   ```response theme={null}
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |id    |name               |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |845466|Clicky McClickHouse|910       |1.4687938697032283|21    |662      |2022-04-26 16:20:36|
   |45332 |Mel Blanc          |909       |5.7884792542982515|19    |148      |2022-04-26 16:17:42|
   +------+-------------------+----------+------------------+------+---------+-------------------+
   ```

6. Выполните `dbt run` и убедитесь, что наша модель обновилась и соответствует приведённым выше результатам:

   ```response theme={null}
   clickhouse-user@clickhouse:~/imdb$  dbt run
   16:12:16  Running with dbt=1.1.0
   16:12:16  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 6 sources, 0 exposures, 0 metrics
   16:12:16
   16:12:17  Concurrency: 1 threads (target='dev')
   16:12:17
   16:12:17  1 of 1 START incremental model imdb_dbt.actor_summary........................... [RUN]
   16:12:24  1 of 1 OK created incremental model imdb_dbt.actor_summary...................... [OK in 6.82s]
   16:12:24
   16:12:24  Finished running 1 incremental model in 7.79s.
   16:12:24
   16:12:24  Completed successfully
   16:12:24
   16:12:24  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary ORDER BY num_movies DESC LIMIT 2;
   ```

   ```response theme={null}
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |id    |name               |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |845466|Clicky McClickHouse|910       |1.4687938697032283|21    |662      |2022-04-26 16:20:36|
   |45332 |Mel Blanc          |909       |5.7884792542982515|19    |148      |2022-04-26 16:17:42|
   +------+-------------------+----------+------------------+------+---------+-------------------+
   ```

<div id="internals">
  ### Внутреннее устройство
</div>

Мы можем определить, какие команды были выполнены для описанного выше инкрементального обновления, выполнив запрос к журналу запросов ClickHouse.

```sql theme={null}
SELECT event_time, query  FROM system.query_log WHERE type='QueryStart' AND query LIKE '%dbt%'
AND event_time > subtractMinutes(now(), 15) ORDER BY event_time LIMIT 100;
```

Скорректируйте приведённый выше запрос под период выполнения. Анализ результатов оставляем пользователю, а здесь выделим общую стратегию, которую адаптер использует для инкрементальных обновлений:

1. Адаптер создаёт временную таблицу `actor_sumary__dbt_tmp`. В неё передаются изменившиеся строки.
2. Создаётся новая таблица `actor_summary_new,`. Затем строки из старой таблицы переносятся в новую, при этом выполняется проверка, чтобы идентификаторы строк отсутствовали во временной таблице. Это позволяет корректно обрабатывать обновления и дубликаты.
3. Результаты из временной таблицы переносятся в новую таблицу `actor_summary`:
4. Наконец, новая таблица атомарно обменивается со старой версией с помощью оператора `EXCHANGE TABLES`. После этого старая и временная таблицы удаляются.

Это показано ниже:

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_05.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=21a9392d8a567b64531960cfc80b3de6" size="lg" alt="инкрементальные обновления dbt" width="1432" height="850" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_05.webp" />

Эта стратегия может вызывать трудности при работе с очень большими моделями. Подробнее см. в разделе [Ограничения](/docs/ru/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations).

<div id="append-strategy-inserts-only-mode">
  ### Стратегия Append (режим только вставки)
</div>

Чтобы обойти ограничения, связанные с большими наборами данных в инкрементальных моделях, адаптер использует параметр конфигурации dbt `incremental_strategy`. Ему можно задать значение `append`. В этом случае обновленные строки вставляются напрямую в целевую таблицу (то есть `imdb_dbt.actor_summary`), а временная таблица не создается.
Примечание: режим append-only требует, чтобы данные были неизменяемыми или чтобы дубликаты считались допустимыми. Если вам нужна инкрементальная модель таблицы с поддержкой изменяемых строк, не используйте этот режим!

Чтобы продемонстрировать этот режим, мы добавим еще одного нового актера и снова выполним `dbt run` с `incremental_strategy='append'`.

1. Настройте режим append-only в actor\_summary.sql:

   ```sql theme={null}
   {{ config(order_by='(updated_at, id, name)', engine='MergeTree()', materialized='incremental', unique_key='id', incremental_strategy='append') }}
   ```

2. Добавим еще одного известного актера — Danny DeBito

   ```sql theme={null}
   INSERT INTO imdb.actors VALUES (845467, 'Danny', 'DeBito', 'M');
   ```

3. Дадим Danny роли в 920 случайных фильмах.

   ```sql theme={null}
   INSERT INTO imdb.roles
   SELECT now() as created_at, 845467 as actor_id, id as movie_id, 'Himself' as role
   FROM imdb.movies
   LIMIT 920 OFFSET 10000;
   ```

4. Выполните `dbt run` и убедитесь, что Danny был добавлен в таблицу `actor_summary`

   ```response theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt run
   16:12:16  Running with dbt=1.1.0
   16:12:16  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 186 macros, 0 operations, 0 seed files, 6 sources, 0 exposures, 0 metrics
   16:12:16
   16:12:17  Concurrency: 1 threads (target='dev')
   16:12:17
   16:12:17  1 of 1 START incremental model imdb_dbt.actor_summary........................... [RUN]
   16:12:24  1 of 1 OK created incremental model imdb_dbt.actor_summary...................... [OK in 0.17s]
   16:12:24
   16:12:24  Finished running 1 incremental model in 0.19s.
   16:12:24
   16:12:24  Completed successfully
   16:12:24
   16:12:24  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

   ```sql theme={null}
   SELECT * FROM imdb_dbt.actor_summary ORDER BY num_movies DESC LIMIT 3;
   ```

   ```response theme={null}
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |id    |name               |num_movies|avg_rank          |genres|directors|updated_at         |
   +------+-------------------+----------+------------------+------+---------+-------------------+
   |845467|Danny DeBito       |920       |1.4768987303293204|21    |670      |2022-04-26 16:22:06|
   |845466|Clicky McClickHouse|910       |1.4687938697032283|21    |662      |2022-04-26 16:20:36|
   |45332 |Mel Blanc          |909       |5.7884792542982515|19    |148      |2022-04-26 16:17:42|
   +------+-------------------+----------+------------------+------+---------+-------------------+
   ```

Обратите внимание, насколько быстрее выполнился этот инкрементальный запуск по сравнению со вставкой для "Clicky".

Повторная проверка таблицы query\_log показывает различия между двумя инкрементальными запусками:

```sql theme={null}
INSERT INTO imdb_dbt.actor_summary ("id", "name", "num_movies", "avg_rank", "genres", "directors", "updated_at")
WITH actor_summary AS (
   SELECT id,
      any(actor_name) AS name,
      uniqExact(movie_id)    AS num_movies,
      avg(rank)                AS avg_rank,
      uniqExact(genre)         AS genres,
      uniqExact(director_name) AS directors,
      max(created_at) AS updated_at
   FROM (
      SELECT imdb.actors.id AS id,
         concat(imdb.actors.first_name, ' ', imdb.actors.last_name) AS actor_name,
         imdb.movies.id AS movie_id,
         imdb.movies.rank AS rank,
         genre,
         concat(imdb.directors.first_name, ' ', imdb.directors.last_name) AS director_name,
         created_at
      FROM imdb.actors
         JOIN imdb.roles ON imdb.roles.actor_id = imdb.actors.id
         LEFT OUTER JOIN imdb.movies ON imdb.movies.id = imdb.roles.movie_id
         LEFT OUTER JOIN imdb.genres ON imdb.genres.movie_id = imdb.movies.id
         LEFT OUTER JOIN imdb.movie_directors ON imdb.movie_directors.movie_id = imdb.movies.id
         LEFT OUTER JOIN imdb.directors ON imdb.directors.id = imdb.movie_directors.director_id
   )
   GROUP BY id
)

SELECT *
FROM actor_summary
-- этот фильтр применяется только при инкрементальном запуске
WHERE id > (SELECT max(id) FROM imdb_dbt.actor_summary) OR updated_at > (SELECT max(updated_at) FROM imdb_dbt.actor_summary)
```

В этом запуске в таблицу `imdb_dbt.actor_summary` напрямую добавляются только новые строки, без создания таблицы.

<div id="deleteinsert-mode-experimental">
  ### Режим удаления и вставки (экспериментальный)
</div>

Изначально в ClickHouse была лишь ограниченная поддержка обновлений и удалений в виде асинхронных [Мутаций](/docs/ru/reference/statements/alter/index). Они могут быть чрезвычайно затратными по I/O, поэтому их обычно следует избегать.

В ClickHouse 22.8 появились [легковесные удаления](/docs/ru/reference/statements/delete), а в ClickHouse 25.7 — [легковесные обновления](/docs/ru/reference/statements/update). С появлением этих возможностей изменения, вносимые отдельными запросами на обновление, даже при асинхронной материализации становятся мгновенно видимыми для пользователя.

Этот режим можно настроить для модели с помощью параметра `incremental_strategy`, например:

```sql theme={null}
{{ config(order_by='(updated_at, id, name)', engine='MergeTree()', materialized='incremental', unique_key='id', incremental_strategy='delete+insert') }}
```

Эта стратегия работает напрямую с таблицей целевой модели, поэтому, если во время выполнения возникнет проблема, данные в инкрементальной модели, скорее всего, окажутся в некорректном состоянии — атомарного обновления здесь нет.

Вкратце этот подход выглядит так:

1. Адаптер создаёт временную таблицу `actor_sumary__dbt_tmp`. Изменённые строки направляются в эту таблицу.
2. Для текущей таблицы `actor_summary` выполняется `DELETE`. Строки удаляются по id из `actor_sumary__dbt_tmp`
3. Строки из `actor_sumary__dbt_tmp` вставляются в `actor_summary` с помощью `INSERT INTO actor_summary SELECT * FROM actor_sumary__dbt_tmp`.

Ниже показан этот процесс:

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_06.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=1544005a262e6b9249b99fc38e0138e8" size="lg" alt="инкрементальное легковесное удаление" width="1345" height="528" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_06.webp" />

<div id="insert_overwrite-mode-experimental">
  ### Режим insert\_overwrite (экспериментальный)
</div>

Включает следующие шаги:

1. Создать staging-таблицу (временную таблицу) с той же структурой, что и отношение инкрементальной модели: `CREATE TABLE {staging} AS {target}`.
2. Выполнить вставку в staging-таблицу только новых записей (полученных с помощью SELECT).
3. Заменить в целевой таблице только новые партиции (присутствующие в staging-таблице).

<br />

У этого подхода есть следующие преимущества:

* Он быстрее стратегии по умолчанию, поскольку не копирует всю таблицу.
* Он безопаснее других стратегий, поскольку не изменяет исходную таблицу, пока операция INSERT не завершится успешно: в случае сбоя на промежуточном этапе исходная таблица не изменяется.
* Он реализует рекомендуемую в дата-инжиниринге практику «неизменяемости партиций», что упрощает инкрементальную и параллельную обработку данных, откаты и т. д.

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/etl-tools/dbt/dbt_07.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=0486243c561a2ad6c335baec32014a65" size="lg" alt="инкрементальный insert overwrite" width="7084" height="2327" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_07.webp" />

<div id="creating-a-snapshot">
  ## Создание снимка
</div>

Снимки dbt позволяют сохранять историю изменений изменяемой модели с течением времени. Это, в свою очередь, позволяет выполнять запросы к моделям на определённый момент времени, чтобы аналитики могли «вернуться назад во времени» и посмотреть на предыдущее состояние модели. Это достигается с помощью [медленно изменяющихся измерений типа 2](https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row), где столбцы с датами начала и окончания фиксируют, в какой период строка была актуальной. Эта функциональность поддерживается адаптером ClickHouse и показана ниже.

В этом примере предполагается, что вы уже выполнили шаг [Создание инкрементной табличной модели](#creating-an-incremental-materialization). Убедитесь, что в вашем actor\_summary.sql не задано inserts\_only=True. Файл models/actor\_summary.sql должен выглядеть так:

```sql theme={null}
   {{ config(order_by='(updated_at, id, name)', engine='MergeTree()', materialized='incremental', unique_key='id') }}

   with actor_summary as (
       SELECT id,
           any(actor_name) as name,
           uniqExact(movie_id)    as num_movies,
           avg(rank)                as avg_rank,
           uniqExact(genre)         as genres,
           uniqExact(director_name) as directors,
           max(created_at) as updated_at
       FROM (
           SELECT {{ source('imdb', 'actors') }}.id as id,
               concat({{ source('imdb', 'actors') }}.first_name, ' ', {{ source('imdb', 'actors') }}.last_name) as actor_name,
               {{ source('imdb', 'movies') }}.id as movie_id,
               {{ source('imdb', 'movies') }}.rank as rank,
               genre,
               concat({{ source('imdb', 'directors') }}.first_name, ' ', {{ source('imdb', 'directors') }}.last_name) as director_name,
               created_at
       FROM {{ source('imdb', 'actors') }}
           JOIN {{ source('imdb', 'roles') }} ON {{ source('imdb', 'roles') }}.actor_id = {{ source('imdb', 'actors') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'movies') }} ON {{ source('imdb', 'movies') }}.id = {{ source('imdb', 'roles') }}.movie_id
           LEFT OUTER JOIN {{ source('imdb', 'genres') }} ON {{ source('imdb', 'genres') }}.movie_id = {{ source('imdb', 'movies') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'movie_directors') }} ON {{ source('imdb', 'movie_directors') }}.movie_id = {{ source('imdb', 'movies') }}.id
           LEFT OUTER JOIN {{ source('imdb', 'directors') }} ON {{ source('imdb', 'directors') }}.id = {{ source('imdb', 'movie_directors') }}.director_id
       )
       GROUP BY id
   )
   select *
   from actor_summary

   {% if is_incremental() %}

   -- этот фильтр будет применяться только при инкрементальном запуске
   where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}})

   {% endif %}
```

1. Создайте файл `actor_summary` в каталоге snapshots.

   ```bash theme={null}
    touch snapshots/actor_summary.sql
   ```

2. Обновите содержимое файла actor\_summary.sql следующим образом:
   ```sql theme={null}
   {% snapshot actor_summary_snapshot %}

   {{
   config(
   target_schema='snapshots',
   unique_key='id',
   strategy='timestamp',
   updated_at='updated_at',
   )
   }}

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

   {% endsnapshot %}
   ```

Несколько замечаний по этому содержимому:

* Запрос `select` определяет результаты, снимки которых вы хотите сохранять с течением времени. Функция ref используется, чтобы сослаться на ранее созданную модель actor\_summary.
* Нам нужен столбец с временной меткой, чтобы отмечать изменения в записях. Здесь можно использовать наш столбец updated\_at (см. [Создание инкрементной модели таблицы](#creating-an-incremental-materialization)). Параметр strategy указывает, что для отслеживания обновлений мы используем временную метку, а параметр updated\_at задает, какой столбец использовать. Если этого столбца нет в вашей модели, можно вместо этого использовать [стратегию check](https://docs.getdbt.com/docs/building-a-dbt-project/snapshots#check-strategy). Это существенно менее эффективно и требует указать список столбцов для сравнения. dbt сравнивает текущие и исторические значения этих столбцов, фиксируя любые изменения (или ничего не делает, если значения совпадают).

3. Выполните команду `dbt snapshot`.

   ```response theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt snapshot
   13:26:23  Running with dbt=1.1.0
   13:26:23  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
   13:26:23
   13:26:25  Concurrency: 1 threads (target='dev')
   13:26:25
   13:26:25  1 of 1 START snapshot snapshots.actor_summary_snapshot...................... [RUN]
   13:26:25  1 of 1 OK snapshotted snapshots.actor_summary_snapshot...................... [OK in 0.79s]
   13:26:25
   13:26:25  Finished running 1 snapshot in 2.11s.
   13:26:25
   13:26:25  Completed successfully
   13:26:25
   13:26:25  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

Обратите внимание, что в базе данных snapshots была создана таблица actor\_summary\_snapshot (это задаётся параметром target\_schema).

4. Выбрав эти данные, вы увидите, что dbt добавил столбцы dbt\_valid\_from и dbt\_valid\_to. У последнего значения равны null. При последующих запусках это обновится.

   ```sql theme={null}
   SELECT id, name, num_movies, dbt_valid_from, dbt_valid_to FROM snapshots.actor_summary_snapshot ORDER BY num_movies DESC LIMIT 5;
   ```

   ```response theme={null}
   +------+----------+------------+----------+-------------------+------------+
   |id    |first_name|last_name   |num_movies|dbt_valid_from     |dbt_valid_to|
   +------+----------+------------+----------+-------------------+------------+
   |845467|Danny     |DeBito      |920       |2022-05-25 19:33:32|NULL        |
   |845466|Clicky    |McClickHouse|910       |2022-05-25 19:32:34|NULL        |
   |45332 |Mel       |Blanc       |909       |2022-05-25 19:31:47|NULL        |
   |621468|Bess      |Flowers     |672       |2022-05-25 19:31:47|NULL        |
   |283127|Tom       |London      |549       |2022-05-25 19:31:47|NULL        |
   +------+----------+------------+----------+-------------------+------------+
   ```

5. Пусть наш любимый актёр Clicky McClickHouse снимется ещё в 10 фильмах.

   ```sql theme={null}
   INSERT INTO imdb.roles
   SELECT now() as created_at, 845466 as actor_id, rand(number) % 412320 as movie_id, 'Himself' as role
   FROM system.numbers
   LIMIT 10;
   ```

6. Снова выполните команду dbt run из каталога `imdb`. Это обновит инкрементную модель. Когда процесс завершится, выполните dbt snapshot, чтобы зафиксировать изменения.

   ```response theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt run
   13:46:14  Running with dbt=1.1.0
   13:46:14  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
   13:46:14
   13:46:15  Concurrency: 1 threads (target='dev')
   13:46:15
   13:46:15  1 of 1 START incremental model imdb_dbt.actor_summary....................... [RUN]
   13:46:18  1 of 1 OK created incremental model imdb_dbt.actor_summary.................. [OK in 2.76s]
   13:46:18
   13:46:18  Finished running 1 incremental model in 3.73s.
   13:46:18
   13:46:18  Completed successfully
   13:46:18
   13:46:18  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1

   clickhouse-user@clickhouse:~/imdb$ dbt snapshot
   13:46:26  Running with dbt=1.1.0
   13:46:26  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
   13:46:26
   13:46:27  Concurrency: 1 threads (target='dev')
   13:46:27
   13:46:27  1 of 1 START snapshot snapshots.actor_summary_snapshot...................... [RUN]
   13:46:31  1 of 1 OK snapshotted snapshots.actor_summary_snapshot...................... [OK in 4.05s]
   13:46:31
   13:46:31  Finished running 1 snapshot in 5.02s.
   13:46:31
   13:46:31  Completed successfully
   13:46:31
   13:46:31  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

7. Если теперь выполнить запрос к нашему снимку, обратите внимание: у нас есть 2 строки для Clicky McClickHouse. В нашей предыдущей записи теперь заполнено значение dbt\_valid\_to. Новое значение записано с тем же значением в столбце dbt\_valid\_from, а значение dbt\_valid\_to равно null. Если бы у нас были новые строки, они также были бы добавлены в снимок.

   ```sql theme={null}
   SELECT id, name, num_movies, dbt_valid_from, dbt_valid_to FROM snapshots.actor_summary_snapshot ORDER BY num_movies DESC LIMIT 5;
   ```

   ```response theme={null}
   +------+----------+------------+----------+-------------------+-------------------+
   |id    |first_name|last_name   |num_movies|dbt_valid_from     |dbt_valid_to       |
   +------+----------+------------+----------+-------------------+-------------------+
   |845467|Danny     |DeBito      |920       |2022-05-25 19:33:32|NULL               |
   |845466|Clicky    |McClickHouse|920       |2022-05-25 19:34:37|NULL               |
   |845466|Clicky    |McClickHouse|910       |2022-05-25 19:32:34|2022-05-25 19:34:37|
   |45332 |Mel       |Blanc       |909       |2022-05-25 19:31:47|NULL               |
   |621468|Bess      |Flowers     |672       |2022-05-25 19:31:47|NULL               |
   +------+----------+------------+----------+-------------------+-------------------+
   ```

Подробные сведения о снимках dbt см. [здесь](https://docs.getdbt.com/docs/building-a-dbt-project/snapshots).

<div id="using-seeds">
  ## Использование seed-файлов
</div>

dbt предоставляет возможность загружать данные из CSV-файлов. Эта возможность не подходит для загрузки больших выгрузок из базы данных и в большей степени рассчитана на небольшие файлы, обычно используемые для кодовых таблиц и [словарей](/docs/ru/concepts/features/dictionaries/index), например для сопоставления кодов стран с названиями стран. В качестве простого примера мы сгенерируем, а затем загрузим список кодов жанров с помощью механизма seed.

1. Мы генерируем список кодов жанров из имеющегося набора данных. В каталоге dbt используйте `clickhouse-client`, чтобы создать файл `seeds/genre_codes.csv`:

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ clickhouse-client --password <password> --query
   "SELECT genre, ucase(substring(genre, 1, 3)) as code FROM imdb.genres GROUP BY genre
   LIMIT 100 FORMAT CSVWithNames" > seeds/genre_codes.csv
   ```

2. Выполните команду `dbt seed`. Это создаст новую таблицу `genre_codes` в нашей базе данных `imdb_dbt` (как задано в конфигурации схемы) со строками из нашего CSV-файла.

   ```bash theme={null}
   clickhouse-user@clickhouse:~/imdb$ dbt seed
   17:03:23  Running with dbt=1.1.0
   17:03:23  Found 1 model, 0 tests, 1 snapshot, 0 analyses, 181 macros, 0 operations, 1 seed file, 6 sources, 0 exposures, 0 metrics
   17:03:23
   17:03:24  Concurrency: 1 threads (target='dev')
   17:03:24
   17:03:24  1 of 1 START seed file imdb_dbt.genre_codes..................................... [RUN]
   17:03:24  1 of 1 OK loaded seed file imdb_dbt.genre_codes................................. [INSERT 21 in 0.65s]
   17:03:24
   17:03:24  Finished running 1 seed in 1.62s.
   17:03:24
   17:03:24  Completed successfully
   17:03:24
   17:03:24  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
   ```

3. Подтвердите, что данные были загружены:

   ```sql theme={null}
   SELECT * FROM imdb_dbt.genre_codes LIMIT 10;
   ```

   ```response theme={null}
   +-------+----+
   |genre  |code|
   +-------+----+
   |Drama  |DRA |
   |Romance|ROM |
   |Short  |SHO |
   |Mystery|MYS |
   |Adult  |ADU |
   |Family |FAM |

   |Action |ACT |
   |Sci-Fi |SCI |
   |Horror |HOR |
   |War    |WAR |
   +-------+----+=
   ```

<div id="further-information">
  ## Дополнительная информация
</div>

В предыдущих руководствах рассмотрены лишь базовые возможности dbt. Рекомендуем ознакомиться с отличной [документацией dbt](https://docs.getdbt.com/docs/introduction).
