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

> CREATE VIEW에 대한 문서

# CREATE VIEW

export const DeprecatedBadge = () => {
  return <div className="deprecatedBadge">
            <div className="deprecatedIcon">
            <svg width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M13 0H1C0.734784 0 0.48043 0.105357 0.292893 0.292893C0.105357 0.48043 0 0.734784 0 1V2.5C0 2.76522 0.105357 3.01957 0.292893 3.20711C0.48043 3.39464 0.734784 3.5 1 3.5V9C1 9.26522 1.10536 9.51957 1.29289 9.70711C1.48043 9.89464 1.73478 10 2 10H12C12.2652 10 12.5196 9.89464 12.7071 9.70711C12.8946 9.51957 13 9.26522 13 9V3.5C13.2652 3.5 13.5196 3.39464 13.7071 3.20711C13.8946 3.01957 14 2.76522 14 2.5V1C14 0.734784 13.8946 0.48043 13.7071 0.292893C13.5196 0.105357 13.2652 0 13 0ZM12 9H2V3.5H12V9ZM13 2.5H1V1H13V2.5ZM5 5.5C5 5.36739 5.05268 5.24021 5.14645 5.14645C5.24021 5.05268 5.36739 5 5.5 5H8.5C8.63261 5 8.75979 5.05268 8.85355 5.14645C8.94732 5.24021 9 5.36739 9 5.5C9 5.63261 8.94732 5.75979 8.85355 5.85355C8.75979 5.94732 8.63261 6 8.5 6H5.5C5.36739 6 5.24021 5.94732 5.14645 5.85355C5.05268 5.75979 5 5.63261 5 5.5Z" fill="currentColor" />
            </svg>
        </div>
            사용 중단 예정 기능
        </div>;
};

