跳转到主要内容
创建一个新视图。视图可以是普通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 ...)

参数化视图

参数化视图与普通视图类似,但可以在创建时定义不会立即解析的参数。这类视图可与表函数配合使用:将视图名称作为函数名,将参数值作为其参数传入。
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 及其内部存储表 (如有) 。此操作要求使用 AtomicReplicated 数据库引擎。
CREATE OR REPLACE MATERIALIZED VIEW [db.]name [ON CLUSTER cluster]
[TO [db.]target_table]
[ENGINE = engine]
[POPULATE]
[REFRESH ...]
AS SELECT ...
关键行为:
  • 不带 TO 子句:旧的内部表会被删除,并创建一个新的内部表。除非指定了 POPULATE,否则内部表中的现有数据将会丢失。
  • TO 子句:仅替换视图定义;目标表及其数据不受影响。
  • 兼容 REFRESHON CLUSTER 和所有引擎选项。POPULATE 仅在 Atomic 数据库中受支持——在 Replicated 数据库中会被拒绝 (请参见下方关于 POPULATE 的说明) 。
  • 需要 CREATE VIEWDROP VIEW 特权。
CREATE OR REPLACE MATERIALIZED VIEW 仅受 AtomicReplicated 数据库引擎支持。不支持 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 views 用于存储由相应 SELECT 查询转换后的数据。 创建不带 TO [db].[table] 的 Materialized view 时,必须指定 ENGINE,即用于存储数据的表引擎。 创建带有 TO [db].[table] 的 Materialized view 时,不能同时使用 POPULATE Materialized view 的实现方式如下:当向 SELECT 中指定的表插入数据时,部分新插入的数据会经过该 SELECT 查询转换,结果再插入到该视图中。
ClickHouse 中的 materialized view 在插入到目标表时使用列名,而不是列顺序。如果 SELECT 查询结果中缺少某些列名,ClickHouse 会使用默认值,即使该列不是 Nullable。安全的做法是在使用 materialized view 时为每一列都添加别名。ClickHouse 中的 materialized view 在实现上更像插入触发器。如果视图查询中包含聚合,它只会应用于刚插入的那一批数据。对源表现有数据的任何更改 (如 update、delete、drop partition 等) 都不会改变 materialized view。ClickHouse 中的 materialized view 在发生错误时不具有确定性行为。这意味着,已经写入的块会保留在目标表中,但出错后的所有块都不会保留。默认情况下,如果向某个视图写入时抛出异常,INSERT 查询将失败。此时数据块是否已经到达源表并不保证——这取决于插入管道的时序,而不是视图错误。对失败的 INSERT 使用插入去重 (insert_deduplicatededuplicate_blocks_in_dependent_materialized_views) 进行重试,以获得对源表及所有依赖视图的 exactly-once 交付。INSERT 查询上设置 materialized_views_ignore_errors=true 只会改变错误报告方式:每个视图错误都会作为警告记录下来,且 INSERT 查询会成功。向失败视图的目标端交付是不完整的——异常发生前已处理的块会被保留,而失败的块及其后的所有块都会从该视图中丢弃。该目标端下游的视图只能看到实际到达的那些块,因此它们的交付也同样是不完整的。未抛出异常的同级视图 (以及它们的下游链) 会被完整写入,而源表也会照常写入。由于 INSERT 会报告成功,客户端不会收到失败信号,也不会触发自动重试;仅当源表写入绝不能被视图侧问题阻塞时,才应使用此设置 (例如 system.*_log 表) 。对于 system.*_log 表,materialized_views_ignore_errors 默认为 true
如果指定 POPULATE,则在创建视图时,现有表数据会被插入到视图中,就像执行 CREATE TABLE ... AS SELECT ... 一样。否则,查询只包含在视图创建之后插入到表中的数据。我们不建议使用 POPULATE,因为在创建视图期间插入到表中的数据不会被插入到该视图中。
鉴于 POPULATE 的工作方式类似于 CREATE TABLE ... AS SELECT ...,因此它有以下限制:
  • 不支持 Replicated database
  • 不支持 ClickHouse Cloud
