数据仓库表设计理论

数据仓库表设计理论

数仓顾名思义是数据仓库,其数据来源大多来自于业务数据(例如:关系型数据库),当设计数仓中表类型时(拉链表、增量表、全量表、流水表、切片表)时,应先观察业务数据的特点再设计数仓表结构

首先业务数据是会不断增长的-即增量,而在不断增长的前提下业务数据又可以分为两类:

  • 增量更新数据源:数据源允许新增、修改和删除操作的数据源
  • 增量非更新数据源:数据源只允许新增数据,不允许对历史数据进行修改的数据源

业务数据中的这两种数据源类型直接决定了数仓中的表设计的选择

一、增量更新数据源

增量更新数据源是指允许新增、修改和删除操作的数据源。这种数据源的主要特点是:

  1. 数据可修改:可以对历史数据进行修改、覆盖,以反映数据的变更。
  2. 实时性高:由于数据可以随时更新,具有很强的实时性和即时性。

常见的增量更新数据源包括关系型数据库、NoSQL数据库、文件系统等,它们都支持对数据进行新增、修改和删除操作。

示例:关系型数据库中的用户表,其手机号字段经常会出现修改

在这里插入图片描述

在2017-01-02这一天表中的数据是, 用户002和004资料进行了修改,005是新增用户:

![ref1]

针对这类数据源,我们将分别阐述下数仓中的各种类型的表设计

1.1、全量表

全量表就是存储了全部数据的表,没有分区之分,可以理解为总共就一个分区。

全量表中存储了截至目前为止最新状态的全部记录,这就表示会存在历史状态的更新。

1.1.1、场景

以业务数据-用户表为例,2020-06-01有三个用户注册,表如下:

![ref1]

2020-06-02有一名用户注册,即新增了一名用户(标红),此时数仓中全量表更新后会记录全量的数据,此时数据表如下:

在这里插入图片描述

1.1.2、实现方式

全量表的实现方式又分为两种:

  • 全量替换:直接获取全表业务数据替换昨日全量单表
  • join替换:通过合并:每日增改数据的临时表 或 流水表,重写后得到全新全量表
