메인 콘텐츠로 건너뛰기
새로운 뷰를 생성합니다. 뷰는 일반, materialized, 갱신 가능한 materialized, 윈도우 유형으로 만들 수 있습니다.

일반 뷰

구문:
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 절의 서브쿼리로 사용됩니다. 예시로, 뷰를 생성했다고 가정하겠습니다:
CREATE VIEW view AS SELECT ...
그리고 쿼리를 작성했습니다:
SELECT a, b, c FROM view
이 쿼리는 다음 서브쿼리를 사용하는 것과 완전히 동일합니다:
SELECT a, b, c FROM (SELECT ...)

매개변수화된 뷰(Parameterized View)

매개변수화된 뷰는 일반 뷰와 유사하지만, 즉시 해석되지 않는 매개변수를 포함해 생성할 수 있습니다. 이러한 뷰는 테이블 함수와 함께 사용할 수 있으며, 이 경우 함수 이름에는 뷰 이름을 지정하고 인수에는 매개변수 값을 지정합니다.
CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...
위 구문은 아래와 같이 매개변수를 치환하여 테이블 함수로 사용할 수 있는 테이블용 뷰를 생성합니다.
SELECT * FROM view(column1=value1, column2=value2 ...)

Materialized View

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']
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 REPLACEIF NOT EXISTS는 서로 배타적이므로 함께 사용할 수 없습니다. 둘을 함께 사용하면 구문 오류가 발생합니다.

CREATE OR REPLACE MATERIALIZED VIEW

CREATE OR REPLACE MATERIALIZED VIEW는 기존 materialized view와 해당 내부 저장 테이블(있는 경우)을 원자적으로 대체합니다. 이 작업을 수행하려면 Atomic 또는 Replicated 데이터베이스 엔진이 필요합니다.
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, 그리고 모든 엔진 옵션과 호환됩니다. POPULATEAtomic 데이터베이스에서만 지원되며, Replicated 데이터베이스에서는 허용되지 않습니다(아래 POPULATE 참고 사항 참조).
  • CREATE VIEWDROP VIEW 권한이 필요합니다.
CREATE OR REPLACE MATERIALIZED VIEWAtomic 또는 Replicated 데이터베이스 엔진에서만 지원됩니다. Ordinary 데이터베이스 엔진에서는 지원되지 않습니다.
예시:
-- 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;
Materialized views를 사용하는 단계별 가이드는 여기에서 확인할 수 있습니다.
materialized view는 해당 SELECT 쿼리로 변환된 데이터를 저장합니다. TO [db].[table] 없이 materialized view를 생성할 때는 데이터를 저장할 테이블 엔진인 ENGINE을 지정해야 합니다. TO [db].[table]과 함께 materialized view를 생성할 때는 POPULATE를 함께 사용할 수 없습니다. materialized view는 다음과 같이 동작합니다. SELECT에 지정된 테이블에 데이터를 삽입하면, 삽입된 데이터의 일부가 이 SELECT 쿼리로 변환되고 그 결과가 뷰에 삽입됩니다.
ClickHouse의 materialized view는 대상 테이블에 삽입할 때 컬럼 순서가 아니라 컬럼 이름을 사용합니다. 일부 컬럼 이름이 SELECT 쿼리 결과에 없으면, 해당 컬럼이 널 허용이 아니더라도 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_errorssystem.*_log 테이블에서 기본적으로 true입니다.
POPULATE를 지정하면, 뷰를 생성할 때 기존 테이블 데이터가 마치 CREATE TABLE ... AS SELECT ...를 수행하는 것처럼 뷰에 삽입됩니다. 그렇지 않으면 쿼리에는 뷰를 생성한 이후 테이블에 삽입된 데이터만 포함됩니다. 뷰 생성 중에 테이블에 삽입된 데이터는 뷰에 삽입되지 않으므로 POPULATE 사용은 권장하지 않습니다.
POPULATECREATE TABLE ... AS SELECT ...처럼 동작하므로 다음과 같은 제한이 있습니다.
  • 복제된 데이터베이스에서는 지원되지 않습니다
  • ClickHouse Cloud에서는 지원되지 않습니다
