物化视图

物化视图源表--基础数据源

创建源表,因为我们的目标涉及报告聚合数据而不是单条记录,所以我们可以解析它,将信息传递给物化视图,并丢弃实际传入的数据。这符合我们的目标并节省了存储空间,因此我们将使用Null表引擎。

CREATE DATABASE IF NOT EXISTS analytics;

CREATE TABLE analytics.hourly_data
(
`domain_name` String,
`event_time` DateTime,
`count_views` UInt64
)
ENGINE = Null;

注意:可以在Null表上创建物化视图。因此,写入表的数据最终会影响视图,但原始原始数据仍将被丢弃

月度汇总表和物化视图

对于第一个物化视图,需要创建 Target 表(本例子中为analytics.monthly_aggregated_data),例中将按月份和域名存储视图的总和。

CREATE TABLE analytics.monthly_aggregated_data
(
`domain_name` String,
`month` Date,
`sumCountViews` AggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree
ORDER BY (domain_name, month);

将转发Target表上数据的物化视图如下:

CREATE MATERIALIZED VIEW analytics.monthly_aggregated_data_mv
TO analytics.monthly_aggregated_data
AS
SELECT
toDate(toStartOfMonth(event_time)) AS month,
domain_name,
sumState(count_views) AS sumCountViews
FROM analytics.hourly_data
GROUP BY domain_name, month;

年度汇总表和物化视图

现在,创建第二个物化视图,该视图将链接到之前的目标表monthly_aggregated_data

首先,创建一个新的目标表,该表将存储每个域名每年汇总的视图总和。

CREATE TABLE analytics.year_aggregated_data
(
`domain_name` String,
`year` UInt16,
`sumCountViews` UInt64
)
ENGINE = SummingMergeTree()
ORDER BY (domain_name, year);

然后创建物化视图,此步骤定义级联。FROM 语句将使用monthly_aggregated_data表,这意味着数据流将是:

1.数据到达hourly_data表。

2.ClickHouse会将收到的数据转发到第一个物化视图monthly_aggregated_data

3.最后,步骤2中接收到的数据将被转发到 year_aggregated_data

CREATE MATERIALIZED VIEW analytics.year_aggregated_data_mv
TO analytics.year_aggregated_data
AS
SELECT
toYear(toStartOfYear(month)) AS year,
domain_name,
sumMerge(sumCountViews) as sumCountViews
FROM analytics.monthly_aggregated_data
GROUP BY domain_name, year;

注意:

在使用物化视图时,一个常见的误解是数据是从表中读取的,这不是Materialized views的工作方式;转发的数据是插入的数据块,而不是表中的最终结果。

想象一下,在这个例子中,monthly_aggregated_data中使用的引擎是一个折叠合并树(CollapsingMergeTree),转发到第二个物化视图year_aggregated_data_mv 的数据将不是折叠表的最终结果,它将转发具有正如SELECT… GROUP BY中定义的字段的数据块。

如果末正在使用CollapsingMergeTreeReplacingMergeTree,甚至SummingMergeTree,并且计划创建级联物化视图,则需要了解此处描述的限制。

采集数据

现在是时候通过插入一些数据来测试我们的级联物化视图了:

INSERT INTO analytics.hourly_data (domain_name, event_time, count_views)
VALUES ('clickhouse.com', '2019-01-01 10:00:00', 1),
('clickhouse.com', '2019-02-02 00:00:00', 2),
('clickhouse.com', '2019-02-01 00:00:00', 3),
('clickhouse.com', '2020-01-01 00:00:00', 6);

查询analytics.hourly_data的内容,将查不到任何记录,因为表引擎为Null,但数据已被处理

 SELECT * FROM analytics.hourly_data

输出:

domain_name|event_time|count_views|
-----------+----------+-----------+

结果

如果尝试查询目标表的sumCountViews字段值,将看到字段值以二进制表示(在某些终端中),因为该值不是以数字的形式存储,而是以AggregateFunction类型存储的。要获得聚合的最终结果,应该使用-Merge后缀。

通过以下查询,sumCountViews字段值无法正常显示:

SELECT sumCountViews FROM analytics.monthly_aggregated_data

输出:

sumCountViews|
-------------+
|
|
|

使用 Merge后缀获取 sumCountViews 值:

SELECT sumMerge(sumCountViews) as sumCountViews
FROM analytics.monthly_aggregated_data;

输出:

sumCountViews|
-------------+
12|

AggregatingMergeTree 中将AggregateFunction 定义为sum,因此可以使用sumMerge。当在AggregateFunction上使用函数avg时,则将使用avgMerge,以此类推。

SELECT month, domain_name, sumMerge(sumCountViews) as sumCountViews
FROM analytics.monthly_aggregated_data
GROUP BY domain_name, month

输出:

month     |domain_name   |sumCountViews|
----------+--------------+-------------+
2020-01-01|clickhouse.com| 6|
2019-01-01|clickhouse.com| 1|
2019-02-01|clickhouse.com| 5|

现在我们可以查看物化视图是否符合我们定义的目标。

现在已经将数据存储在目标表monthly_aggregated_data中,可以按月聚合每个域名的数据:

SELECT month, domain_name, sumMerge(sumCountViews) as sumCountViews
FROM analytics.monthly_aggregated_data
GROUP BY domain_name, month;

输出:

month     |domain_name   |sumCountViews|
----------+--------------+-------------+
2020-01-01|clickhouse.com| 6|
2019-01-01|clickhouse.com| 1|
2019-02-01|clickhouse.com| 5|

按年聚合每个域名的数据:

SELECT year, domain_name, sum(sumCountViews)
FROM analytics.year_aggregated_data
GROUP BY domain_name, year;

输出:

year|domain_name   |sum(sumCountViews)|
----+--------------+------------------+
2019|clickhouse.com| 6|
2020|clickhouse.com| 6|

组合多个源表来创建单个目标表

物化视图还可以用于将多个源表组合以到一个目标表中。这对于创建类似于 UNION ALL逻辑的物化视图非常有用。

首先,创建两个代表不同指标集的源表:

CREATE TABLE analytics.impressions
(
`event_time` DateTime,
`domain_name` String
) ENGINE = MergeTree ORDER BY (domain_name, event_time); CREATE TABLE analytics.clicks
(
`event_time` DateTime,
`domain_name` String
) ENGINE = MergeTree ORDER BY (domain_name, event_time);

然后使用组合的指标集创建 Target表:

CREATE TABLE analytics.daily_overview
(
`on_date` Date,
`domain_name` String,
`impressions` SimpleAggregateFunction(sum, UInt64),
`clicks` SimpleAggregateFunction(sum, UInt64)
) ENGINE = AggregatingMergeTree ORDER BY (on_date, domain_name);

创建两个指向同一Target表的物化视图。不需要显式地包含缺少的列:

CREATE MATERIALIZED VIEW analytics.daily_impressions_mv
TO analytics.daily_overview
AS
SELECT
toDate(event_time) AS on_date,
domain_name,
count() AS impressions,
0 clicks --<<<--- 如果去掉该列,则默认为 clicks为0
FROM
analytics.impressions
GROUP BY toDate(event_time) AS on_date, domain_name; CREATE MATERIALIZED VIEW analytics.daily_clicks_mv
TO analytics.daily_overview
AS
SELECT
toDate(event_time) AS on_date,
domain_name,
count() AS clicks,
0 impressions --<<<---如果去掉该列,则默认为 impressions 为0
FROM
analytics.clicks
GROUP BY toDate(event_time) AS on_date, domain_name;

现在,当插入值时,这些值将被聚合到Target表中的相应列中:

INSERT INTO analytics.impressions (domain_name, event_time)
VALUES ('clickhouse.com', '2019-01-01 00:00:00'),
('clickhouse.com', '2019-01-01 12:00:00'),
('clickhouse.com', '2019-02-01 00:00:00'),
('clickhouse.com', '2019-03-01 00:00:00')
; INSERT INTO analytics.clicks (domain_name, event_time)
VALUES ('clickhouse.com', '2019-01-01 00:00:00'),
('clickhouse.com', '2019-01-01 12:00:00'),
('clickhouse.com', '2019-03-01 00:00:00')
;

查询目标表 the Target table:

SELECT
on_date,
domain_name,
sum(impressions) AS impressions,
sum(clicks) AS clicks
FROM
analytics.daily_overview
GROUP BY
on_date,
domain_name
;

输出:

on_date   |domain_name   |impressions|clicks|
----------+--------------+-----------+------+
2019-01-01|clickhouse.com| 2| 2|
2019-03-01|clickhouse.com| 1| 1|
2019-02-01|clickhouse.com| 1| 0|

参考链接

https://clickhouse.com/docs/en/guides/developer/cascading-materialized-views

AggregateFunction

聚合函数有一个实现定义的中间状态,可以序列化为AggregateFunction(...)数据类型,并通常通过物化视图存储在表中。生成聚合函数状态的常见方法是使用State后缀调用聚合函数。为了以后能获得聚合的最终结果,必须使用带有-Merge后缀的相同聚合函数。

AggregateFunction(name, types_of_arguments...) — 参数数据类型。

参数说明:

  • 聚合函数名称。如果名称对应的聚合函数鞋带参数,则还需要为其它指定参数。
  • 聚合函数参数类型。

示例

CREATE TABLE testdb.aggregated_test_tb
(
`__name__` String,
`count` AggregateFunction(count),
`avg_val` AggregateFunction(avg, Float64),
`max_val` AggregateFunction(max, Float64),
`time_max` AggregateFunction(argMax, DateTime, Float64),
`mid_val` AggregateFunction(quantiles(0.5, 0.9), Float64)
) ENGINE = AggregatingMergeTree()
ORDER BY (__name__);

备注:如果上述SQL未添加ORDER BY (__name__, create_time),执行会报类似如下错误:

SQL 错误 [42]: ClickHouse exception, code: 42, host: 192.168.88.131, port: 8123; Code: 42, e.displayText() = DB::Exception: Storage AggregatingMergeTree requires 3 to 4 parameters:
name of column with date,
[sampling element of primary key],
primary key expression,
index granularity

创建数据源表并插入测试数据

CREATE TABLE testdb.test_tb
(
`__name__` String,
`create_time` DateTime,
`val` Float64
) ENGINE = MergeTree()
PARTITION BY toStartOfWeek(create_time)
ORDER BY (__name__, create_time); INSERT INTO testdb.test_tb(`__name__`, `create_time`, `val`) VALUES
('xiaoxiao', now(), 80.5),
('xiaolin', addSeconds(now(), 10), 89.5),
('xiaohong', addSeconds(now(), 20), 90.5),
('lisi', addSeconds(now(), 30), 79.5),
('zhangshang', addSeconds(now(), 40), 60),
('wangwu', addSeconds(now(), 50), 65);

插入数据

使用以State后缀的聚合函数的INSERT SELECT 以插入数据--比如希望获取目标列数据均值,即avg(target_column),那么插入数据时使用的聚合函数为avgState*State聚合函数返回状态(state),而不是最终值。换句话说,返回一个 AggregateFunction 类型的值。

INSERT INTO testdb.aggregated_test_tb (`__name__`, `count`, `avg_val`, `max_val`, `time_max`, `mid_val`)
SELECT `__name__`,
countState() AS count,
avgState(val) AS avg_val,
maxState(val) AS max_val,
argMaxState(create_time, val) AS time_max,
quantilesState(0.5, 0.9)(val) AS `mid_val`
FROM testdb.test_tb
GROUP BY `__name__`, toStartOfMinute(create_time);

注意:SELECT语句中的字段,要么使用聚合函数调用(比如上述val字段),要么保持原字段不变(比如上述__name__字段),保持原字段不变时,该字段必须包含于GROUP BY子句中,否则会报类似如下错误:

SQL 错误 [215]: ClickHouse exception, code: 215, host: 192.168.88.131, port: 8123; Code: 215, e.displayText() = DB::Exception: Column `__name__` is not under aggregate function and not in GROUP BY (version 20.3.5.21 (official build))

查询数据

AggregatingMergeTree表中查询数据时,使用GROUP BY子句和与插入数据时相同的聚合函数,但使用Merge后缀,比如插入数据时使用的聚合函数为avgState,那么查询时使用的聚合函数为avgMerge

后缀为Merge的聚合函数接受一组状态,将它们组合在一起,并返回完整数据聚合的结果。

例如,以下两个查询返回相同的结果

SELECT `__name__`,
create_time,
avgMerge(avg_val) AS avg_val,
maxMerge(max_val) AS max_val
FROM (
SELECT `__name__`,
toStartOfMinute(create_time) AS create_time,
avgState(val) AS avg_val,
maxState(val) AS max_val
FROM testdb.test_tb
GROUP BY `__name__`, create_time
)
GROUP BY `__name__`, create_time; SELECT `__name__`,
toStartOfMinute(create_time) AS create_time,
avg(val) AS avg_val,
max(val) AS max_val
FROM testdb.test_tb
GROUP BY `__name__`, create_time;

例子:

SELECT `__name__`,
countMerge(`count`),
avgMerge(`avg_val`),
maxMerge(`max_val`),
argMaxMerge(`time_max`),
quantilesMerge(0.5, 0.9)(`mid_val`)
FROM testdb.aggregated_test_tb
GROUP BY `__name__`;

参考链接

https://clickhouse.com/docs/en/sql-reference/data-types/aggregatefunction

AggregatingMergeTree

引擎继承自MergeTree,更改了数据块合并的逻辑。ClickHouse使用一条存储了聚合函数状态组合的单条记录(在一个数据块中)替换带有相同主键(或更准确地说,用相同的排序键)的所有行

说明:数据块是指ClickHouse存储数据的基本单位

可以使用 AggregatingMergeTree 表进行增量数据聚合,包括聚合物化视图。

引擎处理以下类型的所有列:

  • AggregateFunction

  • SimpleAggregateFunction

    如果能减少有序行数,则使用AggregatingMergeTree是合适的

建表

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = AggregatingMergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[TTL expr]
[SETTINGS name=value, ...]

有关请求参数的描述,参阅请求描述

查询语句

创建AggregatingMergeTree表与创建MergeTree表的子句相同。

查询和插入

要插入数据,使用INSERT SELECT使用aggregateState函数进行查询。从AggregatingMergeTree表中查询数据时,使用GROUP BY子句和与插入数据时相同的聚合函数,但使用Merge后缀。

SELECT查询的结果中,AggregateFunction类型的值对所有ClickHouse输出格式都有特定于实现的二进制表示。例如,如果你可以使用SELECT查询将数据转储为TabSeparated格式,则可以使用INSERT查询将此转储重新加载。

一个物化视图示例

CREATE DATABASE testdb;

创建存放原始数据的testdb.visits表:

CREATE TABLE testdb.visits
(
StartDate DateTime64,
CounterID UInt64,
Sign Nullable(Int32),
UserID Nullable(Int32)
) ENGINE = MergeTree
ORDER BY (StartDate, CounterID);

说明:上述StartDate DateTime64, 如果写成StartDate DateTime64 NOT NULL, 运行会报错,如下:

Expected one of: CODEC, ALIAS, TTL, ClosingRoundBracket, Comma, DEFAULT, MATERIALIZED, COMMENT, token (version 20.3.5.21 (official build))

接下来,创建一个AggregatingMergeTree表,该表将存储AggregationFunction,用于跟踪访问总数和唯一用户数。

创建一个AggregatingMergeTree 物化视图,用于监视testdb.revisits表,并使用AggregateFunction 类型:

CREATE TABLE testdb.agg_visits (
StartDate DateTime64,
CounterID UInt64,
Visits AggregateFunction(sum, Nullable(Int32)),
Users AggregateFunction(uniq, Nullable(Int32))
)
ENGINE = AggregatingMergeTree() ORDER BY (StartDate, CounterID);
SQL 错误 [70]: ClickHouse exception, code: 70, host: 192.168.88.131, port: 8123; Code: 70, e.displayText() = DB::Exception: Conversion from AggregateFunction(sum, Int32) to AggregateFunction(sum, Nullable(Int32)) is not supported: while converting source column Visits to destination column Visits: while pushing to view testdb.visits_mv (version 20.3.5.21 (official build))

CREATE TABLE testdb.agg_visits (
StartDate DateTime64,
CounterID UInt64,
Visits AggregateFunction(sum, Int32),
Users AggregateFunction(uniq, Int32)
)
ENGINE = AggregatingMergeTree() ORDER BY (StartDate, CounterID);

创建一个物化视图,从testdb.revisits填充testdb.agg_visits

CREATE MATERIALIZED VIEW testdb.visits_mv TO testdb.agg_visits
AS SELECT
StartDate,
CounterID,
sumState(Sign) AS Visits,
uniqState(UserID) AS Users
FROM testdb.visits
GROUP BY StartDate, CounterID;

插入数据到 testdb.visits 表:

INSERT INTO testdb.visits (StartDate, CounterID, Sign, UserID)
VALUES (1667446031000, 1, 3, 4), (1667446031000, 1, 6, 3);

数据被同时插入到testdb.revisitstestdb.agg_visits中。

执行诸如 SELECT ... GROUP BY ...的语句查询物化视图test.mv_visits以获取聚合数据

SELECT
StartDate,
sumMerge(Visits) AS Visits,
uniqMerge(Users) AS Users
FROM testdb.agg_visits
GROUP BY StartDate
ORDER BY StartDate;

输出:

StartDate          |Visits|Users|
-------------------+------+-----+
2022-11-03 11:27:11| 9| 2|

testdb.revisits中添加另外2条记录,但这次尝试对其中一条记录使用不同的时间戳:

INSERT INTO testdb.visits (StartDate, CounterID, Sign, UserID)
VALUES (1669446031000, 2, 5, 10), (1667446031000, 3, 7, 5);

再次查询,输出如下:

StartDate          |Visits|Users|
-------------------+------+-----+
2022-11-03 11:27:11| 16| 3|
2022-11-26 15:00:31| 5| 1|

参考链接

https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/aggregatingmergetree

ClickHouse 物化视图学习总结的更多相关文章

  1. 转:oracle物化视图学习笔记

    最近学习了一下物化视图,正好经理不在,把学习结果贴出来供大家一起研究一下吧. 先看一下物化视图的大概含义吧,感觉baidu的定义还不错 物化视图,它是用于预先计算并保存表连接或聚集等耗时较多的操作的结 ...

  2. clickhouse物化视图

    今天来简单介绍一下clickhouse的物化视图 物化视图支持表引擎,数据保存形式由它的表引擎决定,创建物化视图的完整语法如下: create materialized view mv_log eng ...

  3. SQL Server物化视图学习笔记

    一. 基本知识   摘抄自http://www.cnblogs.com/kissdodog/p/3385161.html SQL Server索引 - 索引(物化)视图 <第九篇> 索引视 ...

  4. ClickHouse性能优化?试试物化视图

    一.前言 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS):目前我们使用CH作为实时数仓用于统计分析,在做性能优化的时候使用了 物化视图 这一特性作为优化手段,本文主 ...

  5. [terry笔记]物化视图 materialized view基础学习

    一.物化视图定义摘录:     物化视图是包括一个查询结果的数据库对像(由系统实现定期刷新数据),物化视图不是在使用时才读取,而是预先计算并保存表连接或聚集等耗时较多的操作结果,这样在查询时大大提高了 ...

  6. (005)每日SQL学习:关于物化视图的一系列创建等语句

    --给用户授权 GRANT CREATE MATERIALIZED VIEW TO CDR; --创建物化视图的表日志(具体到某个表,物化视图中用到几个表就需要建立几个日志):当用FAST选项创建物化 ...

  7. (004)每日SQL学习:物化视图之二

    一.    物化视图概述 Oracle的物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表.物化视图存储基于远程表的数据,也可以称为快照. 物化视图可 ...

  8. (003)每日SQL学习:普通视图和物化视图

    关于这一点一直就是很懵懂的状态,今天特意网上查了一下资料,以下摘抄网上比较好的答案.以作记录. 普通视图和物化视图的区别答曰:普通视图和物化视图根本就不是一个东西,说区别都是硬拼到一起的,首先明白基本 ...

  9. [转载]oracle物化视图

    原文URL:http://lzfhope.blog.163.com/blog/static/636399220124942523943/?suggestedreading&wumii 环境or ...

  10. Oracle远程数据建物化视图(materialized)创建简单记录,以及DBLINK的创建

    目的:实现远程数据库访问及其相应表的定时同步 一.远程数据库dblink的创建 select * from dba_db_links; select * from user_sys_privs;--查 ...