1.1.2.1、全量替换
-- 创建一张新的全量单表
CREATE TABLE new_table (col1 STRING, col2 INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/user/hive/warehouse/new_table';-- 将全部的业务数据导入至全量单表, 过程不赘述
insert into new_table ..........-- 将新表名替换为旧表名
ALTER TABLE old_table RENAME TO old_table_backup;
ALTER TABLE new_table RENAME TO old_table;-- 删除昨日全量单表
DROP TABLE old_table_backup;
1.1.2.2、join替换

注意:业务数据需要有唯一的ID 和 upadte_time,否则无法使用join替换。

-- 创建一张新的临时增量表
CREATE TABLE incremental_table (id INT, column1 STRING, column2 STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/user/hive/warehouse/incremental_table';-- 将增量数据加载到一个临时表中,一般是通过业务数据的update_time来区分哪些是增量数据
insert into incremental_table xxxx-- 使用LEFT JOIN语句将原表和增量数据合并
SELECT COALESCE(i.id, o.id) as id,COALESCE(i.column1, o.column1) as column1,COALESCE(i.column2, o.column2) as column2,...
FROM original_table o
LEFT JOIN incremental_table i
ON o.id = i.id;-- 这将生成一个包含原表和增量数据的JOIN结果。在上面的例子中,我们使用了COALESCE函数来选择非空值,以便我们可以从原表和增量数据中选择最新的值。
-- 最后,我们可以使用INSERT OVERWRITE语句将合并后的数据插入到原表中
INSERT OVERWRITE TABLE original_table
SELECT COALESCE(i.id, o.id) as id,COALESCE(i.column1, o.column1) as column1,COALESCE(i.column2, o.column2) as column2,...
FROM original_table o
LEFT JOIN incremental_table i
ON o.id = i.id;

总结:全量表设计的优势是不会占用太多磁盘空间;弊端也很明显-不支持历史记录溯源

1.2、增量表

增量表是指只负责追加新的数据记录,而不负责历史数据更改记录,新的数据记录保存在新分区中,历史分区中的数据记录不发生变化。

增量表并不适用增量更新数据源,只适用于增量非更新数据源。

1.3、快照表

快照表是用来存储某个时间点的所有数据-通常粒度是天,相当于是对每天的业务数据做了一次快照,存储当天的全量数据!

  • 例如:快照表中某个分区内的数据是历史到此分区前一天的所有数据,如12号分区中的数据是从历史到11号的所有数据,13号分区中的数据是从历史到12号的所有数据,其他的以此类推。

1.3.1、场景

也可以理解为:每天将业务数据的全量数据存储至数仓-快照分区表的当天分区内,这里假设分区的粒度是天

以业务数据-用户表为例,2020-06-01有三个用户注册,表如下:

在这里插入图片描述

2020-06-02有一名用户注册,即新增了一名用户(标红),此时数仓中快照分区表更新后2020-06-02分区内会记录全量的数据,包括2020-06-01的用户数据(标绿),此时快照表如下:

在这里插入图片描述

同理,2020-06-03又有2名用户注册,即新增了1名用户(标蓝),此时数仓中快照分区表更新后2020-06-03分区内会记录全量数据,即包含2020-06-02的用户数据(标黄),此时快照表如下:

在这里插入图片描述

1.3.2、实现

先将业务数据全部同步至hive临时表中,随后将hive临时表的业务数据放置在快照分区表的今日分区内:

-- 创建一个新hive临时表,存放当日业务的全量数据
CREATE TABLE temp_table (col1 STRING, col2 INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/user/hive/warehouse/temp_table';-- 将全部的业务数据导入至hive临时表, 过程不赘述
insert into temp_table ..........-- 使用INSERT INTO语句将临时表中的数据插入到全量分区表的目标分区中:
INSERT INTO original_table PARTITION (date='2023-05-08')
SELECT * FROM temp_table;-- 删除hive临时表
DROP TABLE temp_table;

总结:此方式便是快照表,该设计的弊端很明显:会大量占用磁盘空间,故并不推荐使用

1.4、流水表

流水表是用于记录数据变更的表,理论上是对于表的每一个修改都会记录,但在实际应用中通常按天为粒度划分,例如流水表中的2017-01-02分区只记录这一天新增和修改的业务数据,这样可以方便地追溯、计算和分析历史数据,同时也提供了可靠的数据源供其他表和报告使用。

1.4.1、场景

还是用user用户数据举例,2017-01-01这一天表中的数据是:

![ref1]

此时流水表的数据为:

注册日期用户编号手机号码dt-时间分区字段
2017-01-010011111112017-01-01
2017-01-010022222222017-01-01
2017-01-010033333332017-01-01
2017-01-010044444442017-01-01

在2017-01-02这一天表中的数据是, 用户002和004资料进行了修改,005是新增用户:

在这里插入图片描述

此时流水表的数据为:

注册日期用户编号手机号码dt-时间分区字段
2017-01-010011111112017-01-01
2017-01-010022222222017-01-01
2017-01-010033333332017-01-01
2017-01-010044444442017-01-01
2017-01-010022333332017-01-02
2017-01-010044324322017-01-02
2017-01-020055555552017-01-02

1.4.2、实现

-- 创建一个user_update_temp临时表,用于存放当日业务的增量数据
CREATE EXTERNAL TABLE ods.user_update_temp (user_num STRING COMMENT '用户编号', mobile STRING COMMENT '手机号码', reg_date STRING COMMENT '注册日期'
)
COMMENT '每日用户资料更新表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user_update';-- 创建一张流水表,用来存储每天变化数据
CREATE EXTERNAL TABLE dws.user_stream (user_num STRING COMMENT '用户编号', mobile STRING COMMENT '手机号码', reg_date STRING COMMENT '注册日期'
)
COMMENT '用户流水表'
PARTITIONED BY (dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/dws/orders';-- 通过业务字段update_time将2017-01-02变化的业务数据导入至user_update_temp临时表,过程不赘述
INSERT OVERWRITE ods.user_update_temp .........-- 使用INSERT INTO语句将ods.user_update中的数据插入到流水表中2017-01-02分区中:
INSERT INTO dws.user_stream PARTITION (dt='2017-01-02')
SELECT * FROM ods.user_update_temp;

注意:流水表很容易和增量表的概念混淆,这里再强调一下:增量表只适用于增量非更新数据源,只负责新增数据,对于历史数据的修改并不记录;而流水表通常用于记录数据变更,包括新增、修改和删除等操作,以便跟踪每个事实或维度的历史变化。为了方便管理和查询,通常将流水表按时间分区。

1.5、拉链表

拉链表是一种维护历史状态以及最新状态数据的表。与快照表类似,算是在快照表的基础上去除了重复状态的数据,也就是一些不变的信息在快照表中每个分区都存储一份,使用拉链表在更新频率和比例不是很大的情况下会十分节省存储。

1.5.1、场景

现在以用户的拉链表来说明2017-01-01这一天表中的数据是:

在这里插入图片描述

在2017-01-02这一天表中的数据是, 用户002和004资料进行了修改,005是新增用户:

![ref1]

在2017-01-03这一天表中的数据是, 用户004和005资料进行了修改,006是新增用户:

![ref1]

如果在数据仓库中设计成历史拉链表保存该表,则会有下面这样一张表,这是最新一天(即2017-01-03)的数据:

![ref1]

说明

  • t_start_date表示该条记录的生命周期开始时间,t_end_date表示该条记录的生命周期结束时间
  • t_end_date = '9999-12-31’表示该条记录目前处于有效状态
  • t_end_date = '2017-01-02’表示该条记录在2017-01-02当日是有效的,在当前日期是无效的
  • 如果查询当前所有有效的记录,则select * from user where t_end_date = ‘9999-12-31’
  • 如果查询2017-01-02的历史快照,则select * from user where t_start_date <= ‘2017-01-02’ and t_end_date >= ‘2017-01-02’。(此处要好好理解,是拉链表比较重要的一块)
  • 解释上一条sql:需求是要查2017-01-02的历史快照,t_start_date是代表这条记录的开始时间,并非是原始数据的时间,例如001用户数据在2017-01-02也有效,故t_start_date <= ‘2017-01-02’;而t_end_date = '2017-01-02’表示该条记录在2017-01-02当日是有效的,又因为t_end_date = '9999-12-31’表示该条记录目前处于有效状态,所以t_end_date >= ‘2017-01-02’

该sql查询结果如下:

![ref1]

和下图2017-01-02的业务数据比较,结果完全一致:

![ref1]

1.5.2、实现

创建拉链表的前提是先根据全量数据表创建初始拉链表,然后再根据每天的增改数据进行合并更新拉链表;

还是以上面的用户表为例,我们要实现用户的拉链表,在实现它之前,我们需要先确定一下我们有哪些数据源可以用。

  1. 我们需要一张ODS层的用户全量表。至少需要用它来初始化。
  2. 流水表-记录每日增改数据。

建表语句:

-- 先创建一张全量表用于初始化
CREATE EXTERNAL TABLE ods.user (user_num STRING COMMENT '用户编号',mobile STRING COMMENT '手机号码',reg_date STRING COMMENT '注册日期'
) COMMENT '用户资料表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user';-- 最后我们创建一张拉链表:
CREATE EXTERNAL TABLE dws.user_his (user_num STRING COMMENT '用户编号',mobile STRING COMMENT '手机号码',reg_date STRING COMMENT '注册日期',t_start_date STRING COMMENT '资料开始日期',t_end_date STRING COMMENT '资料结束日期'
) COMMENT '用户资料拉链表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/dws/user_his';

数据初始化:我们以2017-01-01的数据作为初始化数据

-- 假设ods.user表已经存储了2017-01-01的全量数据,此时拉链表的初始化sql:
INSERT OVERWRITE TABLE dws.user_his
SELECT user_num, mobile, reg_date, '2017-01-01', '9999-12-31'
FROM ods.user;

初始化后的拉链表数据如下:

注册日期用户编号手机号码t_start_datet_end_date
2017-01-010011111112017-01-019999-12-31
2017-01-010022222222017-01-019999-12-31
2017-01-010033333332017-01-019999-12-31
2017-01-010044444442017-01-019999-12-31

数据更新:我们以2017-01-02的数据更新拉链表;

2017-01-02日的流水表数据如下:

注册日期用户编号手机号码dt-时间分区字段
2017-01-010011111112017-01-01
2017-01-010022222222017-01-01
2017-01-010033333332017-01-01
2017-01-010044444442017-01-01
2017-01-010022333332017-01-02
2017-01-010044324322017-01-02
2017-01-020055555552017-01-02
-- 2017-01-02拉链表更新sql:
INSERT OVERWRITE TABLE dws.user_his
SELECT *
FROM
(SELECT A.user_num,A.mobile,A.reg_date,A.t_start_date,CASEWHEN A.t_end_date = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-01'ELSE A.t_end_dateEND AS t_end_dateFROM dws.user_his AS ALEFT JOIN dws.user_stream AS B WHERE dt = '2017-01-02'ON A.user_num = B.user_num
UNIONSELECT C.user_num,C.mobile,C.reg_date,'2017-01-02' AS t_start_date,'9999-12-31' AS t_end_dateFROM dws.user_stream AS C WHERE dt = '2017-01-02'
) AS T;

更新后的拉链表数据如下:

注册日期用户编号手机号码t_start_datet_end_date
2017-01-010011111112017-01-019999-12-31
2017-01-010022222222017-01-012017-01-01
2017-01-010033333332017-01-019999-12-31
2017-01-010044444442017-01-012017-01-01
2017-01-010022333332017-01-029999-12-31
2017-01-010044324322017-01-029999-12-31
2017-01-02005555552017-01-029999-12-31
-- 查询有效数据:
select * from dws.user_his where t_end_date = '9999-12-31'

查询有效数据结果:

注册日期用户编号手机号码t_start_datet_end_date
2017-01-010011111112017-01-019999-12-31
2017-01-010033333332017-01-019999-12-31
2017-01-010022333332017-01-029999-12-31
2017-01-010044324322017-01-029999-12-31
2017-01-02005555552017-01-029999-12-31
-- 查询2017-01-01历史数据: 
select * from dws.user_his where t_start_date <= '2017-01-01' and t_end_date >= '2017-01-01'

2017-01-01历史数据:

注册日期用户编号手机号码t_start_datet_end_date
2017-01-010011111112017-01-019999-12-31
2017-01-010022222222017-01-012017-01-01
2017-01-010033333332017-01-019999-12-31
2017-01-010044444442017-01-012017-01-01

1.6、切片表

切片表根据基础表,往往只反映某一个维度的相应数据。其表结构与基础表结构相同,但数据往往只有某一维度,或者某一个事实条件的数据;切片表以某个维度或者一些特定的条件对事实进行汇总计算,并展示为一个交叉分析的表格。与事实表相比,切片表的数据更加聚合,只包含某些维度或者满足某些特定条件的数据。

1.6.1、场景

假设我们有一个基础表(也称为事实表),记录了一家公司的销售订单信息。该表包含以下字段:订单ID、客户ID、产品ID、销售日期、销售数量和销售额等。

订单ID客户ID产品ID销售日期销售数量销售额
1100120012022-01-013150
2100220022022-01-02280
3100320012022-01-03150
4100120032022-01-045250
5100220022022-01-054160

在这张表中,客户端ID、产品ID、销售日期是维度,而销售数量、销售额是事实。

现在我们希望按照客户维度创建一个切片表,以便分析每个客户的销售情况。

具体来说,我们需要选择客户维度,并对销售数量和销售额这两个度量进行聚合计算,通过多维分析工具或者SQL查询,可以生成如下的切片表:

客户ID销售数量总计销售额总计
10018400
10026240
1003150

在这个切片表中,我们只选择了客户维度,然后,我们使用SUM函数对每个客户的销售数量和销售额进行聚合计算,以便更好地分析不同客户之间的销售情况。

1.6.2、实现

在数据仓库中,切片表的存储方式可以根据不同的需求和性能要求而定,一般来说有以下两种常用的存储方式:

  1. 全量替换:每次运行ETL作业时,都会重新生成整个切片表,并将其覆盖原有的数据。这种方式适用于数据量较小、更新频率低的场景,或者需要保证数据完整性和一致性的场景。
  2. 按照天的粒度进行划分:将切片表按照时间维度(如日、周、月等)进行分区,每个分区存储一段时间内的数据。这种方式适用于数据量较大、更新频率高的场景,或者需要快速查询历史数据的场景。

无论采用哪种存储方式,都需要考虑切片表的设计问题。具体来说,以下是一些设计上的注意点:

  1. 维度表的设计:维度表应该是离线化的静态数据,它们应该是变化不大并且计算时被缓存在内存中,以便提高查询性能。因此,在设计维度表时,应该尽可能地避免经常变化的属性。
  2. 表结构的优化:为了提高查询效率,应该尽可能地减少JOIN操作,尽量将不同维度的数据存储在同一个表中。如果必须进行JOIN操作,则应该尽可能地使用分区表或者基于列存储的数据库,以便提高查询性能。
  3. 时间分区:如果采用按照天的粒度进行划分的方式,那么需要将切片表按照时间维度进行分区,并且使用分区键作为查询条件。这样可以大大提高查询性能,同时也方便进行数据备份和恢复。

总之,在设计和实现切片表时,需要考虑不同的因素,包括数据量、更新频率、查询性能、数据一致性等等,以便得出最优的解决方案。

这里以全量替换举例:

-- 按照上面的例子,假设有一个名为sales_order的基础表
CREATE TABLE sales_order (order_id int,customer_id int,product_id int,sale_date date,quantity int,amount double
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE;-- 我们可以将原始数据加载到sales_order表中
LOAD DATA INPATH 'path/to/sales.csv' OVERWRITE INTO TABLE sales_order;-- 接下来,我们可以使用INSERT INTO SELECT语句来创建切片表customer_sales,并将数据按照客户维度进行聚合计算:
CREATE TABLE customer_sales (customer_id int,total_quantity int,total_amount double
)
AS
SELECT customer_id, SUM(quantity) as total_quantity, SUM(amount) as total_amount 
FROM sales_order 
GROUP BY customer_id;-- 这里采用了全量替换的方式,每次重新运行上面的SQL语句都会完全替换customer_sales表中的数据。

二、增量非更新数据源

当数据源只允许新增数据,不允许对历史数据进行修改时,我们称之为增量不更新数据源。这种数据源的主要特点是:

  1. 数据只能增长:无法对历史数据进行修改、删除或覆盖,只能追加新的数据。
  2. 数据变更历史保留:由于数据源只追加新的数据,因此可以完整地保留数据的变更历史,便于后续的分析和回溯。
  3. 数据量大:由于数据只能增长,因此数据量通常会随着时间的推移而不断增加。
  4. 实时性高:由于数据是实时生成的,具有很强的实时性和即时性。

常见的增量不更新数据源包括:

  1. 日志数据:日志数据是指记录了系统或应用程序操作的信息的数据。由于日志数据一般是只追加不修改,所以可以视为增量不更新数据源。
  2. 传感器数据:传感器数据是指由各种传感器采集的环境、设备或系统状态数据等。这些数据也是只追加不修改的,因此可以视为增量不更新数据源。
  3. 消息系统数据:消息系统数据是指通过消息队列、主题或流处理框架等收集的异步消息数据。与日志数据和传感器数据类似,这些数据也是只追加不修改的。
  4. 用户活动数据:用户活动数据是指记录了用户在应用程序中进行的各种操作和行为的数据。由于这些数据通常无法修改,因此也可以视为增量不更新数据源。

在设计数据模型和存储方案时,需要考虑到数据源的特点,以便满足快速查询和分析的需求,一般来说,可以采用以下策略:

  1. 使用列式存储:由于增量不更新数据源的数据一般只用于查询和分析,因此可以采用列式存储方式,以提高查询性能。
  2. 考虑分区:根据数据源的特点,可以将数据按照时间、地理位置等因素进行分区,以便更快地查询历史数据。
  3. 超大表设计:由于增量不更新数据源的数据量很大,因此需要考虑超大表的设计问题。例如,可以使用分区表、分布式存储等技术来支持数据的高效存储和查询。

2.1、全量表

对于增量不更新数据源,使用全量表进行处理可能会导致性能问题和资源浪费,因为全量表需要重新加载所有数据,并进行完全替换,这将消耗大量的计算资源和时间。

相反,使用增量表可以更有效地处理增量不更新数据源,因为增量表只包含最近追加的数据,并且每次只需要更新最新的增量数据即可。这样,可以避免重复加载和处理历史数据,从而提高处理效率和减少资源消耗。

2.2、增量表

增量表可以高效地处理增量不更新数据源,因为增量表只包含最新的新增数据,并且每次只需要更新最新的增量数据即可。这样,可以避免重复加载和处理历史数据,从而提高处理效率和减少资源消耗。

增量表通常按天为粒度

2.2.1、场景

假设我们有一个传感器监测系统,每个传感器会在固定时间间隔内生成一些数据。这些数据在产生后不会被更改。我们需要设计一个增量表来处理这些传感器数据,并按天分区管理数据。

原始数据格式展示:

字段名称数据类型说明
sensor_idINT传感器ID
timestampTIMESTAMP时间戳
valueFLOAT数据值

14号原始数据:

sensor_idtimestampvalue
12023-05-14 10:00:0023.5
22023-05-14 10:00:0018.2
32023-05-14 10:00:0025.0
42023-05-14 11:00:0024.0
52023-05-14 11:00:0018.4

此时我们设计一个名为sensors_incremental的增量表,用于存储每天新增的传感器数据,并按照日期进行分区。该表包含了四个字段:sensor_id、timestamp、value和dt(时间分区字段)。

增量表初始数据展示如下:

sensor_idtimestampvaluedt(时间分区字段)
12023-05-14 10:00:0023.52023-05-14
22023-05-14 10:00:0018.22023-05-14
32023-05-14 10:00:0025.02023-05-14
42023-05-14 11:00:0024.02023-05-14
52023-05-14 11:00:0018.42023-05-14

第二天原始数据如下:

sensor_idtimestampvalue
12023-05-14 10:00:0023.5
22023-05-14 10:00:0018.2
32023-05-14 10:00:0025.0
32023-05-14 11:00:0024.0
52023-05-14 11:00:0018.4
62023-05-15 11:00:0025.2
72023-05-15 12:00:0023.8
82023-05-15 12:00:0018.6
92023-05-15 12:00:0025.5

第二天增量表sensors_incremental数据:

sensor_idtimestampvaluedt(时间分区字段)
12023-05-14 10:00:0023.52023-05-14
22023-05-14 10:00:0018.22023-05-14
32023-05-14 10:00:0025.02023-05-14
42023-05-14 11:00:0024.02023-05-14
52023-05-14 11:00:0018.42023-05-14
62023-05-15 1100:0025.22023-05-15
72023-05-15 12:00:0023.82023-05-15
82023-05-15 12:00:0018.62023-05-15
92023-05-15 12:00:0025.52023-05-15

2.2.2、实现

-- 创建增量表
CREATE TABLE sensors_incremental (sensor_id INT,timestamp TIMESTAMP,value FLOAT
)
PARTITIONED BY (day DATE)
STORED AS PARQUET;-- 将原始数据加载到sensors_temp临时表中
LOAD DATA INPATH 'path/to/sensors.csv' OVERWRITE INTO TABLE sensors_temp;-- 将临时表数据初始化加载进增量表
INSERT INTO sensors_incremental PARTITION (date='2023-05-14') SELECT * FROM sensors_temp;-- 第二天将15号临时表数据加载进增量表
INSERT INTO sensors_incremental PARTITION (date='2023-05-15') SELECT * FROM sensors_temp;

通过使用以上增量表设计和相应的SQL语句,我们可以有效地处理传感器数据,并实现按天分区的管理和查询。

2.3、快照表

快照表同样适用于增量非更新数据源,快照表是用来存储某个时间点的所有数据-通常粒度是天,相当于是对每天的业务数据做了一次快照,存储当天的全量数据!

其设计和实现与增量更新数据源保持一致,详细参考:1.1.3、快照表

2.4、流水表

流水表同样适用于增量不更新数据源,流水表是用于记录数据变更的表,流水表如果是按天为粒度划分,那和增量表几乎一模一样,因为不涉及到数据的修改,故增量非更新数据源下的流水表和增量表的数据几乎保持一致。

其设计和实现与增量更新数据源保持一致,详细参考:1.1.4、流水表

2.5、拉链表

拉链表同样适用于增量不更新数据源,其设计和实现与增量更新数据源保持一致,详细参考:1.1.5、拉链表

2.6、切片表

切片表是根据事实数据生成的维度表,故同样适用于增量不更新数据源,其设计和实现与增量更新数据源保持一致,详细参考:1.1.6、切片表

三、总结

增量表、全量表、快照表、拉链表、切片表和流水表都是常用的数据表格设计方式,每种表格设计方式都有自己的优劣势和适用场景。

  • 增量表:只记录新增或更改操作的数据表,可以减少存储和处理开销,适用于高频产生新增事件的情况。
  • 全量表:记录所有数据的完整表格,可以提供准确的数据,但无法提供历史溯源记录
  • 快照表:记录某个时间点的部分或全部数据,可用于数据快照和查询。通常使用在查询性质的报表系统中。
  • 拉链表:记录数据变化的时间范围。适用于需要准确跟踪数据变化的场景,例如会计系统。
  • 切片表:记录具有多维度属性的实体,例如客户、产品等,适用于类似于分析型场景。
  • 流水表:记录所有事件(包括新增、删除、修改等),可以追溯完整的历史信息,适用于审计和数据分析以及为其他类型的表提供数据源

总体而言,以上的表格设计方式都有其自身的优缺点以及适用场景,应根据具体的业务需求进行合理选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/6582.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

练习——动态内存分配的笔试题

今天我们分享几道经典的笔试题&#xff0c;做完直接变成陈泽 第一题 ~~ --------------------------------------------------------------------------------------------------~~ void GetMemory(char* p) {p (char*)malloc(100); } void Test(void) {char* str NULL;Get…

阿里云容器镜像仓库(ACR)的创建和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

使用spark进行hbase的bulkload

使用spark进行hbase的bulkload 一、 背景 HBase 是一个面向列&#xff0c;schemaless&#xff0c;高吞吐&#xff0c;高可靠可水平扩展的 NoSQL 数据库&#xff0c;用户可以通过 HBase client 提供的 put get 等 api 实现在数据的实时读写。在过去的几年里&#xff0c;HBase …

HTTP 请求走私漏洞(HTTP Request Smuggling)

一、什么是Http 请求走私漏洞&#xff1f; HTTP请求走私漏洞&#xff08;HTTP Request Smuggling&#xff09;是一种安全漏洞&#xff0c;利用了HTTP协议中请求和响应的解析和处理方式的不一致性。攻击者通过构造特定的恶意请求&#xff0c;以欺骗服务器和代理服务器&#xff0…

Godot 4 源码分析 - 增加管道通信

学习研究Godot 4&#xff0c;很爽&#xff0c;虽然很庞杂&#xff0c;但相对于自己的水平来说&#xff0c;很强大&#xff0c;尤其是vulkan这块直接打包可用&#xff0c;省得自己从头琢磨。 一点一点地消化、优化与完善&#xff0c;最终才能成为自己的。 这段时间就在Godot的…

Pytorch迁移学习使用Resnet50进行模型训练预测猫狗二分类

目录 1.ResNet残差网络 1.1 ResNet定义 1.2 ResNet 几种网络配置 1.3 ResNet50网络结构 1.3.1 前几层卷积和池化 1.3.2 残差块&#xff1a;构建深度残差网络 1.3.3 ResNet主体&#xff1a;堆叠多个残差块 1.4 迁移学习猫狗二分类实战 1.4.1 迁移学习 1.4.2 模型训练 1.…

华为数通HCIP-ISIS基础

IS-IS的基本概念 isis&#xff08;中间系统到中间路由协议&#xff09; 链路状态路由协议、IGP、无类路由协议&#xff1b; IS-IS是一种链路状态路由协议&#xff0c;IS-IS与OSPF在许多方面非常相似:运行IS-IS协议的直连设备之间通过发送Hello报文发现彼此&#xff0c;然后建…

从零开始搭建vue3 + ts + pinia + vite +element-plus项目

前言&#xff1a;据说vue2将于 2023 年 12 月 31 日停止维护&#xff0c;最近打算搭建一个vue3项目来学习一下&#xff0c;以防忘记&#xff0c;记录一下搭建过程。 一、使用npm创建项目 前提条件&#xff1a;已安装 16.0 或更高版本的 Node.js 执行 “npm init vuelatest”…

【Java基础教程】(四十三)多线程篇 · 下:深入剖析Java多线程编程:同步、死锁及经典案例——生产者与消费者,探究sleep()与wait()的差异

Java基础教程之多线程 下 &#x1f539;本节学习目标1️⃣ 线程的同步与死锁1.1 同步问题的引出2.2 synchronized 同步操作2.3 死锁 2️⃣ 多线程经典案例——生产者与消费者&#x1f50d;分析sleep()和wait()的区别&#xff1f; &#x1f33e; 总结 &#x1f539;本节学习目标…

谷歌插件(Chrome扩展) “Service Worker (无效)” 解决方法

问题描述&#xff1a; 写 background 文件的时候报错了&#xff0c;说 Service Worker 设置的 background 无效。 解决&#xff08;检查&#xff09;方法&#xff1a; 检查配置文件&#xff08;manifest.json&#xff09; 中的 manifest_version 是否为 3。 background 中的…

如何动态修改 spring aop 切面信息?让自动日志输出框架更好用

业务背景 很久以前开源了一款 auto-log 自动日志打印框架。 其中对于 spring 项目&#xff0c;默认实现了基于 aop 切面的日志输出。 但是发现一个问题&#xff0c;如果切面定义为全切范围过大&#xff0c;于是 v0.2 版本就是基于注解 AutoLog 实现的。 只有指定注解的类或…

DataWhale AI夏令营——机器学习

DataWhale AI夏令营——机器学习 学习记录一1. 异常值分析2. 单变量箱线图可视化3. 特征重要性分析 学习记录一 锂电池电池生产参数调控及生产温度预测挑战赛 已配置环境&#xff0c;跑通baseline&#xff0c;并在此基础上对数据进行了简单的分析。 1. 异常值分析 对训练集…

K8S初级入门系列之八-网络

一、前言 本章节我们将了解K8S的相关网络概念&#xff0c;包括K8S的网络通讯原理&#xff0c;以及Service以及相关的概念&#xff0c;包括Endpoint&#xff0c;EndpointSlice&#xff0c;Headless service&#xff0c;Ingress等。 二、网络通讯原理和实现 同一K8S集群&…

PMP 数据收集工具与技术

数据收集工具与技术 (9个) 标杆对照 标杆对照是指将实际或计划的产品、流程和实践与其他可比组织的 做法进行比较&#xff0c;以便识别最佳实践、形成改进意见&#xff0c;并为绩效考核 提供依据。 头脑风暴 头脑风暴是一种数据收集和创意技术&#xff0c;主要用于在短时间…

三维点云中的坐标变换(只讲关键部分)

一、坐标旋转 坐标旋转包含绕x、y、z轴旋转&#xff0c;在右手坐标系中&#xff0c;x-翻滚(roll)&#xff0c;y-俯仰(pitch)&#xff0c;z-航向(yaw)。如果想详细了解&#xff0c;可以网络搜索 在PCL中&#xff0c;从baseLink到map的转换关系为:先绕x轴旋转,在绕y轴旋转,最后绕…

【软件工程中的各种图】

1、用例图&#xff08;use case diagrams&#xff09; 【概念】描述用户需求&#xff0c;从用户的角度描述系统的功能 【描述方式】椭圆表示某个用例&#xff1b;人形符号表示角色 【目的】帮组开发团队以一种可视化的方式理解系统的功能需求 【用例图】 2、静态图(Static …

【数据结构】C--单链表(小白入门基础知识)

前段时间写了一篇关于顺序表的博客&#xff0c;http://t.csdn.cn/0gCRp 顺序表在某些时候存在着一些不可避免的缺点: 问题&#xff1a; 1. 中间 / 头部的插入删除&#xff0c;时间复杂度为 O(N) 2. 增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不…

前端 | ( 十一)CSS3简介及基本语法(上) | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 系列笔记&#xff1a; 【HTML4】&#xff08;一&#xff09;前端简介【HTML4】&#xff08;二&#xff09;各种各样的常用标签【HTML4】&#xff08;三&#xff09;表单及HTML4收尾…

2023/07/23

1. 必须等待所有请求结束后才能执行后续操作的处理方式 方式一&#xff1a; async func () {const p1 await api1();const p2 await api2();const p3 await api3();Promise.all([p1, p2, p3]).then(res > {后续操作...}) }方式二&#xff1a;待补充 2. flex 弹性盒子布…

FPGA实现串口回环

文章目录 前言一、串行通信1、分类1、同步串行通信2、异步串行通信 2、UART串口通信1、UART通信原理2、串口通信时序图 二、系统设计1、系统框图2.RTL视图 三、源码1、串口发送模块2、接收模块3、串口回环模块4、顶层模块 四、测试效果五、总结六、参考资料 前言 环境&#xff…