대신 별도의 INSERT ... SELECT를 사용할 수 있습니다.
SELECT 쿼리에는 DISTINCT, GROUP BY, ORDER BY, LIMIT를 포함할 수 있습니다. 해당 변환은 삽입된 데이터의 각 블록에서 서로 독립적으로 수행된다는 점에 유의하십시오. 예를 들어 GROUP BY가 설정된 경우 데이터는 삽입 중에 집계되지만, 이는 삽입된 데이터의 단일 패킷 내에서만 수행됩니다. 이후에는 데이터가 추가로 집계되지 않습니다. 예외적으로 SummingMergeTree처럼 자체적으로 데이터 집계를 수행하는 ENGINE을 사용할 때는 다릅니다. materialized view가 TO [db.]name 구문을 사용하는 경우, 뷰를 DETACH하고 대상 테이블에 ALTER를 실행한 다음, 앞서 분리한(DETACH) 뷰를 ATTACH할 수 있습니다. materialized view는 optimize_on_insert 설정의 영향을 받는다는 점에 유의하십시오. 데이터는 뷰에 삽입되기 전에 머지됩니다. 뷰는 일반 테이블과 동일하게 표시됩니다. 예를 들어 SHOW TABLES 쿼리 결과에 나열됩니다. 뷰를 삭제하려면 DROP VIEW를 사용하십시오. DROP TABLE도 VIEW에 사용할 수 있습니다.

SQL 보안

DEFINERSQL SECURITY를 사용하면 뷰의 기반 쿼리를 실행할 때 어떤 ClickHouse 사용자를 사용할지 지정할 수 있습니다. SQL SECURITY에는 DEFINER, INVOKER, NONE의 세 가지 유효한 값이 있습니다. DEFINER 절에는 기존 사용자 또는 CURRENT_USER를 지정할 수 있습니다. 다음 표는 뷰에서 SELECT할 때 어떤 사용자에게 어떤 권한이 필요한지 설명합니다. SQL 보안 옵션과 관계없이, 어떤 경우에도 뷰를 읽으려면 여전히 GRANT SELECT ON <view> 권한이 필요합니다.
SQL security optionViewMaterialized View
DEFINER alicealice는 뷰의 소스 테이블에 대한 SELECT 권한이 있어야 합니다.alice는 뷰의 소스 테이블에 대한 SELECT 권한과 뷰의 대상 테이블에 대한 INSERT 권한이 있어야 합니다.
INVOKER사용자는 뷰의 소스 테이블에 대한 SELECT 권한이 있어야 합니다.materialized view에는 SQL SECURITY INVOKER를 지정할 수 없습니다.
NONE--
SQL SECURITY NONE은 더 이상 권장되지 않는 옵션입니다. SQL SECURITY NONE으로 뷰를 생성할 수 있는 권한이 있는 사용자는 임의의 쿼리를 실행할 수 있습니다. 따라서 이 옵션으로 뷰를 생성하려면 GRANT ALLOW SQL SECURITY NONE TO <user> 권한이 필요합니다.
DEFINER/SQL SECURITY를 지정하지 않으면 기본값이 사용됩니다: DEFINER/SQL SECURITY를 지정하지 않고 뷰를 ATTACH하면, materialized view의 기본값은 SQL SECURITY NONE이고 일반 뷰의 기본값은 SQL SECURITY INVOKER입니다. 기존 뷰의 SQL 보안을 변경하려면 다음을 사용합니다:
ALTER TABLE MODIFY SQL SECURITY { DEFINER | INVOKER | NONE } [DEFINER = { user | CURRENT_USER }]

예시

CREATE VIEW test_view
DEFINER = alice SQL SECURITY DEFINER
AS SELECT ...
CREATE VIEW test_view
SQL SECURITY INVOKER
AS SELECT ...

