创建一个新视图。视图可以是普通、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 ...)由于参数化视图依赖于参数值,未提供参数时便没有 schema。
这意味着 system.columns 表中没有参数化视图的相关信息。
此外,只有提供参数后,DESCRIBE 查询才能执行。
DESCRIBE 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 REPLACE 和 IF 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和所有引擎选项。POPULATE仅在Atomic数据库中受支持——在Replicated数据库中会被拒绝 (请参见下方关于POPULATE的说明) 。 - 需要
CREATE VIEW和DROP VIEW特权。
示例:
-- 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 用于存储由相应 SELECT 查询转换后的数据。
创建不带 TO [db].[table] 的 Materialized view 时,必须指定 ENGINE,即用于存储数据的表引擎。
创建带有 TO [db].[table] 的 Materialized view 时,也可以使用 POPULATE 从现有源数据回填目标表 (目标表可能已包含数据,此时回填的行会被追加) 。POPULATE 不能与 REFRESH 结合使用:可刷新的 可刷新materialized view 会在首次刷新时填充,因此 POPULATE 会将初始数据加载两次 (请改用 EMPTY 跳过首次刷新) 。
Materialized view 的实现方式如下:当向 SELECT 中指定的表插入数据时,部分新插入的数据会经过该 SELECT 查询转换,结果再插入到该视图中。
如果指定 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 这样的去重引擎。
SELECT 查询可以包含 DISTINCT、GROUP BY、ORDER BY、LIMIT。请注意,相应的转换会在每个插入数据块上独立执行。例如,如果设置了 GROUP BY,数据会在插入期间进行 聚合,但仅限于单个插入数据包内的数据,之后不会再进一步聚合。例外情况是使用可自行执行数据 聚合 的 ENGINE,例如 SummingMergeTree。
如果 materialized view 使用 TO [db.]name 这种写法,你可以先 DETACH 该视图,对目标表执行 ALTER,然后再 ATTACH 之前已 DETACH 的视图。
视图看起来与普通表相同。例如,它们会显示在 SHOW TABLES 查询结果中。
要删除视图,请使用 DROP VIEW。不过,DROP TABLE 对 VIEW 也同样适用。
SQL 安全
DEFINER 和 SQL SECURITY 允许你指定在执行视图的底层查询时使用哪个 ClickHouse 用户。
SQL SECURITY 有三个合法取值:DEFINER、INVOKER 或 NONE。你可以在 DEFINER 子句中指定任何现有用户或 CURRENT_USER。
下表说明了从视图中查询时,哪个用户需要具备哪些权限。
请注意,无论 SQL 安全选项是什么,在所有情况下,仍然需要具备 GRANT SELECT ON <view> 才能读取该视图。
| SQL 安全选项 | 视图 | Materialized View |
|---|---|---|
DEFINER alice |
alice 必须拥有该视图源表的 SELECT 权限。 |
alice 必须拥有该视图源表的 SELECT 权限,以及该视图目标表的 INSERT 权限。 |
INVOKER |
用户必须拥有该视图源表的 SELECT 权限。 |
不能为 materialized view 指定 SQL SECURITY INVOKER。 |
NONE |
- | - |
如果未指定 DEFINER/SQL SECURITY,结果取决于 ignore_empty_sql_security_in_create_view_query 服务器设置。
当其默认值为 true 时,查询会按原样存储,且视图会获得空的 SQL 安全类型。普通视图随后将使用调用者的权限运行;对于显式指定目标表的 materialized view,将跳过对该目标表的访问检查:向源表插入数据不需要目标表的 INSERT 权限,从视图中读取数据也不需要其 SELECT 权限。
当值为 false 时,会在创建时将以下默认值写入视图定义:
SQL SECURITY:普通视图为INVOKER(可通过default_normal_view_sql_security配置) ,materialized view 为DEFINER(可通过default_materialized_view_sql_security配置)DEFINER:CURRENT_USER(可通过default_view_definer配置)
无论该设置为何值,可刷新materialized view 始终会获得这些默认值。
视图在服务器启动时附加或重新加载时,会保留其存储定义中的 SQL 安全类型,因此,未指定 DEFINER/SQL SECURITY 而存储的视图会保留空的 SQL 安全类型。
要更改现有视图的 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|YEARREFRESH 子句必须至少指定 EVERY、AFTER 或 DEPENDS ON 之一。单独使用 REFRESH (即一个都不带) 会被拒绝。不带 EVERY/AFTER 的 REFRESH DEPENDS ON ... 是 REFRESH AFTER 0 SECOND DEPENDS ON ... 的简写;请参见下方的 刷新依赖。
周期性运行相应的查询,并将其结果存储到表中。
- 如果指定了
APPEND,每次刷新都会将行插入表中,而不会删除现有行。该 insert 不是原子的,和普通的INSERT INTO ... SELECT查询一样。 - 否则,每次刷新都会以原子方式替换表'的现有内容。
与常规不可刷新的 materialized view 的区别:
- 没有插入触发器。当新数据插入到
SELECT中指定的表时,它不会自动推送到可刷新materialized view。相反,只有在周期性刷新或手动刷新执行时才会插入数据。 - 对
SELECT查询没有限制。表函数 (例如url()) 、视图、UNION、JOIN 都允许使用。
刷新调度
刷新调度示例:
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 5RANDOMIZE 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 保持一致的传播延迟
如果两个视图都使用相同周期的 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 构成的关系图:
- X 从某个 stream 中取出一批行,并将其写入一个表。
- 然后,Y 和 Z 都从该表读取数据,执行不同的聚合,并将结果追加到其他表中。
- 该批次完全处理完后,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- 在带有APPEND的 Replicated database 中,控制是让所有副本独立刷新,还是在每个计划时间点仅由一个副本刷新。视图创建后不可更改。默认值:false。
更改刷新参数
可使用 ALTER TABLE ... MODIFY REFRESH 更改现有可刷新materialized view的刷新参数:
ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]调度 (EVERY 或 AFTER) 是必填项:该语句始终会用指定内容替换所有刷新参数——包括调度、RANDOMIZE FOR、DEPENDS ON 和刷新设置。凡是未指定的内容,都会重置为默认值 (设置) 或被移除 (依赖项、随机化) 。
示例:
-- 更改调度计划,删除现有设置和依赖项。
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。这在创建视图后等待首次刷新时尤其有用。
窗口视图
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 ...→ 不允许 (临时对象仅在当前会话内有效) 。POPULATE、REFRESH、TO [db.table]、内部引擎以及所有 MV 专用子句 → 不适用于临时视图。
关于分布式查询的说明
临时视图只是一个定义,本身不包含可传递的数据。如果临时视图引用了临时表 (例如 Memory) ,其数据在分布式查询执行期间也可以像临时表一样传送到远程服务器。
示例
-- 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;