可改用单独的 INSERT ... SELECT
SELECT 查询可以包含 DISTINCTGROUP BYORDER BYLIMIT。请注意,相应的转换会在每个插入数据块上独立执行。例如,如果设置了 GROUP BY,数据会在插入期间进行 聚合,但仅限于单个插入数据包内的数据,之后不会再进一步聚合。例外情况是使用可自行执行数据 聚合 的 ENGINE,例如 SummingMergeTree 如果 materialized view 使用 TO [db.]name 这种写法,你可以先 DETACH 该视图,对目标表执行 ALTER,然后再 ATTACH 之前已 DETACH 的视图。 请注意,materialized view 会受到 optimize_on_insert 设置的影响。数据会先合并,再插入到视图中。 视图看起来与普通表相同。例如,它们会显示在 SHOW TABLES 查询结果中。 要删除视图,请使用 DROP VIEW。不过,DROP TABLE 对 VIEW 也同样适用。

SQL 安全

DEFINERSQL SECURITY 允许你指定在执行视图的底层查询时使用哪个 ClickHouse 用户。 SQL SECURITY 有三个合法取值:DEFINERINVOKERNONE。你可以在 DEFINER 子句中指定任何现有用户或 CURRENT_USER 下表说明了从视图中查询时,哪个用户需要具备哪些权限。 请注意,无论 SQL 安全选项是什么,在所有情况下,仍然需要具备 GRANT SELECT ON <view> 才能读取该视图。
SQL 安全选项视图Materialized 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,则 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 ...

Live View

该功能已弃用,后续将被移除。 为方便查阅,旧版文档见此处

可刷新materialized 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 子句必须至少指定 EVERYAFTERDEPENDS ON 之一。单独使用 REFRESH (即一个都不带) 会被拒绝。不带 EVERY/AFTERREFRESH DEPENDS ON ...REFRESH AFTER 0 SECOND DEPENDS ON ... 的简写;请参见下方的 刷新依赖 周期性运行相应的查询,并将其结果存储到表中。
  • 如果指定了 APPEND,每次刷新都会将行插入表中,而不会删除现有行。该 insert 不是原子的,和普通的 INSERT INTO ... SELECT 查询一样。
  • 否则,每次刷新都会以原子方式替换表’的现有内容。
与常规不可刷新的 materialized view 的区别:
  • 没有插入触发器。当新数据插入到 SELECT 中指定的表时,它不会自动推送到可刷新materialized view。相反,只有在周期性刷新或手动刷新执行时才会插入数据。
  • SELECT 查询没有限制。表函数 (例如 url()) 、视图、UNION、JOIN 都允许使用。
查询中 REFRESH ... SETTINGS 部分的 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 秒内完成 刷新,那么它又会恢复为每分钟 刷新 一次。 (特别地,它不会为了补上错过的 刷新 而改为每 10 秒 刷新 一次——因为并不存在这样的积压。) 通常,materialized view 创建后会立即启动第一次 刷新:距离上次 刷新 的时间相当于无穷大,因此无论什么调度,都会认为现在应该刷新。如果指定了 EMPTY,则会跳过这次初始 刷新,第一次 刷新 会在下一个计划时间发生;例如,对于 EVERY 1 HOUR,第一次 刷新 会在当前小时结束时发生。

在 Replicated DB 中

如果可刷新 materialized view 位于 Replicated database 中,各副本会相互协调,从而确保在每个计划的刷新时间点只有一个副本执行刷新。这里要求使用 ReplicatedMergeTree 表引擎,这样所有副本都能看到刷新生成的数据。 APPEND 模式下,可以通过 SETTINGS all_replicas = 1 禁用协调。这样一来,各副本会彼此独立地执行刷新。在这种情况下,不要求使用 ReplicatedMergeTree。 在非 APPEND 模式下,仅支持协调刷新。若要使用非协调方式,请使用 Atomic database 和 CREATE ... ON CLUSTER 查询,在所有副本上创建可刷新 materialized view。 协调通过 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 仅适用于可刷新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]
如果刷新执行时间超过刷新周期,依赖项和依赖方都可能各自独立地跳过某些时间槽。无法保证依赖方会对依赖项的每一次刷新都恰好执行一次刷新。
           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 时间开始刷新。 允许循环依赖,而且这很有用。请考虑下面这个由可刷新materialized view 构成的关系图:
  1. X 从某个 stream 中取出一批行,并将其写入一个表。
  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 database 中。如果没有协调机制,服务器重启会中断这一循环,因此每次重启后都需要手动执行一次 SYSTEM REFRESH VIEW,而不是只在创建视图后执行一次。