라이브 view

이 기능은 더 이상 사용이 권장되지 않으며, 향후 제거될 예정입니다. 편의를 위해 이전 문서는 여기에서 확인하실 수 있습니다.

갱신 가능 구체화 뷰

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]
[TO[db.]name] [(columns)] [ENGINE = engine]
[EMPTY]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
여기서 interval은 단순한 인터벌의 연속입니다:
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 ...의 축약형입니다. 자세한 내용은 아래의 갱신 의존성을 참조하십시오. 해당 쿼리를 주기적으로 실행하고 결과를 테이블에 저장합니다.
  • APPEND를 지정하면 각 갱신 시 기존 행을 삭제하지 않고 테이블에 행을 삽입합니다. 이 삽입은 일반적인 INSERT INTO ... SELECT 쿼리와 마찬가지로 원자적이지 않습니다.
  • 그렇지 않으면 각 갱신 시 테이블의 이전 내용이 원자적으로 대체됩니다.
일반적인 비갱신형 materialized view와의 차이점:
  • 삽입 트리거가 없습니다. SELECT에 지정된 테이블에 새 데이터가 삽입되어도 갱신 가능 구체화 뷰로 자동 반영되지 않습니다. 대신 데이터 삽입은 주기적 또는 수동 갱신 실행 중에만 이루어집니다.
  • SELECT 쿼리에는 제한이 없습니다. 테이블 함수(예: url()), 뷰, UNION, JOIN을 모두 사용할 수 있습니다.
쿼리의 REFRESH ... SETTINGS 부분에 있는 설정은 갱신 설정(예: refresh_retries)이며, 일반 설정(예: max_threads)과는 구분됩니다. 일반 설정은 쿼리 끝에 SETTINGS를 사용해 지정할 수 있습니다.

갱신 일정

예시 갱신 일정:
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는 각 갱신 시각을 무작위로 조정합니다. 예:
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에서

갱신 가능 구체화 뷰가 복제된 데이터베이스에 있는 경우, 레플리카는 서로 coordination하여 예약된 각 시점마다 하나의 레플리카만 갱신을 수행합니다. 갱신으로 생성된 데이터를 모든 레플리카가 확인할 수 있도록 ReplicatedMergeTree 테이블 엔진이 필요합니다. APPEND 모드에서는 SETTINGS all_replicas = 1을 사용해 coordination을 비활성화할 수 있습니다. 이렇게 하면 각 레플리카가 서로 독립적으로 갱신을 수행합니다. 이 경우 ReplicatedMergeTree는 필요하지 않습니다. APPEND가 아닌 모드에서는 coordination된 갱신만 지원됩니다. coordination되지 않은 방식을 사용하려면 Atomic 데이터베이스와 CREATE ... ON CLUSTER 쿼리를 사용해 모든 레플리카에 갱신 가능 구체화 뷰를 생성하십시오. coordination은 Keeper를 통해 수행됩니다. znode 경로는 default_replica_path 서버 설정으로 결정됩니다.

갱신 의존성

DEPENDS ON은 서로 다른 테이블의 갱신 시점을 동기화합니다:
CREATE MATERIALIZED VIEW dependent REFRESH EVERY 1 HOUR DEPENDS ON dependency [...]
종속 뷰의 갱신은 모든 의존 뷰의 갱신이 완료된 후에야 시작됩니다. 다른 뷰가 갱신된 직후 바로 갱신하려면:
CREATE MATERIALIZED VIEW dependent REFRESH AFTER 0 SECOND DEPENDS ON dependency [...]
또는 같은 의미로:
CREATE MATERIALIZED VIEW dependent REFRESH DEPENDS ON dependency [...]
DEPENDS ON은 갱신 가능 구체화 뷰(Refreshable Materialized View) 사이에서만 작동합니다. 특히 의존성 뷰에서 TO <table>을 사용하는 경우에는 테이블 이름이 아니라 뷰 이름을 사용해야 합니다. DEPENDS ON 목록에 일반 테이블, 갱신 가능하지 않은 뷰가 포함되어 있거나 오타가 있으면 해당 뷰는 갱신되지 않으며, system.view_refreshes에 상태가 MissingDependencies로 표시됩니다. 의존성은 ALTER를 사용해 변경하거나 제거할 수 있습니다. 자세한 내용은 갱신 매개변수 변경을 참조하십시오.

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