새로운 뷰를 생성합니다. 뷰는 [일반](#normal-view), [materialized](#materialized-view), [갱신 가능 구체화 뷰](#refreshable-materialized-view) 유형으로 만들 수 있습니다.

## 일반 뷰

구문:

```sql theme={null}
CREATE [OR REPLACE] VIEW [IF NOT EXISTS] [db.]table_name [(alias1 [, alias2 ...])] [ON CLUSTER cluster_name]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER | NONE }]
AS SELECT ...
[COMMENT 'comment']
```

일반 뷰는 데이터를 전혀 저장하지 않습니다. 대신 접근할 때마다 다른 테이블에서 데이터를 읽어 옵니다. 즉, 일반 뷰는 저장된 쿼리에 불과합니다. 뷰에서 읽을 때는 이 저장된 쿼리가 [FROM](/ko/reference/statements/select/from) 절의 서브쿼리로 사용됩니다.

예시로, 뷰를 생성했다고 가정하겠습니다:

```sql theme={null}
CREATE VIEW view AS SELECT ...
```

그리고 쿼리를 작성했습니다:

```sql theme={null}
SELECT a, b, c FROM view
```

이 쿼리는 다음 서브쿼리를 사용하는 것과 완전히 동일합니다:

```sql theme={null}
SELECT a, b, c FROM (SELECT ...)
```

## 매개변수화된 뷰(Parameterized View)

매개변수화된 뷰는 일반 뷰와 유사하지만, 즉시 해석되지 않는 매개변수를 포함해 생성할 수 있습니다.
이러한 뷰는 테이블 함수와 함께 사용할 수 있으며, 이 경우 함수 이름에는 뷰 이름을 지정하고 인수에는 매개변수 값을 지정합니다.

```sql theme={null}
CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...
```

위 구문은 아래와 같이 매개변수를 치환하여 테이블 함수로 사용할 수 있는 테이블용 뷰를 생성합니다.

```sql theme={null}
SELECT * FROM view(column1=value1, column2=value2 ...)
```

매개변수화된 뷰는 매개변수 값에 따라 달라지므로, 매개변수가 제공되지 않으면 스키마가 없습니다.
즉, `system.columns` 테이블에는 매개변수화된 뷰에 대한 정보가 없습니다.
또한 매개변수가 제공된 경우에만 `DESCRIBE` 쿼리를 사용할 수 있습니다.

```sql theme={null}
DESCRIBE view(column1=value1, column2=value2 ...)
```

## Materialized View

```sql theme={null}
CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
```

```sql theme={null}
CREATE OR REPLACE MATERIALIZED VIEW [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
```

`OR REPLACE`와 `IF NOT EXISTS`는 서로 배타적이므로 함께 사용할 수 없습니다. 둘을 함께 사용하면 구문 오류가 발생합니다.

### CREATE OR REPLACE MATERIALIZED VIEW

`CREATE OR REPLACE MATERIALIZED VIEW`는 기존 materialized view와 해당 내부 저장 테이블(있는 경우)을 원자적으로 대체합니다. 이 작업을 수행하려면 `Atomic` 또는 `Replicated` 데이터베이스 엔진이 필요합니다.

```sql theme={null}
CREATE OR REPLACE MATERIALIZED VIEW [db.]name [ON CLUSTER cluster]
[TO [db.]target_table]
[ENGINE = engine]
[POPULATE]
[REFRESH ...]
AS SELECT ...
```

주요 동작:

* **`TO` 절 없이**: 기존 내부 테이블이 삭제되고 새 테이블이 생성됩니다. `POPULATE`를 지정하지 않으면 내부 테이블의 기존 데이터는 손실됩니다.
* **`TO` 절 사용 시**: 뷰 정의만 대체되며 대상 테이블과 그 데이터에는 영향이 없습니다.
* `REFRESH`, `ON CLUSTER`, 그리고 모든 엔진 옵션과 호환됩니다. `POPULATE`는 `Atomic` 데이터베이스에서만 지원되며, `Replicated` 데이터베이스에서는 허용되지 않습니다(아래 `POPULATE` 참고 사항 참조).
* `CREATE VIEW` 및 `DROP VIEW` 권한이 필요합니다.

<Note>
  `CREATE OR REPLACE MATERIALIZED VIEW`는 `Atomic` 또는 `Replicated` 데이터베이스 엔진에서만 지원됩니다. `Ordinary` 데이터베이스 엔진에서는 지원되지 않습니다.
</Note>

**예시:**

```sql theme={null}
-- Create a materialized view with an inner table
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, sum(y) AS total FROM src GROUP BY x;

-- Replace with a new definition (old inner table data is lost)
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, count() AS cnt FROM src GROUP BY x;

-- Replace with POPULATE to backfill from existing source data
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    POPULATE
    AS SELECT x FROM src;

-- Replace an inner-table MV with a TO-table MV (target data is preserved)
CREATE OR REPLACE MATERIALIZED VIEW mv TO target
    AS SELECT x FROM src;
```

<Tip>
  [Materialized views](/ko/concepts/features/materialized-views/cascading-materialized-views)를 사용하는 단계별 가이드는 여기에서 확인할 수 있습니다.
</Tip>

materialized view는 해당 [SELECT](/ko/reference/statements/select/index) 쿼리로 변환된 데이터를 저장합니다.

`TO [db].[table]` 없이 materialized view를 생성할 때는 데이터를 저장할 테이블 엔진인 `ENGINE`을 지정해야 합니다.

`TO [db].[table]`과 함께 materialized view를 생성할 때는 `POPULATE`를 사용하여 기존 소스 데이터로 대상 테이블을 백필할 수도 있습니다(대상 테이블에 이미 데이터가 있을 수 있으며, 이 경우 백필된 행은 추가됩니다). `POPULATE`는 `REFRESH`와 함께 사용할 수 없습니다. [갱신 가능 구체화 뷰](#refreshable-materialized-view)는 첫 번째 갱신으로 채워지므로, `POPULATE`를 사용하면 초기 데이터가 두 번 로드됩니다(대신 `EMPTY`를 사용하여 첫 번째 갱신을 건너뛰십시오).

materialized view는 다음과 같이 동작합니다. `SELECT`에 지정된 테이블에 데이터를 삽입하면, 삽입된 데이터의 일부가 이 `SELECT` 쿼리로 변환되고 그 결과가 뷰에 삽입됩니다.

<Note>
  ClickHouse의 materialized view는 대상 테이블에 삽입할 때 컬럼 순서가 아니라 **컬럼 이름**을 사용합니다. 일부 컬럼 이름이 `SELECT` 쿼리 결과에 없으면, 해당 컬럼이 [널 허용](/ko/reference/data-types/nullable)이 아니더라도 ClickHouse는 기본값을 사용합니다. materialized view를 사용할 때는 모든 컬럼에 별칭을 추가하는 것이 안전합니다.

  ClickHouse의 materialized view는 삽입 트리거에 더 가깝게 구현됩니다. 뷰 쿼리에 집계가 포함된 경우, 집계는 새로 삽입된 데이터 배치에만 적용됩니다. 원본 테이블의 기존 데이터에 대한 변경(예: 업데이트, 삭제, 파티션 삭제 등)은 materialized view에 반영되지 않습니다.

  ClickHouse의 materialized view는 오류 발생 시 결정적 동작을 보장하지 않습니다. 즉, 이미 기록된 블록은 대상 테이블에 유지되지만, 오류 이후의 모든 블록은 유지되지 않습니다.

  기본적으로 뷰 중 하나에 푸시하는 과정에서 예외가 발생하면 `INSERT` 쿼리가 실패합니다. 그 시점까지 블록이 이미 원본 테이블에 도달했는지는 보장되지 않습니다. 이는 뷰 오류가 아니라 삽입 파이프라인의 타이밍에 따라 달라집니다. 원본 테이블과 모든 종속 뷰에 대해 exactly-once 전달을 보장하려면, 삽입 중복 제거(`insert_deduplicate`, `deduplicate_blocks_in_dependent_materialized_views`)를 사용하여 실패한 `INSERT`을 다시 시도하십시오.

  `INSERT` 쿼리에서 `materialized_views_ignore_errors=true`를 설정하면 오류 보고 방식만 바뀝니다. 각 뷰 오류는 경고로 기록되고 `INSERT` 쿼리는 성공합니다. 오류가 발생한 뷰의 대상에 대한 전달은 부분적으로만 이루어집니다. 예외가 발생하기 전에 처리된 블록은 유지되고, 오류가 발생한 블록과 그 이후의 모든 블록은 해당 뷰에서 삭제됩니다. 해당 대상의 다운스트림 뷰는 실제로 도착한 블록만 보게 되므로, 이들에 대한 전달도 부분적으로만 이루어집니다. 오류를 발생시키지 않은 형제 뷰(및 그 다운스트림 체인)에는 전체가 기록되며, 원본 테이블도 평소처럼 기록됩니다. `INSERT`가 성공으로 보고되므로 클라이언트는 실패 신호를 받지 못하고 자동 재시도도 트리거되지 않습니다. 따라서 이 설정은 원본 테이블 쓰기가 뷰 측 문제로 차단되면 안 되는 경우(예: `system.*_log` 테이블)에만 사용하십시오.

  `materialized_views_ignore_errors`는 `system.*_log` 테이블에서 기본적으로 `true`입니다.
</Note>

`POPULATE`를 지정하면 뷰를 생성할 때 기존 원본 테이블 데이터가 뷰에 삽입됩니다. 그렇지 않으면 뷰에는 뷰를 생성한 후 원본 테이블에 삽입된 데이터만 포함됩니다.

일반 `CREATE MATERIALIZED VIEW`에서 `POPULATE`는 기본적으로 **원자적**입니다(설정 `materialized_views_populate_atomically = 1`). 원본 테이블에 짧은 배타적 잠금을 설정한 상태에서 뷰는 원본 테이블의 새 삽입을 구독하고 기존 데이터의 스냅샷을 함께 가져오므로, 채우기와 동시에 삽입되는 모든 행은 누락되거나 중복되지 않고 뷰에 **정확히 한 번** 전달됩니다. 이후 (오래 실행될 수 있는) 채우기는 잠금을 유지하지 않고 고정된 스냅샷을 읽습니다.

이는 로컬 삽입 경로의 원자성입니다. 배타적 잠금은 **동일한 서버에서** 이 원본 테이블의 스토리지 잠금을 획득하는 삽입과만 직렬화되므로, exactly-once 보장은 이 서버를 통해 도착하는 삽입에 적용됩니다. 이는 클러스터 전체 보장이 아닙니다. 채우기와 동시에 `ReplicatedMergeTree` 원본의 다른 레플리카에 삽입되거나 분산 쓰기 경로(예: `Distributed` 테이블에 삽입하거나 `ON CLUSTER`를 통해 삽입)를 통해 삽입된 행은 이 범위에 포함되지 않으므로 여전히 누락되거나 중복될 수 있습니다.

채우기가 실패하면(예: 사용 중인 원본 테이블의 배타적 잠금을 `lock_acquire_timeout` 내에 획득할 수 없거나 실행 중인 뷰의 `SELECT`에서 예외가 발생하는 경우) 방금 생성된 뷰가 삭제되고 `CREATE` 쿼리가 실패합니다. 생성된 항목은 남지 않으므로 간단히 다시 시도할 수 있습니다. `TO [db].[table]` 형식에서는 이 롤백이 뷰만 삭제하며, 기존 대상 테이블은 삭제하지 않습니다. 단, 실패한 채우기가 이미 대상에 삽입한 행은 해당 테이블에 대한 실패한 `INSERT ... SELECT` 이후와 마찬가지로 그대로 남으므로, `CREATE`를 다시 시도하면 해당 행이 다시 삽입됩니다. 백필이 정확해야 한다면 비워진 대상 테이블 또는 새 대상 테이블에 다시 시도하거나 `ReplacingMergeTree`와 같은 중복 제거 엔진을 사용하십시오.

<Note>
  원자성을 보장하려면 원본 테이블이 고정된 특정 시점의 스냅샷을 읽을 수 있어야 합니다. 즉, `MergeTree` 엔진 계열 및 `Memory`가 해당합니다. 그 밖의 모든 원본(뷰, `Distributed`, `Merge`, `Buffer`, `Log` 엔진 계열 또는 `Atomic` 데이터베이스에 속하지 않는 테이블)에서는 데이터 채우기가 레거시 비원자적 동작(서버 로그에 기록됨)으로 대체됩니다. 기존 데이터는 별도의 조정되지 않은 스냅샷으로 읽히므로, 데이터 채우기 중에 삽입된 행이 누락되거나 중복될 수 있습니다. 이 경우 정확한 데이터가 필요하다면 뷰를 생성하고 별도의 `INSERT ... SELECT`를 실행하십시오. `materialized_views_populate_atomically = 0`을 설정하면 모든 원본에 대해 이 레거시 동작이 강제됩니다.

  원자적 데이터 채우기는 일반 `CREATE MATERIALIZED VIEW`에만 적용됩니다. `CREATE OR REPLACE` / `REPLACE MATERIALIZED VIEW ... POPULATE`는 항상 레거시 비원자적 데이터 채우기를 사용합니다.

  `POPULATE`는 `Replicated` 데이터베이스에서 지원되지 않으며(`database_replicated_allow_heavy_create`를 사용하여 재정의 가능), ClickHouse Cloud에서도 지원되지 않습니다. 이 재정의를 통해 활성화하면 데이터 채우기는 항상 레거시 비원자적 방식으로 수행됩니다. 실패한 데이터 채우기는 모든 레플리카에서 일관되게 롤백할 수 없기 때문입니다.
</Note>

`SELECT` 쿼리에는 `DISTINCT`, `GROUP BY`, `ORDER BY`, `LIMIT`를 포함할 수 있습니다. 해당 변환은 삽입된 데이터의 각 블록에서 서로 독립적으로 수행된다는 점에 유의하십시오. 예를 들어 `GROUP BY`가 설정된 경우 데이터는 삽입 중에 집계되지만, 이는 삽입된 데이터의 단일 패킷 내에서만 수행됩니다. 이후에는 데이터가 추가로 집계되지 않습니다. 예외적으로 `SummingMergeTree`처럼 자체적으로 데이터 집계를 수행하는 `ENGINE`을 사용할 때는 다릅니다.

materialized view가 `TO [db.]name` 구문을 사용하는 경우, 뷰를 `DETACH`하고 대상 테이블에 `ALTER`를 실행한 다음, 앞서 분리한(`DETACH`) 뷰를 `ATTACH`할 수 있습니다.

뷰는 일반 테이블과 동일하게 표시됩니다. 예를 들어 `SHOW TABLES` 쿼리 결과에 나열됩니다.

뷰를 삭제하려면 [DROP VIEW](/ko/reference/statements/drop#drop-view)를 사용하십시오. `DROP TABLE`도 VIEW에 사용할 수 있습니다.

## SQL 보안

`DEFINER` 및 `SQL SECURITY`를 사용하면 뷰의 기반 쿼리를 실행할 때 어떤 ClickHouse 사용자를 사용할지 지정할 수 있습니다.
`SQL SECURITY`에는 `DEFINER`, `INVOKER`, `NONE`의 세 가지 유효한 값이 있습니다. `DEFINER` 절에는 기존 사용자 또는 `CURRENT_USER`를 지정할 수 있습니다.

다음 표는 뷰에서 `SELECT`할 때 어떤 사용자에게 어떤 권한이 필요한지 설명합니다.
SQL 보안 옵션과 관계없이, 어떤 경우에도 뷰를 읽으려면 여전히 `GRANT SELECT ON <view>` 권한이 필요합니다.

| SQL security option | View | Materialized View |
| - | - | - |
| `DEFINER alice` | `alice`는 뷰의 소스 테이블에 대한 `SELECT` 권한이 있어야 합니다. | `alice`는 뷰의 소스 테이블에 대한 `SELECT` 권한과 뷰의 대상 테이블에 대한 `INSERT` 권한이 있어야 합니다. |
| `INVOKER` | 사용자는 뷰의 소스 테이블에 대한 `SELECT` 권한이 있어야 합니다. | materialized view에는 `SQL SECURITY INVOKER`를 지정할 수 없습니다. |
| `NONE` | - | - |

<Note>
  `SQL SECURITY NONE`은 더 이상 권장되지 않는 옵션입니다. `SQL SECURITY NONE`으로 뷰를 생성할 수 있는 권한이 있는 사용자는 임의의 쿼리를 실행할 수 있습니다.
  따라서 이 옵션으로 뷰를 생성하려면 `GRANT ALLOW SQL SECURITY NONE TO <user>` 권한이 필요합니다.
</Note>

`DEFINER`/`SQL SECURITY`를 지정하지 않으면 결과는 [`ignore_empty_sql_security_in_create_view_query`](/ko/reference/settings/server-settings/settings/other#ignore_empty_sql_security_in_create_view_query) 서버 설정에 따라 달라집니다.

기본값이 `true`이면 쿼리가 작성된 그대로 저장되고 뷰에는 비어 있는 SQL 보안 유형이 적용됩니다. 일반 뷰는 호출자의 권한으로 실행되며, 명시적으로 대상 테이블을 지정한 materialized view에서는 해당 대상 테이블에 대한 접근 검사가 건너뛰어집니다. 즉, 원본 테이블에 삽입할 때 대상 테이블에 대한 `INSERT` 권한이 필요하지 않으며, 뷰에서 읽을 때도 해당 대상 테이블에 대한 `SELECT` 권한이 필요하지 않습니다.

`false`이면 생성 시 다음 기본값이 뷰 정의에 기록됩니다:

* `SQL SECURITY`: 일반 뷰에는 `INVOKER`([`default_normal_view_sql_security`](/ko/reference/settings/session-settings/default#default_normal_view_sql_security)로 구성 가능), materialized view에는 `DEFINER`([`default_materialized_view_sql_security`](/ko/reference/settings/session-settings/default#default_materialized_view_sql_security)로 구성 가능)
* `DEFINER`: `CURRENT_USER`([`default_view_definer`](/ko/reference/settings/session-settings/default#default_view_definer)로 구성 가능)

갱신 가능 구체화 뷰는 설정과 관계없이 항상 이러한 기본값을 적용받습니다.

뷰는 서버 시작 시 ATTACH되거나 다시 로드될 때 저장된 정의의 SQL 보안 유형을 유지하므로, `DEFINER`/`SQL SECURITY` 없이 저장된 뷰는 비어 있는 SQL 보안 유형을 유지합니다.

기존 뷰의 SQL 보안을 변경하려면 다음을 사용합니다:

```sql theme={null}
ALTER TABLE MODIFY SQL SECURITY { DEFINER | INVOKER | NONE } [DEFINER = { user | CURRENT_USER }]
```

### 예시

```sql theme={null}
CREATE VIEW test_view
DEFINER = alice SQL SECURITY DEFINER
AS SELECT ...
```

```sql theme={null}
CREATE VIEW test_view
SQL SECURITY INVOKER
AS SELECT ...
```

## 라이브 view

<DeprecatedBadge />

이 기능은 더 이상 사용이 권장되지 않으며, 향후 제거될 예정입니다.

편의를 위해 이전 문서는 [여기](https://pastila.nl/?00f32652/fdf07272a7b54bda7e13b919264e449f.md)에서 확인하실 수 있습니다.

## 갱신 가능 구체화 뷰

```sql theme={null}
CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
REFRESH [EVERY|AFTER interval [OFFSET interval]]
[RANDOMIZE FOR interval]
[DEPENDS ON [db.]name [, [db.]name [, ...]]]
[SETTINGS name = value [, name = value [, ...]]]
[APPEND [INCREMENTAL]]
[TO[db.]name] [(columns)] [ENGINE = engine]
[EMPTY]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
```

여기서 `interval`은 단순한 인터벌의 연속입니다:

```sql theme={null}
number SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR
```

`REFRESH` 절에는 `EVERY`, `AFTER`, `DEPENDS ON` 중 적어도 하나를 지정해야 합니다. 이들 없이 단독으로 사용하는 `REFRESH`는 허용되지 않습니다. `EVERY`/`AFTER` 없이 사용하는 `REFRESH DEPENDS ON ...`는 `REFRESH AFTER 0 SECOND DEPENDS ON ...`의 축약형입니다. 자세한 내용은 아래의 [갱신 의존성](#refresh-dependencies)을 참조하십시오.

해당 쿼리를 주기적으로 실행하고 결과를 테이블에 저장합니다.

* `APPEND`를 지정하면 각 갱신 시 기존 행을 삭제하지 않고 테이블에 행을 삽입합니다. 이 삽입은 일반적인 `INSERT INTO ... SELECT` 쿼리와 마찬가지로 원자적이지 않습니다.
* `APPEND INCREMENTAL`을 지정하면 각 갱신 시 이전 갱신 이후 원본 테이블에 커밋된 행에 대해서만 쿼리를 실행하고 결과를 추가합니다.
* 그렇지 않으면 각 갱신 시 테이블의 이전 내용이 원자적으로 대체됩니다.

일반적인 비갱신형 materialized view와의 차이점:

* 삽입 트리거가 없습니다. `SELECT`에 지정된 테이블에 새 데이터가 삽입되어도 갱신 가능 구체화 뷰로 자동 반영되지 *않습니다*. 대신 데이터 삽입은 주기적 또는 수동 갱신 실행 중에만 이루어집니다.
* `SELECT` 쿼리에는 제한이 없습니다. 테이블 함수(예: `url()`), 뷰, UNION, JOIN을 모두 사용할 수 있습니다. 단, `APPEND INCREMENTAL`은 예외입니다. `enable_block_number_column = 1` 및 `enable_block_offset_column = 1`이 설정된 단일 일반 `MergeTree` 원본 테이블이 필요하며, `JOIN`, `UNION`, 서브쿼리, 뷰 및 테이블 함수는 허용되지 않습니다.

<Note>
  쿼리의 `REFRESH ... SETTINGS` 부분에 있는 설정은 갱신 설정(예: `refresh_retries`)이며, 일반 설정(예: `max_threads`)과는 구분됩니다. 일반 설정은 쿼리 끝에 `SETTINGS`를 사용해 지정할 수 있습니다.
</Note>

### 갱신 일정

예시 갱신 일정:

```sql theme={null}
REFRESH EVERY 1 DAY -- every day, at midnight (UTC)
REFRESH EVERY 1 MONTH -- on 1st day of every month, at midnight
REFRESH EVERY 1 MONTH OFFSET 5 DAY 2 HOUR -- on 6th day of every month, at 2:00 am
REFRESH EVERY 2 WEEK OFFSET 5 DAY 15 HOUR 10 MINUTE -- every other Saturday, at 3:10 pm
REFRESH EVERY 30 MINUTE -- at 00:00, 00:30, 01:00, 01:30, etc
REFRESH AFTER 30 MINUTE -- 30 minutes after the previous refresh completes, no alignment with time of day
-- REFRESH AFTER 1 HOUR OFFSET 1 MINUTE -- syntax error, OFFSET is not allowed with AFTER
REFRESH EVERY 1 WEEK 2 DAYS -- every 9 days, not on any particular day of the week or month;
                            -- specifically, when day number (since 1969-12-29) is divisible by 9
REFRESH EVERY 5 MONTHS -- every 5 months, different months each year (as 12 is not divisible by 5);
                       -- specifically, when month number (since 1970-01) is divisible by 5
```

`RANDOMIZE FOR`는 각 갱신 시각을 무작위로 조정합니다. 예:

```sql theme={null}
REFRESH EVERY 1 DAY OFFSET 2 HOUR RANDOMIZE FOR 1 HOUR -- every day at random time between 01:30 and 02:30
```

지정된 뷰에서는 한 번에 최대 하나의 갱신만 실행될 수 있습니다. 예를 들어 `REFRESH EVERY 1 MINUTE`가 설정된 뷰의 갱신에 2분이 걸리면, 실제로는 2분마다 갱신됩니다. 이후 속도가 빨라져 10초 만에 갱신을 마치게 되면, 다시 1분마다 갱신됩니다. (즉, 누락된 갱신을 따라잡기 위해 10초마다 갱신되지는 않습니다. 그런 식의 밀린 갱신은 존재하지 않습니다.)

일반적으로 첫 번째 갱신은 materialized view가 생성된 직후 즉시 시작됩니다. 마지막 갱신 이후 경과 시간은 무한대이므로, 어떤 일정이든 지금 갱신할 시점으로 간주됩니다. `EMPTY`를 지정하면 이 초기 갱신은 건너뛰고, 첫 번째 갱신은 다음 예약된 시각에 수행됩니다. 예를 들어 `EVERY 1 HOUR`의 경우 첫 번째 갱신은 현재 시간의 끝에 수행됩니다.

### 복제된 DB에서

갱신 가능 materialized view가 [복제된 데이터베이스](/ko/reference/engines/database-engines/replicated)에 있는 경우, 레플리카는 서로 coordination하여 예약된 각 시점마다 하나의 레플리카만 갱신을 수행합니다. 갱신으로 생성된 데이터를 모든 레플리카가 확인할 수 있도록 [ReplicatedMergeTree](/ko/reference/engines/table-engines/mergetree-family/replication) 테이블 엔진이 필요합니다.

`APPEND` 모드에서는 `SETTINGS all_replicas = 1`을 사용해 coordination을 비활성화할 수 있습니다. 이렇게 하면 각 레플리카가 서로 독립적으로 갱신을 수행합니다. 이 경우 ReplicatedMergeTree는 필요하지 않습니다.

`APPEND`가 아닌 모드에서는 coordination된 갱신만 지원됩니다. coordination되지 않은 방식을 사용하려면 `Atomic` 데이터베이스와 `CREATE ... ON CLUSTER` 쿼리를 사용해 모든 레플리카에 갱신 가능 materialized view를 생성하십시오.

coordination은 Keeper를 통해 수행됩니다. znode 경로는 [default\_replica\_path](/ko/reference/settings/server-settings/settings/default-replica#default_replica_path) 서버 설정으로 결정됩니다.

### 갱신 의존성

`DEPENDS ON`은 서로 다른 테이블의 갱신 시점을 동기화합니다:

```sql theme={null}
CREATE MATERIALIZED VIEW dependent REFRESH EVERY 1 HOUR DEPENDS ON dependency [...]
```

종속 뷰의 갱신은 모든 의존 뷰의 갱신이 완료된 후에야 시작됩니다.

다른 뷰가 갱신된 직후 바로 갱신하려면:

```sql theme={null}
CREATE MATERIALIZED VIEW dependent REFRESH AFTER 0 SECOND DEPENDS ON dependency [...]
```

또는 같은 의미로:

```sql theme={null}
CREATE MATERIALIZED VIEW dependent REFRESH DEPENDS ON dependency [...]
```

<Note>
  `DEPENDS ON`은 갱신 가능 구체화 뷰(Refreshable Materialized View) 사이에서만 작동합니다. 특히 의존성 뷰에서 `TO <table>`을 사용하는 경우에는 테이블 이름이 아니라 뷰 이름을 사용해야 합니다. `DEPENDS ON` 목록에 일반 테이블, 갱신 가능하지 않은 뷰가 포함되어 있거나 오타가 있으면 해당 뷰는 갱신되지 않으며, `system.view_refreshes`에 상태가 `MissingDependencies`로 표시됩니다. 의존성은 `ALTER`를 사용해 변경하거나 제거할 수 있습니다. 자세한 내용은 [갱신 매개변수 변경](#changing-refresh-parameters)을 참조하십시오.
</Note>

#### 일관된 전파 지연 시간을 위해 DEPENDS ON 사용

두 뷰가 모두 같은 주기의 `REFRESH EVERY`를 사용하면 의존성은 각 타임슬롯마다 적용됩니다.

예를 들어 뷰 X와 Y가 모두 `REFRESH EVERY 1 HOUR`를 사용하고, Y가 X의 출력 테이블을 읽는다고 가정하겠습니다. 의존성이 없으면 Y는 일반적으로 X의 이전 시간 갱신 데이터만 보게 됩니다. `DEPENDS ON X`를 사용하면 Y의 11:00 갱신은 X의 11:00 갱신이 완료된 후에만 시작됩니다.

```text theme={null}
           10:00            11:00            12:00
           │                │                │
  X:        [run]┐           [run]┐           [run]┐
                 │                │                │
  Y:             └►[run]          └►[run]          └►[run]
```

의존 항목과 종속 항목은 갱신이 갱신 주기보다 오래 걸리면 각각 독립적으로 timeslot을 건너뛸 수 있습니다. 종속 항목이 의존 항목의 각 갱신마다 정확히 한 번씩 갱신된다는 보장은 없습니다.

```text theme={null}
           10:00          11:00          12:00          13:00
           │              │              │              |
  X:        [run]┐         [run]┐         [run]┐         [run]┐
                 │              └────┐    (Y skips 12:00)     └───┐
  Y:             └►[10:00 ru------un]└►[11:00 ru---------------un]└►[13:00 run]
```

#### 배치 스트림 처리에 DEPENDS ON 사용하기

`REFRESH EVERY`를 사용하지 않으면, 의존하는 뷰 X는 X가 마지막으로 갱신된 이후 모든 의존성이 각각 최소 한 번씩 갱신되었을 때 갱신됩니다. `REFRESH AFTER T`는 지연 시간을 추가합니다. 즉, 의존성이 갱신을 완료한 후 T 시간이 지나면 의존하는 뷰의 갱신이 시작됩니다.

순환 의존성은 허용되며 유용하게 활용할 수 있습니다. 다음 갱신 가능 구체화 뷰 그래프를 살펴보십시오:

1. X는 어떤 스트림에서 행 배치를 가져와 테이블(table)에 저장합니다.
2. 그런 다음 Y와 Z는 모두 해당 테이블을 읽고, 서로 다른 집계를 수행한 후 결과를 다른 테이블에 추가합니다.
3. 배치가 완전히 처리되면 X는 다음 배치를 가져오고, 이 사이클이 반복됩니다.

```text theme={null}
            source
               │
               ▼
          ┌─────────┐
     ┌───►│    X    │◄───┐
     │    └──┬───┬──┘    │
  DEPENDS    │   │    DEPENDS
    ON       ▼   ▼      ON
     │      ┌─┐ ┌─┐      │
     └──────┤Y│ │Z├──────┘
            └─┘ └─┘
```

전체 예시:

```sql theme={null}
CREATE TABLE current_batch (t UInt64, v Int64) ENGINE ReplicatedMergeTree ORDER BY t;
CREATE TABLE batch_log (max_t UInt64, n Int64, v_sum Int64, processed_at DateTime64) ENGINE ReplicatedMergeTree ORDER BY max_t;
CREATE TABLE stats (h UInt64, n UInt64) ENGINE ReplicatedSummingMergeTree ORDER BY h;

-- (system.numbers stands in for a data source with monotonically increasing timestamps or sequence numbers)
CREATE MATERIALIZED VIEW current_batch_v REFRESH EVERY 10 SECOND DEPENDS ON batch_log_v, stats_v TO current_batch AS SELECT number as t, number * 10 as v FROM system.numbers WHERE number > (SELECT max(max_t) FROM batch_log) LIMIT 100;

CREATE MATERIALIZED VIEW batch_log_v REFRESH DEPENDS ON current_batch_v APPEND TO batch_log AS SELECT max(t) as max_t, count() as n, sum(v) as v_sum, now64() as processed_at FROM current_batch;

CREATE MATERIALIZED VIEW stats_v REFRESH DEPENDS ON current_batch_v APPEND TO stats AS SELECT cityHash64(v) % 20 as h, count() as n FROM current_batch GROUP BY h;

-- Must trigger initial refresh manually.
SYSTEM REFRESH VIEW current_batch_v;
```

더 긴 체인도 잘 작동합니다.

이 기능은 갱신 조정 기능이 활성화된 경우, 즉 뷰가 Replicated 또는 Shared 데이터베이스에 있을 때에만 제대로 작동합니다. 조정 기능이 없으면 서버를 재시작할 때 사이클이 끊어지므로, 뷰를 생성한 후 한 번만이 아니라 재시작할 때마다 수동으로 `SYSTEM REFRESH VIEW`를 실행해야 합니다.

### 갱신 설정

사용 가능한 갱신 설정은 다음과 같습니다.

* `refresh_retries` - 갱신 쿼리가 예외로 실패한 경우 재시도할 횟수입니다. 모든 재시도가 실패하면 다음으로 예약된 갱신 시각으로 넘어갑니다. 0은 재시도하지 않음을, -1은 무한 재시도를 의미합니다. 기본값: 2.
* `refresh_retry_initial_backoff_ms` - `refresh_retries`가 0이 아닐 때 첫 번째 재시도 전까지의 지연 시간입니다. 이후 각 재시도마다 지연 시간이 두 배로 증가하며, 최대 `refresh_retry_max_backoff_ms`까지 늘어납니다. 기본값: 100 ms.
* `refresh_retry_max_backoff_ms` - 갱신 시도 사이의 지연 시간이 지수적으로 증가할 때의 상한입니다. 기본값: 60000 ms(1분).
* `all_replicas` - `APPEND`를 사용하는 [복제된 데이터베이스](/ko/reference/engines/database-engines/replicated)에서 모든 레플리카가 각각 독립적으로 갱신할지, 아니면 예약된 각 시각마다 하나의 레플리카만 갱신할지를 제어합니다. 이 설정은 뷰를 생성한 후에는 변경할 수 없습니다. 기본값: `false`.

### 갱신 매개변수 변경

기존 갱신 가능 구체화 뷰의 갱신 매개변수는 [`ALTER TABLE ... MODIFY REFRESH`](/ko/reference/statements/alter/view#alter-table--modify-refresh-statement)를 사용해 변경합니다:

```sql theme={null}
ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]
```

스케줄(`EVERY` 또는 `AFTER`)은 필수입니다. 이 구문은 항상 모든 갱신 매개변수(스케줄, `RANDOMIZE FOR`, `DEPENDS ON`, 갱신 설정)를 지정된 값으로 *모두* 대체합니다. 생략된 항목은 기본값으로 재설정되거나(설정), 제거됩니다(의존성, 무작위화).

<Note>
  * 갱신 설정만 변경하려는 경우(예: `refresh_retries`)에는 기존 스케줄을 다시 지정하십시오:

    ```sql theme={null}
    ALTER TABLE rmv MODIFY REFRESH EVERY 1 HOUR SETTINGS refresh_retries = 5;
    ```

  * materialized view에서는 `ALTER TABLE ... MODIFY SETTING refresh_retries = ...`가 지원되지 않습니다. 반드시 `MODIFY REFRESH`를 사용해야 합니다.

  * 갱신 모드는 변경할 수 없습니다. `APPEND`와 `INCREMENTAL`은 추가하거나 제거할 수 없습니다.

  * `all_replicas` 설정은 생성 후 변경할 수 없습니다.
</Note>

예시:

```sql theme={null}
-- Change the schedule, drop existing settings and dependencies.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE;

-- Change the schedule and tune retry behavior.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE
SETTINGS refresh_retries = 5,
         refresh_retry_initial_backoff_ms = 500,
         refresh_retry_max_backoff_ms = 60000;

-- Keep the dependency while changing the period.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR DEPENDS ON other_rmv;

-- Drop the dependency by omitting `DEPENDS ON`.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR;
```

### 기타 작업

모든 갱신 가능 materialized view의 상태는 [`system.view_refreshes`](/ko/reference/system-tables/view_refreshes) 테이블에서 확인할 수 있습니다. 특히 갱신 진행 상황(실행 중인 경우), 마지막 및 다음 갱신 시각, 갱신이 실패한 경우의 예외 메시지가 포함됩니다.

갱신을 수동으로 중지, 시작, 실행 또는 취소하려면 [`SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW`](/ko/reference/statements/system#managing-refreshable-materialized-views)를 사용하십시오.

갱신이 완료될 때까지 기다리려면 [`SYSTEM WAIT VIEW`](/ko/reference/statements/system#wait-view)를 사용하십시오. 특히 뷰를 생성한 후 초기 갱신이 완료될 때까지 기다릴 때 유용합니다.

<Note>
  재미있는 사실: 갱신 쿼리는 현재 갱신 중인 뷰를 읽을 수 있으며, 갱신 전 버전의 데이터를 보게 됩니다. 즉, Conway의 생명 게임을 구현할 수 있습니다: [https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==](https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==)
</Note>

## 관련 콘텐츠

* 블로그: [ClickHouse에서 시계열 데이터 다루기](https://clickhouse.com/blog/working-with-time-series-data-and-functions-ClickHouse)
* 블로그: [ClickHouse로 관측성 솔루션 구축하기 - 2부 - 트레이스](https://clickhouse.com/blog/storing-traces-and-spans-open-telemetry-in-clickhouse)

## 임시 뷰

ClickHouse는 다음과 같은 특성을 가진 **임시 뷰**를 지원합니다(해당하는 경우 임시 테이블과 동일):

* **세션 수명**
  임시 뷰는 현재 세션이 유지되는 동안에만 존재합니다. 세션이 종료되면 자동으로 삭제됩니다.

* **데이터베이스 없음**
  임시 뷰에는 데이터베이스 이름을 붙여 지정할 **수 없습니다**. 임시 뷰는 데이터베이스 바깥(세션 네임스페이스)에 존재합니다.

* **복제되지 않음 / ON CLUSTER 없음**
  임시 객체는 세션에 로컬로만 존재하므로 `ON CLUSTER`를 사용해 생성할 **수 없습니다**.

* **이름 해석**
  임시 객체(테이블 또는 뷰)와 영구 객체의 이름이 같고, 쿼리에서 데이터베이스를 지정하지 않고 해당 이름을 참조하면 **임시** 객체가 사용됩니다.

* **논리 객체(저장소 없음)**
  임시 뷰는 `SELECT` 텍스트만 저장합니다(내부적으로 `View` 저장소를 사용). 데이터를 영구 저장하지 않으며 `INSERT`를 받을 수 없습니다.

* **Engine 절**
  `ENGINE`은 지정하지 않아도 됩니다. `ENGINE = View`로 지정하더라도 무시되며 동일한 논리 뷰로 처리됩니다.

* **보안 / 권한**
  임시 뷰를 생성하려면 `CREATE TEMPORARY VIEW` 권한이 필요하며, 이 권한은 `CREATE VIEW`에 암묵적으로 포함됩니다.

* **SHOW CREATE**
  임시 뷰의 DDL을 출력하려면 `SHOW CREATE TEMPORARY VIEW view_name;`를 사용하십시오.

### 구문

```sql theme={null}
CREATE TEMPORARY VIEW [IF NOT EXISTS] view_name AS <select_query>
```

`OR REPLACE`는 임시 뷰에서는 지원되지 않습니다(임시 테이블과 일관성을 맞추기 위함입니다). 임시 뷰를 “대체”해야 하는 경우, 해당 뷰를 삭제한 후 다시 생성하십시오.

### 예시

먼저 임시 원본 테이블을 만들고, 그 위에 임시 뷰를 생성합니다:

```sql theme={null}
CREATE TEMPORARY TABLE t_src (id UInt32, val String);
INSERT INTO t_src VALUES (1, 'a'), (2, 'b');

CREATE TEMPORARY VIEW tview AS
SELECT id, upper(val) AS u
FROM t_src
WHERE id <= 2;

SELECT * FROM tview ORDER BY id;
```

해당 DDL을 확인합니다:

```sql theme={null}
SHOW CREATE TEMPORARY VIEW tview;
```

삭제:

```sql theme={null}
DROP TEMPORARY VIEW IF EXISTS tview;  -- 임시 뷰는 TEMPORARY TABLE 구문으로 삭제됩니다
```

### 지원되지 않는 항목 / 제한 사항

* `CREATE OR REPLACE TEMPORARY VIEW ...` → **허용되지 않음** (`DROP` + `CREATE` 사용).
* `CREATE TEMPORARY MATERIALIZED VIEW ...` → **허용되지 않음**.
* `CREATE TEMPORARY VIEW db.view AS ...` → **허용되지 않음** (데이터베이스 지정자 사용 불가).
* `CREATE TEMPORARY VIEW view ON CLUSTER 'name' AS ...` → **허용되지 않음** (임시 객체는 세션 로컬임).
* `POPULATE`, `REFRESH`, `TO [db.table]`, 내부 엔진, 그리고 모든 MV 전용 절 → 임시 뷰에는 **적용되지 않음**.

### 분산 쿼리 관련 참고 사항

임시 **뷰**는 단지 정의일 뿐이므로, 별도로 전달할 데이터는 없습니다. 임시 뷰가 임시 **테이블**(예: `Memory`)을 참조하는 경우, 해당 데이터는 임시 테이블과 마찬가지로 분산 쿼리 실행 중 원격 서버로 전송될 수 있습니다.

#### 예시

```sql theme={null}
-- A session-scoped, in-memory table
CREATE TEMPORARY TABLE temp_ids (id UInt64) ENGINE = Memory;

INSERT INTO temp_ids VALUES (1), (5), (42);

-- A session-scoped view over the temp table (purely logical)
CREATE TEMPORARY VIEW v_ids AS
SELECT id FROM temp_ids;

-- Replace 'test' with your cluster name.
-- GLOBAL JOIN forces ClickHouse to *ship* the small join-side (temp_ids via v_ids)
-- to every remote server that executes the left side.
SELECT count()
FROM cluster('test', system.numbers) AS n
GLOBAL ANY INNER JOIN v_ids USING (id)
WHERE n.number < 100;

```
