> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouse에서 dbt를 사용하기 위한 가이드

# 가이드

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 어댑터를 설정하는 가이드와, 공개적으로 제공되는 IMDB 데이터셋을 사용해 ClickHouse에서 dbt를 사용하는 예시를 제공합니다. 이 예시에서는 다음 단계를 다룹니다:

1. dbt 프로젝트를 생성하고 ClickHouse 어댑터를 설정합니다.
2. 모델을 정의합니다.
3. 모델을 업데이트합니다.
4. 증분 모델을 생성합니다.
5. 스냅샷 모델을 생성합니다.
6. materialized view를 사용합니다.

이 가이드는 [문서](/docs/ko/integrations/connectors/data-ingestion/etl-tools/dbt/index), [기능 및 구성](/docs/ko/integrations/connectors/data-ingestion/etl-tools/dbt/features-and-configurations), [materializations 참고](/docs/ko/integrations/connectors/data-ingestion/etl-tools/dbt/materializations)와 함께 사용할 수 있도록 작성되었습니다.

<div id="setup">
  ## 설정
</div>

환경을 준비하려면 [dbt 및 ClickHouse 어댑터 설정](/docs/ko/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>
  테이블 `roles`의 `created_at` 컬럼 기본값은 `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. 프로젝트 폴더로 이동합니다.

   ```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` 파일을 업데이트하여 첫 번째 model인 `actor_summary`를 지정하고, profile을 `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
   ```

   `user`와 `password`는 환경에 맞게 수정해야 합니다. 추가로 사용할 수 있는 설정은 [여기](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>

뷰 머티리얼라이즈를 사용하면 모델은 실행할 때마다 ClickHouse의 `CREATE VIEW AS` 구문을 통해 뷰로 다시 생성됩니다. 이 방식은 데이터를 추가로 저장할 필요는 없지만, 테이블 머티리얼라이즈보다 쿼리 성능이 느립니다.

1. `imdb` 폴더에서 `models/example` 디렉터리를 삭제하십시오:

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

2. `models` 폴더의 `actors` 내에 새 파일을 만드세요. 여기서는 각 파일이 하나의 actor 모델을 나타내도록 생성합니다:

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

3. `models/actors` 폴더에 `schema.yml`과 `actor_summary.sql` 파일을 생성하세요.

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

   파일 `schema.yml`에서 테이블을 정의합니다. 이렇게 정의한 테이블은 이후 매크로에서 사용할 수 있습니다. Edit
   `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`은 실제 model을 정의합니다. config 함수에서는 이 model이 ClickHouse에서 뷰로 구체화되도록 요청한다는 점에 유의하십시오. 테이블은 `schema.yml` 파일에서 `source` 함수를 통해 참조합니다. 예를 들어 `source('imdb', 'movies')`는 `imdb` 데이터베이스의 `movies` 테이블을 가리킵니다. `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
   ```

   최종 actor\_summary에 `updated_at` 컬럼을 포함했다는 점에 유의하십시오. 이는 이후 증분 머티리얼라이제이션에 사용됩니다.

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는 요청한 대로 model을 ClickHouse의 뷰로 생성합니다. 이제 이 뷰에 직접 쿼리할 수 있습니다. 이 뷰는 `imdb_dbt` 데이터베이스에 생성되며, 이는 `clickhouse_imdb` profile 아래 `~/.dbt/profiles.yml` 파일의 스키마(schema) 매개변수로 결정됩니다.

   ```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/ko/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는 증분 머티리얼라이즈를 제공합니다. 이를 사용하면 마지막 실행 이후 변경된 레코드를 테이블에 삽입하거나 업데이트할 수 있으므로 이벤트성 데이터에 적합합니다. 내부적으로는 먼저 업데이트된 모든 레코드가 포함된 임시 테이블을 만든 다음, 변경되지 않은 레코드와 업데이트된 레코드를 모두 새 대상 테이블(target table)에 삽입합니다. 따라서 큰 결과 집합에서는 table 모델과 유사한 [제약 사항](/docs/ko/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations)이 있습니다.

큰 집합에서 이러한 제약을 극복하기 위해 어댑터는 'inserts\_only' 모드를 지원합니다. 이 모드에서는 임시 테이블을 만들지 않고 모든 업데이트를 대상 테이블에 삽입합니다(자세한 내용은 아래 참조).

이 예시를 설명하기 위해 배우 "Clicky McClickHouse"를 추가하겠습니다. 그는 무려 910편의 영화에 출연하며, [Mel Blanc](https://en.wikipedia.org/wiki/Mel_Blanc)보다도 더 많은 영화에 출연한 인물이 됩니다.

1. 먼저 model을 incremental 유형으로 변경합니다. 이를 추가하려면 다음이 필요합니다:

   1. **unique\_key** - 어댑터가 행을 고유하게 식별할 수 있도록 unique\_key를 제공해야 합니다. 이 경우 쿼리의 `id` 필드면 충분합니다. 이렇게 하면 구체화된 테이블(Materialized Table)에 중복 행이 생기지 않습니다. 고유성 제약 조건에 관한 자세한 내용은 [여기](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` 테이블의 업데이트와 추가에만 반응합니다. 모든 테이블에 반응하도록 하려면 이 모델을 여러 개의 하위 모델로 나누고, 각 하위 모델에 자체 증분 기준을 설정하는 것이 좋습니다. 이후 이러한 모델은 서로 참조하고 연결할 수 있습니다. 모델 간 교차 참조에 대한 자세한 내용은 [here](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. 이제 증분 업데이트를 보여주기 위해 모델에 데이터를 추가하겠습니다. `actors` 테이블에 배우 "Clicky McClickHouse"를 추가하세요:

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

위와 같은 증분 업데이트를 위해 실행된 SQL 문은 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,`를 생성합니다. 그런 다음 이전 테이블의 행을 이전 테이블에서 새 테이블로 스트리밍하면서, 행 ID가 임시 테이블에 존재하지 않는지 확인합니다. 이렇게 하면 업데이트와 중복을 효과적으로 처리할 수 있습니다.
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/ko/integrations/connectors/data-ingestion/etl-tools/dbt/index#limitations)을 참조하십시오.

<div id="append-strategy-inserts-only-mode">
  ### Append Strategy (삽입 전용 모드)
</div>

증분 모델에서 대규모 데이터셋의 한계를 보완하기 위해 어댑터는 dbt 구성 매개변수 `incremental_strategy`를 사용합니다. 이 매개변수는 `append` 값으로 설정할 수 있습니다. 이렇게 설정하면 업데이트된 행이 대상 테이블(즉, `imdb_dbt.actor_summary`)에 직접 삽입되고, 임시 테이블은 생성되지 않습니다.
참고: Append only 모드를 사용하려면 데이터가 불변이어야 하거나 중복이 허용되어야 합니다. 변경된 행을 지원하는 증분 테이블 모델이 필요하다면 이 모드를 사용하지 마십시오.

이 모드를 설명하기 위해 새 배우를 한 명 더 추가한 다음, `incremental_strategy='append'`로 dbt run을 다시 실행하겠습니다.

1. actor\_summary.sql에서 append only 모드를 구성합니다:

   ```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을 실행하고 actor-summary 테이블에 Danny가 추가되었는지 확인합니다.

   ```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/ko/reference/statements/alter/index) 형태로만 업데이트와 삭제를 제한적으로 지원해 왔습니다. 이는 IO 사용량이 매우 클 수 있으므로 일반적으로 피하는 것이 좋습니다.

ClickHouse 22.8에서는 [경량한 삭제](/docs/ko/reference/statements/delete)가 도입되었고, ClickHouse 25.7에서는 [경량 업데이트](/docs/ko/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') }}
```

이 전략은 대상 모델의 테이블에서 직접 수행되므로 작업 중 문제가 발생하면 증분 모델의 데이터가 유효하지 않은 상태가 될 가능성이 높습니다. atomic update가 없기 때문입니다.

요약하면, 이 접근 방식은 다음과 같습니다:

1. 어댑터가 임시 테이블 `actor_sumary__dbt_tmp`를 생성합니다. 변경된 행은 이 테이블로 스트리밍됩니다.
2. 현재 `actor_summary` 테이블에 `DELETE`를 실행합니다. `actor_sumary__dbt_tmp`의 id를 기준으로 행을 삭제합니다.
3. `actor_sumary__dbt_tmp`의 행을 `INSERT INTO actor_summary SELECT * FROM actor_sumary__dbt_tmp`를 사용해 `actor_summary`에 삽입합니다.

이 과정은 아래에 나와 있습니다:

<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. 증분 모델 릴레이션과 동일한 구조의 스테이징(임시) 테이블을 생성합니다: `CREATE TABLE {staging} AS {target}`.
2. 새 레코드만(SELECT로 생성된 레코드) 스테이징 테이블에 삽입합니다.
3. 새 파티션만(스테이징 테이블에 있는 파티션) 대상 테이블로 교체합니다.

<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 incremental" width="7084" height="2327" data-path="images/integrations/data-ingestion/etl-tools/dbt/dbt_07.webp" />

<div id="creating-a-snapshot">
  ## 스냅샷 생성
</div>

dbt 스냅샷은 시간에 따라 변경되는 모델의 변경 이력을 기록할 수 있게 해줍니다. 이를 통해 모델에 대해 특정 시점 기준 쿼리를 수행할 수 있으며, 분석가는 모델의 이전 상태를 "과거 시점으로 되돌아가" 확인할 수 있습니다. 이는 행이 유효한 기간을 기록하는 시작일 및 종료일 컬럼을 사용하는 [type-2 Slowly Changing Dimensions](https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row) 방식으로 구현됩니다. 이 기능은 ClickHouse 어댑터에서 지원되며, 아래에서 예시를 보여줍니다.

이 예시는 [Creating an Incremental Table Model](#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. `snapshots` 디렉터리에 `actor_summary` 파일을 생성합니다.

   ```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 모델을 참조하는 데 사용됩니다.
* 레코드 변경을 나타내려면 `timestamp` 컬럼이 필요합니다. 여기서는 updated\_at 컬럼([증분 테이블 모델 생성](#creating-an-incremental-materialization) 참고)을 사용할 수 있습니다. `strategy` 매개변수는 업데이트를 표시하는 데 `timestamp`를 사용함을 의미하며, `updated&#95;at` 매개변수는 사용할 컬럼을 지정합니다. 모델에 이 컬럼이 없다면 대신 [check strategy](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
   ```

`actor_summary_snapshot` 테이블이 snapshots DB에 생성된 것을 확인할 수 있습니다. 이는 `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. `imdb` 디렉터리에서 `dbt run` 명령을 다시 실행하세요. 그러면 증분 모델이 업데이트됩니다. 이 작업이 완료되면 변경 사항을 기록하기 위해 `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. 이제 스냅샷을 쿼리하면 Clicky McClickHouse에 대한 행이 2개 있는 것을 확인할 수 있습니다. 이전 항목에는 이제 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 파일에서 데이터를 로드하는 기능을 제공합니다. 이 기능은 데이터베이스의 대규모 내보내기 데이터를 로드하는 데는 적합하지 않으며, 일반적으로 코드 테이블과 [dictionaries](/docs/ko/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` 명령을 실행합니다. 그러면 데이터베이스 `imdb_dbt`에 새 테이블 `genre_codes`가 생성되고(이는 schema 구성에 정의되어 있음), 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)를 참조하십시오.