두 뷰가 모두 같은 주기의 REFRESH EVERY를 사용하면 의존성은 각 타임슬롯마다 적용됩니다. 예를 들어 뷰 X와 Y가 모두 REFRESH EVERY 1 HOUR를 사용하고, Y가 X의 출력 테이블을 읽는다고 가정하겠습니다. 의존성이 없으면 Y는 일반적으로 X의 이전 시간 갱신 데이터만 보게 됩니다. DEPENDS ON X를 사용하면 Y의 11:00 갱신은 X의 11:00 갱신이 완료된 후에만 시작됩니다.
           10:00            11:00            12:00
           │                │                │
  X:        [run]┐           [run]┐           [run]┐
                 │                │                │
  Y:             └►[run]          └►[run]          └►[run]
의존 항목과 종속 항목은 갱신이 갱신 주기보다 오래 걸리면 각각 독립적으로 timeslot을 건너뛸 수 있습니다. 종속 항목이 의존 항목의 각 갱신마다 정확히 한 번씩 갱신된다는 보장은 없습니다.
           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는 다음 배치를 가져오고, 이 사이클이 반복됩니다.
            source


          ┌─────────┐
     ┌───►│    X    │◄───┐
     │    └──┬───┬──┘    │
  DEPENDS    │   │    DEPENDS
    ON       ▼   ▼      ON
     │      ┌─┐ ┌─┐      │
     └──────┤Y│ │Z├──────┘
            └─┘ └─┘
전체 예시:
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를 사용하는 복제된 데이터베이스에서 모든 레플리카가 각각 독립적으로 갱신할지, 아니면 예약된 각 시각마다 하나의 레플리카만 갱신할지를 제어합니다. 이 설정은 뷰를 생성한 후에는 변경할 수 없습니다. 기본값: false.

갱신 매개변수 변경

기존 갱신 가능 구체화 뷰의 갱신 매개변수는 ALTER TABLE ... MODIFY REFRESH를 사용해 변경합니다:
ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]
스케줄(EVERY 또는 AFTER)은 필수입니다. 이 구문은 항상 모든 갱신 매개변수(스케줄, RANDOMIZE FOR, DEPENDS ON, 갱신 설정)를 지정된 값으로 모두 대체합니다. 생략된 항목은 기본값으로 재설정되거나(설정), 제거됩니다(의존성, 무작위화).
  • 갱신 설정만 변경하려는 경우(예: refresh_retries)에는 기존 스케줄을 다시 지정하십시오:
    ALTER TABLE rmv MODIFY REFRESH EVERY 1 HOUR SETTINGS refresh_retries = 5;
    
  • materialized view에서는 ALTER TABLE ... MODIFY SETTING refresh_retries = ...가 지원되지 않습니다. 반드시 MODIFY REFRESH를 사용해야 합니다.
  • APPEND를 추가하거나 제거하는 것은 지원되지 않습니다.
  • all_replicas 설정은 생성 후 변경할 수 없습니다.
예시:
-- 스케줄을 변경하고 기존 설정 및 의존성을 삭제합니다.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE;

-- 스케줄을 변경하고 재시도 동작을 조정합니다.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE
SETTINGS refresh_retries = 5,
         refresh_retry_initial_backoff_ms = 500,
         refresh_retry_max_backoff_ms = 60000;

-- 주기를 변경하면서 의존성을 유지합니다.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR DEPENDS ON other_rmv;

-- `DEPENDS ON`을 생략하여 의존성을 삭제합니다.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR;