刷新设置

可用的刷新设置:
  • refresh_retries - 刷新查询因异常失败时的重试次数。如果所有重试都失败,则跳过并等待下一个计划刷新时间。0 表示不重试,-1 表示无限重试。默认值:2。
  • refresh_retry_initial_backoff_ms - 如果 refresh_retries 不为 0,首次重试前的延迟时间。此后每次重试的延迟都会翻倍,直到达到 refresh_retry_max_backoff_ms。默认值:100 毫秒。
  • refresh_retry_max_backoff_ms - 刷新尝试之间延迟时间指数增长的上限。默认值:60000 毫秒 (1 分钟) 。
  • all_replicas - 在带有 APPENDReplicated database 中,控制是让所有副本独立刷新,还是在每个计划时间点仅由一个副本刷新。视图创建后不可更改。默认值:false

更改刷新参数

可使用 ALTER TABLE ... MODIFY REFRESH 更改现有可刷新materialized view的刷新参数:
ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]
调度 (EVERYAFTER) 是必填项:该语句始终会用指定内容替换所有刷新参数——包括调度、RANDOMIZE FORDEPENDS 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’s game of life:https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==

Window View

这是一项 Experimental 功能,未来的发行版中可能会发生向后不兼容的变更。使用 allow_experimental_window_view 设置来启用 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——即用于存储数据的表引擎。

时间窗口函数

时间窗口函数 用于获取记录的窗口上下界。窗口视图 需要配合时间窗口函数使用。

时间属性

窗口视图 支持按 处理时间事件时间 进行处理。 处理时间 允许 窗口视图 基于本地机器时间生成结果,且默认使用这种方式。它是最直观的时间概念,但不具备确定性。可以通过将时间窗口函数的 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 语法支持事件时间处理。 窗口视图提供三种 watermark 策略:
  • STRICTLY_ASCENDING:发出截至当前已观测到的最大时间戳作为 watermark。时间戳小于该最大时间戳的行不属于迟到数据。
  • ASCENDING:发出截至当前已观测到的最大时间戳减 1 作为 watermark。时间戳等于或小于该最大时间戳的行不属于迟到数据。
  • BOUNDED:WATERMARK=INTERVAL。发出 watermark,其值为已观测到的最大时间戳减去指定延迟。
以下查询展示了如何使用 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);
默认情况下,当 watermark 到达时会触发窗口,晚于 watermark 到达的元素会被丢弃。窗口视图可通过设置 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 语句来修改窗口视图中定义的 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 秒的滚动窗口:
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*) 。

窗口视图用法

窗口视图适用于以下场景:
  • 监控:按时间对指标日志进行聚合和计算,并将结果输出到目标表。仪表板可以将目标表用作源表。
  • 分析:在时间窗口内自动聚合并预处理数据。这在分析大量日志时非常有用。预处理可消除多个查询中的重复计算,并降低查询延迟。

临时视图

ClickHouse 支持临时视图,其特性如下 (在适用情况下与临时表一致) :
  • 会话生命周期 临时视图仅在当前会话期间存在。会话结束后会自动删除。
  • 无数据库 不能使用数据库名限定临时视图。它存在于数据库之外 (会话命名空间) 。
  • 不复制 / 不支持 ON CLUSTER 临时对象仅在当前会话内有效,不能通过 ON CLUSTER 创建。
  • 名称解析 如果临时对象 (表或视图) 与持久对象同名,且某个查询在不带数据库名的情况下引用该名称,则会使用临时对象。
  • 逻辑对象 (无存储) 临时视图仅存储其 SELECT 文本 (内部使用 View 存储) 。它不会持久化数据,也不接受 INSERT
  • Engine 子句 无需指定 ENGINE;如果写为 ENGINE = View,也会被忽略,并视为相同的逻辑视图。
  • 安全 / 特权 创建临时视图需要 CREATE TEMPORARY VIEW 特权,而 CREATE VIEW 会隐式授予该特权。
  • SHOW CREATE 使用 SHOW CREATE TEMPORARY VIEW view_name; 可输出临时视图的 DDL。

语法

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 ...不允许 (临时对象仅在当前会话内有效) 。
  • POPULATEREFRESHTO [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 将较小的 join 端(通过 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日