随机推荐

  1. Vs Code, Visual Studio 2022, Angular and Live Server Running Through Https and IP Address

    前言 之前就写过 angular cli, vs code liveserver, vs 2019 iis express 10, vs code kestrel 使用 https + ip. 但写的 ...

  2. Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数

    title: Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数 date: 2024/9/14 updated: 2024/9/14 author: cmdragon excerpt: 通 ...

  3. C++ STL list容器——链表

    list容器 简介 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列节点组成,节点可以在运行时动态生成.每个节点包括两部分:一个是存储 ...

  4. BFS 马的遍历————洛谷p1443

    马的遍历 题目描述 有一个 \(n \times m\) 的棋盘,在某个点 \((x, y)\) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步. 输入格式 输入只有一行四个整数,分别为 ...

  5. [OI] 整体二分

    整体二分可以理解成普通二分改版,其实并没有改多少,并且一般对 check() 函数的复杂度要求更宽松 先来看一道经典题目:求区间排名 给一个数列,若干组询问 \((l,r,k)\),求 \([l,r] ...

  6. Flutter 实现骨架屏

    什么是骨架屏 在客户端开发中,我们总是需要等待拿到服务端的响应后,再将内容呈现到页面上,那么在用户发起请求到客户端成功拿到响应的这段时间内,应该在屏幕上呈现点什么好呢? 答案是:骨架屏 那么什么是骨架 ...

  7. 将一个Eigen::Matrix中的数据(数组格式),按行写入到json文件当中.

    1.这里主要实现如何以数组的形式写入到json文件当中,因为c++的Jsoncpp库中的.append只支持一个字符的写入(还是python的json友好).去网上找了老久的解决办法,发现中文解答全是 ...

  8. Java日期时间API系列31-----Jdk8中java.time包中的新的日期时间API类,时间戳的获取方式对比、转换和使用。

    时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到.Java中本来已经有相关获取时间戳的方法,Java8后增加新的类In ...

  9. iOS定义常量的两种方式define和FOUNDATION_EXPORT

    FOUNDATION_EXPORT的使用方法: 1.h文件 FOUNDATION_EXPORT NSString * const kTestString; 2.m文件NSString * const ...

  10. 04 Transformer 中的位置编码的 Pytorch 实现

    1:10 点赞 16:00 我爱你 你爱我 1401 class PositionalEncoding(nn.Module): def __init__(self, dim, dropout, max ...