기타 작업

모든 갱신 가능 materialized view의 상태는 system.view_refreshes 테이블에서 확인할 수 있습니다. 특히 갱신 진행 상황(실행 중인 경우), 마지막 및 다음 갱신 시각, 갱신이 실패한 경우의 예외 메시지가 포함됩니다. 갱신을 수동으로 중지, 시작, 실행 또는 취소하려면 SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW를 사용하십시오. 갱신이 완료될 때까지 기다리려면 SYSTEM WAIT VIEW를 사용하십시오. 특히 뷰를 생성한 후 초기 갱신이 완료될 때까지 기다릴 때 유용합니다.
재미있는 사실: 갱신 쿼리는 현재 갱신 중인 뷰를 읽을 수 있으며, 갱신 전 버전의 데이터를 보게 됩니다. 즉, Conway의 생명 게임을 구현할 수 있습니다: https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==

윈도우 뷰

이 기능은 실험 단계이며, 향후 릴리스에서 하위 호환성이 깨지는 방식으로 변경될 수 있습니다. allow_experimental_window_view 설정으로 윈도우 뷰와 WATCH 쿼리 사용을 활성화하십시오. set allow_experimental_window_view = 1 명령을 입력하십시오.
CREATE WINDOW VIEW [IF NOT EXISTS] [db.]table_name [TO [db.]table_name] [INNER ENGINE engine] [ENGINE engine] [WATERMARK strategy] [ALLOWED_LATENESS interval_function] [POPULATE]
AS SELECT ...
GROUP BY time_window_function
[COMMENT 'comment']
윈도우 뷰는 시간 윈도우별로 데이터를 집계하고, 윈도우가 실행될 준비가 되면 결과를 출력할 수 있습니다. 부분 집계 결과를 내부(또는 지정된) 테이블에 저장해 지연 시간을 줄이며, 처리 결과를 지정된 테이블로 푸시하거나 WATCH 쿼리를 사용해 알림을 전송할 수 있습니다. 윈도우 뷰 생성은 MATERIALIZED VIEW 생성과 유사합니다. 윈도우 뷰는 중간 데이터를 저장하기 위한 내부 스토리지 엔진이 필요합니다. 내부 스토리지는 INNER ENGINE 절을 사용해 지정할 수 있으며, 지정하지 않으면 기본 내부 엔진으로 AggregatingMergeTree를 사용합니다. TO [db].[table] 없이 윈도우 뷰를 생성할 때는 데이터를 저장할 테이블 엔진인 ENGINE을 반드시 지정해야 합니다.

시간 윈도우 함수

시간 윈도우 함수는 레코드의 윈도우 하한과 상한을 가져오는 데 사용됩니다. 윈도우 뷰는 시간 윈도우 함수와 함께 사용해야 합니다.

시간 속성

윈도우 뷰는 **처리 시간(processing time)**과 이벤트 시간(event time) 처리를 지원합니다. **처리 시간(processing time)**은 로컬 머신의 시간을 기준으로 윈도우 뷰가 결과를 생성하도록 하며, 기본적으로 사용됩니다. 가장 직관적인 시간 개념이지만 결정성을 보장하지는 않습니다. 처리 시간 속성은 시간 윈도 함수의 time_attr를 테이블 컬럼으로 설정하거나 now() 함수를 사용해 정의할 수 있습니다. 다음 쿼리는 처리 시간을 사용하는 윈도우 뷰를 생성합니다.
CREATE WINDOW VIEW wv AS SELECT count(number), tumbleStart(w_id) as w_start from date GROUP BY tumble(now(), INTERVAL '5' SECOND) as w_id
이벤트 시간은 각 개별 이벤트가 이벤트를 생성한 장치에서 실제로 발생한 시각입니다. 이 시각은 일반적으로 레코드가 생성될 때 레코드 안에 포함됩니다. 이벤트 시간 처리를 사용하면 이벤트 순서가 뒤바뀌거나 늦게 도착하는 경우에도 일관된 결과를 얻을 수 있습니다. 윈도우 뷰는 WATERMARK 구문을 사용해 이벤트 시간 처리를 지원합니다. 윈도우 뷰는 세 가지 워터마크 전략을 제공합니다.
  • STRICTLY_ASCENDING: 지금까지 관측된 최대 타임스탬프의 워터마크를 내보냅니다. 타임스탬프가 최대 타임스탬프보다 작은 행은 지연된 행으로 간주되지 않습니다.
  • ASCENDING: 지금까지 관측된 최대 타임스탬프에서 1을 뺀 워터마크를 내보냅니다. 타임스탬프가 최대 타임스탬프와 같거나 더 작은 행은 지연된 행으로 간주되지 않습니다.
  • BOUNDED: WATERMARK=INTERVAL. 관측된 최대 타임스탬프에서 지정된 지연 시간을 뺀 워터마크를 내보냅니다.
다음 쿼리는 WATERMARK를 사용하여 윈도우 뷰를 생성하는 예시입니다.
CREATE WINDOW VIEW wv WATERMARK=STRICTLY_ASCENDING AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
CREATE WINDOW VIEW wv WATERMARK=ASCENDING AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
CREATE WINDOW VIEW wv WATERMARK=INTERVAL '3' SECOND AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
기본적으로 윈도우는 워터마크가 도착하면 트리거되며, 워터마크보다 늦게 도착한 요소는 삭제됩니다. 윈도우 뷰는 ALLOWED_LATENESS=INTERVAL을 설정해 지연 이벤트 처리를 지원합니다. 지연 처리의 예시는 다음과 같습니다.
CREATE WINDOW VIEW test.wv TO test.dst WATERMARK=ASCENDING ALLOWED_LATENESS=INTERVAL '2' SECOND AS SELECT count(a) AS count, tumbleEnd(wid) AS w_end FROM test.mt GROUP BY tumble(timestamp, INTERVAL '5' SECOND) AS wid;
지연 트리거로 방출된 요소는 이전 계산의 갱신된 결과로 처리해야 합니다. 윈도우가 끝날 때 트리거되는 대신, 윈도우 뷰는 지연 이벤트가 도착하는 즉시 트리거됩니다. 따라서 동일한 윈도우에 대해 여러 번 출력될 수 있습니다. 사용자는 이러한 중복 결과를 고려하거나 중복을 제거해야 합니다. ALTER TABLE ... MODIFY QUERY statement를 사용하면 윈도우 뷰에 지정된 SELECT 쿼리를 수정할 수 있습니다. 새 SELECT 쿼리의 결과 데이터 구조는 TO [db.]name 절의 유무와 관계없이 원래 SELECT 쿼리와 동일해야 합니다. 중간 상태는 재사용할 수 없으므로 현재 윈도우의 데이터는 손실된다는 점에 유의하십시오.

새 윈도우 모니터링

윈도우 뷰는 변경 사항을 모니터링하는 WATCH 쿼리를 지원하며, TO 구문을 사용해 결과를 테이블로 출력할 수도 있습니다.
WATCH [db.]window_view
[EVENTS]
[LIMIT n]
[FORMAT format]
쿼리를 종료하기 전에 받을 업데이트 횟수를 설정하려면 LIMIT를 지정할 수 있습니다. EVENTS 절을 사용하면 WATCH 쿼리의 축약형을 사용할 수 있으며, 쿼리 결과 대신 최신 쿼리 워터마크만 받습니다.

설정

  • window_view_clean_interval: 오래된 데이터를 정리하기 위한 윈도우 뷰 정리 간격(초)입니다. 시스템 시간 또는 WATERMARK 구성에 따라 아직 완전히 트리거되지 않은 윈도우는 유지되며, 그 외 데이터는 삭제됩니다.
  • window_view_heartbeat_interval: watch 쿼리가 살아 있음을 나타내는 하트비트 간격(초)입니다.
  • wait_for_window_view_fire_signal_timeout: 이벤트 시간 처리에서 윈도우 뷰 실행 신호를 기다리는 시간 제한입니다.

예시

data라는 로그 테이블에서 10초마다 클릭 로그의 개수를 집계해야 한다고 가정하겠습니다. 테이블 구조는 다음과 같습니다:
CREATE TABLE data ( `id` UInt64, `timestamp` DateTime) ENGINE = Memory;
먼저, 10초 인터벌의 tumble window를 사용하는 window view를 생성합니다:
CREATE WINDOW VIEW wv as select count(id), tumbleStart(w_id) as window_start from data group by tumble(timestamp, INTERVAL '10' SECOND) as w_id
그런 다음 WATCH 쿼리를 사용해 결과를 확인합니다.
WATCH wv
로그가 data 테이블에 삽입되면,
INSERT INTO data VALUES(1,now())
WATCH 쿼리는 다음과 같이 결과가 출력되어야 합니다:
┌─count(id)─┬────────window_start─┐
│         1 │ 2020-01-14 16:56:40 │
└───────────┴─────────────────────┘
또는 TO 구문을 사용해 출력을 다른 테이블로 보낼 수도 있습니다.
CREATE WINDOW VIEW wv TO dst AS SELECT count(id), tumbleStart(w_id) as window_start FROM data GROUP BY tumble(timestamp, INTERVAL '10' SECOND) as w_id
추가 예시는 ClickHouse의 상태 유지 테스트에서도 확인할 수 있습니다(해당 테스트에서는 *window_view*라는 이름을 사용합니다).

윈도우 뷰 사용

윈도우 뷰는 다음과 같은 시나리오에서 유용합니다:
  • 모니터링: 메트릭 로그를 시간별로 집계하고 계산한 뒤, 결과를 대상 테이블(target table)에 출력합니다. 대시보드는 대상 테이블을 소스 테이블로 사용할 수 있습니다.
  • 분석: 시간 윈도우에서 데이터를 자동으로 집계하고 전처리합니다. 이는 대량의 로그를 분석할 때 유용합니다. 전처리를 통해 여러 쿼리에서 반복 계산을 없애고 쿼리 지연 시간을 줄일 수 있습니다.

임시 뷰

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;를 사용하십시오.

구문

CREATE TEMPORARY VIEW [IF NOT EXISTS] view_name AS <select_query>
OR REPLACE는 임시 뷰에서는 지원되지 않습니다(임시 테이블과 일관성을 맞추기 위함입니다). 임시 뷰를 “대체”해야 하는 경우, 해당 뷰를 삭제한 후 다시 생성하십시오.

예시

먼저 임시 원본 테이블을 만들고, 그 위에 임시 뷰를 생성합니다:
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을 확인합니다:
SHOW CREATE TEMPORARY VIEW tview;
삭제:
DROP TEMPORARY VIEW IF EXISTS tview;  -- 임시 뷰는 TEMPORARY TABLE 구문으로 삭제됩니다

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

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

분산 쿼리 관련 참고 사항

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

예시

-- 세션 범위의 인메모리 테이블
CREATE TEMPORARY TABLE temp_ids (id UInt64) ENGINE = Memory;

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

-- 임시 테이블에 대한 세션 범위의 뷰 (순수 논리적)
CREATE TEMPORARY VIEW v_ids AS
SELECT id FROM temp_ids;

-- 'test'를 실제 클러스터 이름으로 변경하십시오.
-- GLOBAL JOIN은 ClickHouse가 작은 조인 측(v_ids를 통한 temp_ids)을
-- 왼쪽을 실행하는 모든 원격 서버로 *전송*하도록 강제합니다.
SELECT count()
FROM cluster('test', system.numbers) AS n
GLOBAL ANY INNER JOIN v_ids USING (id)
WHERE n.number < 100;

마지막 수정일 2026년 6월 25일