在当今数据驱动の世界 (shì jiè 世界) 中,组织越来越依赖数据分析来获取 valuable insights(宝贵见解)并做出明智决策。数据建模在此过程中发挥着重要作用,为构建和组织数据以支持有效分析提供坚实的基础。此外,理解数据建模和归一化的概念对于充分发挥分析潜力并从复杂数据集获得可操作的洞察力至关重要。
数据建模是指定义系统内数据实体的结构、关系和属性。数据建模的一个重要方面是数据的归一化。数据归一化是一种消除数据冗余并改善数据完整性的技术。它涉及将数据分解成逻辑单元并组织到单独的表中,从而减少数据重复并提高整体数据库效率。归一化确保数据以结构化和一致的方式存储,这对于准确的分析和可靠的结果至关重要。
在分析方面,数据建模为创建分析模型提供了坚实的基础。通过理解实体和数据结构之间的关系,分析师可以设计有效的模型来捕获相关信息并支持所需的分析目标。换句话说,精心设计的数据模型使分析师能够执行复杂查询、联接表和汇总数据以产生有意义的洞察力。
理解数据建模和归一化对于实际数据分析至关重要。如果没有适当的数据模型,分析师可能难以访问和正确解释数据,从而导致错误的结论和无效的决策。此外,缺乏归一化会导致数据异常、不一致以及汇总数据困难,从而阻碍分析过程。
本书强调 SQL 和 dbt 是维持高效数据分析工程项目的两项核心技术,这同样适用于设计和实现高效的数据模型。原因在于,SQL 通过强大的查询功能使用户能够定义表格、处理数据以及检索信息。它无与伦比的灵活性和通用性使其成为构建和维护数据模型的出色工具,使用户能够描述复杂的关系并轻松访问特定数据子集。
作为 SQL 的补充,dbt 在这一叙述中扮演着核心角色,使数据建模上升到一个全新的水平。它作为一个构建和编排复杂数据管道的综合框架。在该框架内,用户可以定义转换逻辑、应用必要的业务规则,并创建可重用的模块化代码组件(称为模型)。值得注意的是,dbt 不仅仅是独立的功能:它可以无缝集成版本控制系统,使协作变得轻而易切,并确保数据模型保持一致性、可审计性和轻松的可重现性。
SQL 和 dbt 在数据建模中的另一个重要方面是它们都强调测试和文档,尽管两者之间存在着一些值得澄清的区别。在数据建模的背景下,测试涉及验证数据模型的准确性、可靠性以及对业务规则的遵循程度。需要注意的是,dbt 的测试功能不同于软件开发中的传统单元测试,但它们具有类似的目的。dbt 不是提供传统单元测试,而是提供与分析师习惯运行的验证查询相类似的验证查询。这些验证查询检查数据质量、数据完整性和对定义规则的遵循情况,从而对模型的输出充满信心。此外,dbt 还擅长文档编制,是分析师和利益相关者 alike(同样)的宝贵资源。这些文档简化了对驱动数据模型的底层逻辑和假设的理解,从而提高透明度并促进有效的协作。
总之,SQL 和 dbt 使数据专业人员能够创建健壮、可扩展且可维护的数据模型,从而驱动深入的分析和明智的决策制定。通过利用这些工具,组织可以充分挖掘数据潜力,推动创新并在当今数据驱动的格局中获得竞争优势。在相同的数据架构和策略中结合两者可以为数据建模带来显著的优势。
数据建模简介
在数据库设计领域,创建一个结构化和组织良好的环境对于有效存储、操作和利用数据至关重要。数据库建模通过提供表示特定现实或业务及其流程和规则的蓝图,在实现这一目标方面发挥着重要作用。
然而,在深入创建此蓝图之前,我们应该专注于理解业务的细微差别。
为了创建准确且有意义的数据模型,理解业务的运营、术语和流程至关重要。我们可以通过访谈、文档分析和流程研究来收集需求,从而深入了解业务的需求和数据要求。在此收集过程中,我们应该专注于自然的沟通方式 - 书面语言。通过用明确的句子表达业务事实,我们可以确保对业务的描述准确无误,避免歧义。将复杂的句子分解成包含主语、谓语和直接宾语的简单结构,有助于简洁地捕捉业务现实。
除了这些基本做法之外,值得注意的是,该领域的专家(例如 Lawrence Corr 在其畅销书《敏捷数据仓库设计》(DecisionOne Press) 中)主张在数据模型设计的初始阶段采用更多技术,例如白板和调研问卷调查。这些额外的策略可以为过程增添细节,从而更全面地探索业务需求,并确保生成的数据库模型与业务目标和细微之处无缝对齐。
一旦理解阶段完成,我们将进入数据库建模的三个基本步骤:
- 概念阶段 (Conceptual phase)
- 逻辑阶段 (Logical phase)
- 物理阶段 (Physical phase)
这些步骤构成创建健壮且组织良好的数据库结构的旅程。让我们通过一家图书出版商的例子来阐述这个过程。
数据建模的概念阶段
数据库建模的概念阶段是构建数据库蓝图的第一步。在这个阶段,我们将重点放在理解业务需求和定义数据模型的基本结构上,而不考虑具体的技术实现细节。
主要目标
概念阶段的主要目标包括:
- 识别业务实体及其属性
- 定义实体之间的关系
- 确定数据模型的粒度
- 创建概念数据模型
关键步骤
为了实现这些目标,我们可以采取以下步骤:
- 确定数据库的目的和目标:首先需要明确数据库的用途和目标,以及它需要解决的具体问题或需求。
- 收集需求:通过访谈利益相关者和主题专家来全面了解所需的数据元素、关系和约束。
- 实体分析和定义:识别将在数据库中表示的关键对象或概念,并定义它们的属性和关系。
- 轻度规范化:在设计数据库初始草图时进行轻度规范化,以确保已识别实体和关系之间的完整性,并通过围绕概念相关的语义结构组织实体和属性来最小化冗余。
- 识别键:识别主键和外键等关键字段,对于维护表之间的唯一性和建立关系至关重要。
概念模型的表示方法
数据库设计通常通过图表、文本描述或其他方法来创建,这些方法可以有效地捕获和传达数据库的设计和概念。
- 实体关系图 (ERD) 是用于可视化表示数据库概念的最常用工具之一。使用 ERD 创建的可视化模型可以作为图表化表示,有效地描述要建模的实体、它们之间的关系以及这些关系的基数。
- 通过使用 ERD 模型,我们可以直观地描述数据库结构,包括作为主要组件的实体、实体之间的连接或关联以及关系的数量或程度。
案例:图书出版商的数据库概念设计
让我们通过一个非常简单的数据库概念设计示例来理解这些步骤:
假设 O'Reilly 出版社想要跟踪以前出版的书籍和作者,以及尚未出版的新书的发布日期。我们与出版商的经理进行了一系列访谈,开始准确理解需要存储在数据库中的数据。主要目标是识别涉及的实体、它们的關係以及每个实体的属性。请注意,此练习仅用于说明目的,并进行了适当的简化。
我们在图书管理的子集中识别了三个不同的实体:
- 图书 (Book)
此实体表示由 O'Reilly 出版的书籍。属性可能包括图书编号 (book_id)、标题 (title)、出版日期 (publication_date)、ISBN 号 (ISBN)、价格 (price) 和特定类别 (category)。访谈者表示,在这个模型中,一本图书只能属于一个类别。
- 作者 (Author)
此实体代表为 O'Reilly 撰写书籍的作者。属性可能包括作者编号 (author_id)、作者姓名 (author_name)、电子邮件 (email) 和简介 (bio)。
- 类别 (Category)
此实体表示图书类别,可以包含类别编号 (category_id) 作为唯一标识符和类别名称 (category_name) 等属性。
识别实体及其属性之后,下一步是识别实体之间的关系。在数据库设计中,实体之间存在几种类型的关系,关系的类型可以称为关系的基数 (cardinality)。
例如,在 一对一 (one-to-one) 的关系中,我们可以将一个图书实体连接到一个作者实体,其中每本书都与单个作者相关联,反之亦然。
在 一对多 (one-to-many) 的关系中,可以考虑将类别实体链接到图书实体,每本书只能属于一个类别,但每个类别可以有多本书。
相反,在 多对一 (many-to-one) 的关系中,可以想到一个出版商实体连接到一个图书实体,同一个出版商可以出版多本书。
最后,在 多对多 (many-to-many) 的关系中,我们可以将图书实体与读者实体相关联,表示多个读者可以拥有多本书。
继续我们的练习,我们还确定了两个清晰的关系:
-
图书-类别 (Book-Category) 关系:
- 建立书籍和类别之间的联系。一本书可以属于一个类别,一个类别可以包含多本书。这种关系表示为一对多的关系。
-
图书-作者 (Book-Author) 关系:
- 建立书籍和作者之间的联系。一本书可以有多个作者,一个作者可以写多本书。这种关系表示为多对多的关系。正是在这种关系中,特定书籍的出版才会发生。
识别关系时,通常使用代表实体之间真实交互的关系名称。例如,我们可以不使用 BookCategory 而使用 Classifies,因为类别对图书进行分类;或者不使用 Book-Author 而使用 Publishes,因为作者出版图书。
既然我们已经了解了实体、属性和关系,那么我们就可以使用实体关系图 (ERD) 来设计数据库。通过这样做,我们可以可视化地表示实体、关系和基数,如图 2-1所示。
正如我们所观察到的,实体关系图 (ERD) 中使用以下组件可视化表示数据库的结构:
-
实体 (Entity): 用矩形框表示,代表真实世界的对象或概念,例如图书 (Book) 或作者 (Author)。
-
关系 (Relationship): 用菱形表示,展示实体之间的关联方式。
-
属性 (Attribute): 用阴影框表示,描述实体的属性或特征,例如名称 (Name) 或出版日期 (Publish Date)。属性可以进一步分类为:
- 键属性 (Key Attribute): 下划线标识的阴影框,用于唯一标识实体。
- 非键属性 (Non-Key Attribute): 非下划线标识的阴影框,提供有关实体的其他信息。
- 在设计此类图表时还存在其他类型的属性,但此处仅介绍基础部分。
-
基数 (Cardinality): 定义关系中实例的数量,通常使用符号 1、M 或 N 表示。
- 1: 表示一对一的关系
- M: 表示一对多的关系 (一个实体对应多个实体)
- N: 表示多对多的关系 (多个实体对应多个实体) 例如,一个作者可以写多本书 (M),而一本书可以有多个作者 (M)。
需要注意的是,ERD 中的符号可能略有差异,但基本含义保持一致。
数据库建模的逻辑阶段
数据库建模的逻辑阶段侧重于规范化数据以消除冗余、提高数据完整性并优化查询性能。
该阶段会生成一个标准化逻辑模型,该模型准确反映实体之间的关系和依赖性。 逻辑阶段可以分为两个步骤:
-
实体关系模式的重构:
- 基于特定标准优化模式。
- 此步骤不依赖于任何特定的逻辑模型。
-
将优化的 ERD 转换为特定逻辑模型:
- 假设我们已决定将 ERD 映射到关系数据库模型(这将是我们的案例),而不是文档或图形数据库,那么概念 ERD 练习中的每个实体都将表示为一个表。
- 每个实体的属性成为相应表的列。
- 主键约束会指示每个表的主键列。
- 此外,多对多关系由单独的连接表表示,该表包含引用对应实体的外键。
通过将概念 ERD 练习转换为使用关系模型的逻辑模式,我们建立了实体、其属性及其关系的结构化表示。 逻辑模式可以作为在特定数据库管理系统 (DBMS) 中实现数据库的基础,同时独立于任何特定系统。
为了有效地实现这种转换,需要应用所有规范化步骤。这里提供一种有效的算法:
- 实体 E 转换为表 T
- E 的名称成为 T 的名称
- E 的主键成为 T 的主键
- E 的简单属性成为 T 的简单属性
对于关系,我们也可以分享一些步骤:
N:1 关系
- 在表 T1 中定义外键,引用表 T2 的主键。 这会建立两个表之间的连接,指示 N:1 关系。
- 与关系相关的属性 (Attrs) 被映射并包含在表 T1 中。
N:N 关系
- 创建一个特定的交叉引用表来表示关系 REL。
- REL 的主键被定义为表 T1 和 T2 的主键组合,它们在交叉引用表中充当外键。
- 与关系相关的属性 (Attrs) 被映射并包含在交叉引用表中。
现在让我们将这些规则应用于我们之前的概念模型;见图 2-2。
在我们的示例中,根据算法的建议,一些实体可以直接映射到表。作者 (Authors)、图书 (Books) 和类别 (Category) 就是这种情况。
我们识别了图书和类别之间的一对多 (1:N) 关系,即一本书属于一个类别,但一个类别可以包含多本书。为了映射这种关系,我们在图书表中创建一个外键来引用对应的类别表。
我们还存在一个多对多 (N:N) 关系。在这种情况下,我们必须创建一个新表(交叉引用表)来存储该关系。在本例中,我们创建了 Publishes 表,其主键由相关实体(图书编号和作者编号)的组合构成。同时,关系的属性也变成了这个交叉引用表的属性。
数据库建模的物理阶段
现在,我们将通过物理阶段(又称为物理模型创建)将标准化逻辑模型转换为物理数据库设计。此步骤定义存储结构、索引策略和数据类型,以确保高效的数据存储和检索。逻辑模型侧重于概念表示,而物理模型则处理平滑数据管理所需的实现细节。
在我们的案例中,让我们从之前的逻辑模型继续,假设我们将使用 MySQL 数据库引擎。示例 2-1 展示了图书数据库的物理模型。
示例 2-1. 物理模型中的图书数据库
CREATE TABLE category (
category_id INT PRIMARY KEY,
category_name VARCHAR(255)
);
CREATE TABLE books (
book_id INT PRIMARY KEY,
ISBN VARCHAR(13),
title VARCHAR(50),
summary VARCHAR(255),
FOREIGN KEY (category_id) REFERENCES category(category_id)
);
CREATE TABLE authors (
author_id INT PRIMARY KEY,
author_name VARCHAR(255),
date_birth DATETIME
);
CREATE TABLE publishes (
book_id INT,
author_id INT,
publish_date DATE,
planned_publish_date DATE,
FOREIGN KEY (book_id) REFERENCES books(book_id),
FOREIGN KEY (author_id) REFERENCES author(author_id)
);
示例 2-1 中,我们创建了四个表:类别 (category)、图书 (books)、作者 (authors) 和 出版 (publishes)。物理设计方面会微调表结构、数据类型和约束以适应 MySQL 数据库系统。例如,在类别表中,我们可以将 category_id 列的数据类型指定为 INT,确保它适合存储整数值,同时将其定义为主键,因为它标识表上的唯一记录。类似地,category_name 列可以定义为 VARCHAR(255) 以容纳可变长度的类别名称。
在图书表中,可以为 book_id (INT)、ISBN (VARCHAR(13))、title (VARCHAR(50)) 和 summary (VARCHAR(255)) 等列分配适当的数据类型和长度。此外,category_id 列可以配置为外键,引用类别表中的 category_id 列。请注意,每个 ISBN 代码由 13 个字符长度的字符串组成。因此,我们不需要比这更大的字符串。类似地,在作者表中,可以为 author_id (INT)、author_name (VARCHAR(255)) 和 date_birth (DATETIME) 等列定义数据类型,所有这些都遵循预期的值域。
在 publishes 表中,我们强调定义了外键约束来建立图书表中的 book_id 列和作者表中的 author_id 列之间的关系。同时,外键由它所关联的两个表的的主键组成。
经过所有这些步骤,我们已经成功地从需求转移到概念,再到逻辑关系模型,并最终使用 MySQL 完成模型的实际实现,从而构建了我们的数据库。
数据规范化过程
数据规范化技术包含几个步骤,每个步骤都旨在将数据组织成逻辑高效的结构。示例 2-2 展示了一个包含一些相关属性的图书表。
示例 2-2. 需要规范化的图书表
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(100),
author VARCHAR(100),
publication_year INT,
genre VARCHAR(50)
);
1. 第一范式 (1NF)
数据规范化的第一步称为第一范式 (1NF),它要求消除重复的组,并将数据分解成更小的原子单元。我们将创建一个包含作者编号和作者姓名的“authors”表。现在,图书表不再重复存储完整的姓名,而是引用作者编号,如示例 2-3 所示。
示例 2-3. 1NF 的图书表
-- 作者表
CREATE TABLE authors (
author_id INT PRIMARY KEY,
author_name VARCHAR(100)
);
-- 图书表
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(100),
publication_year INT,
genre VARCHAR(50),
author_id INT,
FOREIGN KEY (author_id) REFERENCES authors(author_id)
);
2. 第二范式 (2NF)
进入第二范式 (2NF) 时,我们检查数据中的依赖关系。我们观察到出版年份在功能上依赖于图书编号,而类型则依赖于作者编号。为了遵循 2NF,我们将图书表拆分为三个表:
- books:包含图书编号和标题
- authors:包含作者编号和姓名
- bookDetails:存储图书编号、出版年份和类型
这确保了每一列都只依赖于主键,如示例 2-4 所示。
示例 2-4. 2NF 的图书表
-- 作者表
CREATE TABLE authors (
author_id INT PRIMARY KEY,
author_name VARCHAR(100)
);
-- 图书表
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(100)
);
-- 图书详细信息表
CREATE TABLE bookDetails (
book_id INT PRIMARY KEY,
author_id INT,
genre VARCHAR(50),
publication_year INT,
FOREIGN KEY (author_id) REFERENCES authors(author_id)
);
3. 第三范式 (3NF)
第三范式 (3NF) 侧重于消除传递依赖。我们意识到类型可以通过 bookDetails 表从图书编号中推导出来。为了解决这个问题,我们创建一个新的“genres”表,其中包含类型编号和类型名称,bookDetails 表现在引用类型编号,而不是直接存储类型名称 (示例 2-5)。
示例 2-5. 3NF 的图书表
CREATE TABLE authors (
author_id INT PRIMARY KEY,
author_name VARCHAR(100)
);
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(100)
);
CREATE TABLE genres (
genre_id INT PRIMARY KEY,
genre_name VARCHAR(50)
);
CREATE TABLE bookDetails (
book_id INT PRIMARY KEY,
author_id INT,
genre_id INT,
publication_year INT,
FOREIGN KEY (author_id) REFERENCES authors(author_id),
FOREIGN KEY (genre_id) REFERENCES genres(genre_id)
);
通过遵循这些范式,我们可以创建更简洁、更有效的数据库结构,从而改善数据完整性、减少冗余并简化数据操作。
通过遵循这些范式(3NF),我们可以创建更简洁、更有效的数据库结构,从而改善数据完整性、减少冗余并简化数据操作。这些最终的规范化结构 (3NF) 通常用于操作型系统(也称为在线事务处理 (OLTP) 系统)中,这些系统旨在高效地处理和存储事务并检索事务数据,例如客户订单、银行交易甚至工资单。重要的是强调,如果需要,我们可以应用进一步的规范化步骤,例如第四范式 (4NF) 和第五范式 (5NF),以解决复杂的数据依赖关系并确保更高水平的数据完整性。
数据规范化对于在 OLTP 系统中高效处理和存储单个事务至关重要。在此过程中,数据被分成更小的、冗余更少的块来实现这一目标,从而为 OLTP 系统带来诸多优势。
数据规范化因其强调减少数据冗余和提高数据完整性而闻名,因为数据被组织成多个表,每个表都服务于特定目的。这些表通过主键和外键链接来建立它们之间的关系,确保每个表中的记录都是唯一的,并且同一个字段不会在多个表中重复出现,除了一些关键字段或系统字段(例如 ID 或创建 timestamps)。
数据规范化相关的原因另一个是它可以提升并最大化性能。这些经过规范化的数据库旨在通过最小化数据冗余并在表之间建立定义良好的关系来高效地处理快速读写操作,从而使数据库能够以闪电般的速度处理大量事务。这对于及时执行操作至关重要的交易系统来说非常重要。
最后但并非最不重要的一点是,规范化数据库专注于只存储当前数据,以使数据库表示可用的最新信息。在存储客户信息的表中,每条记录始终反映客户的最新详细信息,例如名字、电话号码和其他相关数据,确保数据库准确地反映当前状态。
然而,对于分析项目或系统来说,范式就有些不同了。通常,用户希望能够在不需要进行大量链接的情况下检索所需的数据,这是规范化过程的必然结果。虽然 OLTP 系统针对写操作进行了优化,以避免像 web 应用这样的实时系统出现延迟增加,但分析系统的用户希望通过读取优化尽快获取分析数据。
与存储实时数据的规范化事务数据库不同,分析数据库期望包含实时和非实时数据,并作为过去数据的历史存档。而且,分析数据库通常需要来自多个 OLTP 系统的数据来提供业务流程的集成视图。理解这些差异确实至关重要,因为它们支持了数据组织、保留和利用方面的不同需求。
但是,需要澄清的是,我们刚才探讨的主要是为了性能优化和遵循 OLTP 数据库设计最佳实践而进行的规范化。虽然这个基础很重要,但它只是分析工程更广泛领域的一个方面。
为了提供更清晰的路线图,让我们首先确定我们的旅程将从探索这种基础类型的数据建模开始,这种数据建模是 OLTP 系统的基础。接下来,我们将转向讨论针对 OLAP 环境优化的数据建模方法。通过区分这两种方法,我们旨在提供对数据建模这两个方面的全面理解,为后续章节深入研究分析工程方法及其应用奠定基础。
维度数据建模
数据建模是设计和组织数据库以高效存储和管理数据的基础方面。正如我们之前讨论过的,它涉及定义系统内数据实体的结构、关系和属性。数据建模的一种流行方法是维度数据建模,它侧重于为分析和报告需求建模数据。维度数据建模特别适用于数据仓库和商业智能应用程序。它强调创建维度模型,该模型由表示可测量数据的“事实表”和提供描述性上下文 的“维度表”组成。通过使用星型模式和雪花模式等维度数据建模技术,可以将数据组织成一种方式,简化复杂查询并实现高效的数据分析。
数据建模和维度数据建模之间的关系在于它们互补的性质。数据建模为捕获和组织数据提供了基础,而维度数据建模则提供了一种专门的用于支持分析和报告需求的数据建模技术。结合这些方法,可以让组织设计健壮灵活的数据库,既能进行事务处理,又能进行深入的数据分析。
为了理解维度数据建模,我们首先应该向被认为是数据仓库和维度数据建模之父的两个人表示敬意:Bill Inmon 和 Ralph Kimball。他们被公认为是企业级信息收集、管理和分析用于决策支持的领域的先驱。他们为数据仓库主题做出了重大贡献,每个人都提倡不同的理念和方法。
Inmon 提议创建一个涵盖整个企业的集中式数据仓库,旨在生成全面的商业智能系统。另一方面,Kimball 建议创建多个针对特定部门的小型数据市集,支持部门级别的分析和报告。他们不同的观点导致了数据仓库设计技术和实施策略的对比。
除了不同的方法之外,Inmon 和 Kimball 还提出了在数据仓库环境下对数据进行结构化的不同方法。Inmon 提倡在企业数据仓库中使用关系模型 (ERD),特别是第三范式 (3NF)。相反,Kimball 的方法在维度数据仓库中采用多维模型,利用星型模式和雪花模式。Inmon 认为使用关系模型对数据进行结构化可以确保企业范围的一致性。这种一致性使得使用维度模型相对轻松地创建数据市集。另一方面,Kimball 认为在维度模型中组织数据有利于信息总线,使用户能够更有效地理解、分析、汇总和探索数据的不一致性。此外,Kimball 的方法允许直接从分析系统访问数据。相比之下,Inmon 的方法限制分析系统仅从企业数据仓库访问数据,需要与数据市集交互才能检索数据。
在接下来的部分中,我们将深入研究三种建模技术:星型模式、雪花模式和新兴的数据仓库方法 (Data Vault)。 Data Vault 于 2000 年由 Dan Linstedt 提出,近年来发展势头强劲。它遵循更加规范化的结构,这与 Inmon 的方法并不完全一致,但类似。
星型模式建模
星型模式是一种广泛用于关系数据仓库的建模方法,尤其适用于分析和报告目的。它涉及将表分类为维度表或事实表,以便有效地组织和表示业务单元以及相关的观察或事件。
维度表用于描述要建模的业务实体。这些实体可以包括各种方面,例如产品、人员、地点和概念,包括时间。在星型模式中,您通常会找到一个日期维度表,该表提供用于分析的综合日期集。维度表通常由一个或多个关键列组成,这些列用作每个实体的唯一标识符,以及其他描述性列,提供有关实体的详细信息。
另一方面,事实表存储业务中发生的观察或事件。这些包括销售订单、库存水平、汇率、温度和其他可测量数据。事实表包含维度键列(引用维度表)和数字测量列。维度键列决定了事实表的维度,并指定哪些维度包含在分析中。例如,存储销售目标的事实表可能包含日期和产品键的维度键列,表明分析包括与时间和产品相关的维度。事实表 的粒度由其维度键列中的值决定。例如,如果销售目标事实表中的日期列存储代表每个月第一天 的值,则该表的粒度为 月/产品 级别。这意味着事实表按月汇总销售目标数据,具体到每个产品。
通过将数据结构化成星型模式,其中维度表代表业务单位,事实表捕获观察或事件,公司可以高效地执行复杂分析并获得有意义的见解。星型模式为查询和汇总数据提供了清晰直观的结构,使分析和理解数据集中的维度和事实之间关系变得更加容易。
回到我们的图书表,我们将遵循建模步骤来开发一个简单的星型模式模型。第一步是识别维度表。但首先,让我们回顾一下示例 2-6 中的基表。
示例 2-6. 星型模式的基表
-- 这是我们的基表
CREATE TABLE books (
book_id INT PRIMARY KEY,
title VARCHAR(100),
author VARCHAR(100),
publication_year INT,
genre VARCHAR(50)
);
我们应该识别 books 表中所有单独的维度(与特定业务实体相关的属性),并为每个维度创建单独的维度表。在我们的示例中,就像归一化步骤一样,我们识别了三个实体:图书、作者和类型。让我们看示例 2-7 的物理模型。
示例 2-7. 星型模式的维度表
-- 创建维度表
CREATE TABLE dimBooks (
book_id INT PRIMARY KEY,
title VARCHAR(100)
);
CREATE TABLE dimAuthors (
author_id INT PRIMARY KEY,
author VARCHAR(100)
);
CREATE TABLE dimGenres (
genre_id INT PRIMARY KEY,
genre VARCHAR(50)
);
在命名维度表时,建议使用描述性强且直观的名称来反映它们所代表的实体。例如,如果我们有一个代表图书的维度表,我们可以将其命名为 dimBook 或 simply books。类似地,对于代表作者、类型或其他实体的维度表,可以使用相关且易于理解的名称,例如 dimAuthor 或 dimGenre。
对于事实表,建议使用表明所捕获的测量或事件的名称。例如,如果我们有一个记录图书销售的事实表,我们可以将其命名为 factBookSales 或 salesFact。这些名称表明该表包含有关图书销售的数据。现在,我们可以创建一个名为 factBookPublish 的事实表(见示例 2-8)来捕获出版数据。
示例 2-8. 星型模式的事实表
-- 创建事实表
CREATE TABLE factBookPublish (
book_id INT,
author_id INT,
genre_id INT,
publication_year INT,
FOREIGN KEY (book_id) REFERENCES dimBooks (book_id),
FOREIGN KEY (author_id) REFERENCES dimAuthors (author_id),
FOREIGN KEY (genre_id) REFERENCES dimGenres (genre_id)
);
此代码创建一个新的事实表 factBookPublish,其列表示与维度相关的测量或事件。在本例中,它只是出版年份。外键约束建立了事实表和维度表之间的关系。
通过使用表示图书数据集的星型模式模型,我们现在可以为执行各种分析操作和提取有价值的见解奠定坚实的基础。星型模式的维度结构允许高效直观的查询,使我们能够从不同的角度探索数据。完成建模过程后,我们最终会得到一个类似于图 2-3 的模型,该模型类似于星形,因此得名“星型模式”。
利用这个模型,我们现在可以轻松地通过过滤条件(例如类型、作者或出版年份)来分析图书出版情况。例如,我们可以快速检索特定类型的总出版量。通过将维度表与事实表连接起来(如示例 2-9 所示),我们可以毫不费力地深入了解图书、作者、类型和销售之间的关系。
示例 2-9. 从星型模式检索数据
-- 示例:检索特定类型的总出版量
SELECT COALESCE(dg.genre, 'Not Available'), -- 或者 '-1'
COUNT(*) AS total_publications
FROM factBookPublish bp
LEFT JOIN dimGenres dg ON dg.genre_id = bp.genre_id
GROUP BY g.genre;
正如你所看到的,我们在将事实表与维度表连接时使用了 LEFT JOIN(左连接)。这是一种相当普遍的做法。它确保事实表中的所有记录都包含在结果中,无论维度表中是否存在匹配的记录。这一点很重要,因为它承认并非每个事实记录都必须在每个维度中都有对应的条目。通过使用 LEFT JOIN,您可以保留事实表中的所有数据,同时用维度表中的相关属性对其进行丰富。这使您可以基于不同的维度进行分析和汇总,并从不同的角度探索数据。
但是,我们必须处理任何缺失的对应关系。因此,我们使用 COALESCE 运算符,它通常用于设置默认值,例如 -1 或“Not Available”。LEFT JOIN 还允许增量更新维度表。如果向维度表添加了新记录,LEFT JOIN 仍会包含现有事实记录,并将其与可用的维度数据关联。这种灵活性确保了即使您的维度数据随着时间推移而演变,您的分析和报告仍然保持一致。
总而言之,星型模式的简单性和非规范化结构使其非常适合进行汇总和归纳。您可以生成各种报告,例如销售趋势、畅销类型或按作者分类的收入。此外,星型模式还可以方便进行下钻和上卷操作,使您可以深入查看更详细的信息,或汇总到更高层次的聚合级别,以全面查看数据。这种建模技术还可以无缝集成到数据可视化工具和商业智能平台中。通过将您的模型连接到诸如 Tableau、Power BI 或 Looker 之类的工具,您可以创建引人注目的可视化仪表板和交互式报告。这些资源使利益相关者能够快速掌握洞察力并一目了然地做出数据驱动决策。
但是,值得注意的是,前面的例子并没有完全突出星型模式所倡导的非规范化方面。例如,如果您的数据集严格遵循每本书一个类型的方案,那么您可以通过直接在统一的 dimBooks 表中合并类型信息来进一步简化模型,从而促进非规范化并简化数据访问。
雪花模式建模
雪花模式是一种比星型模式更规范化的数据模型。它通过将维度表拆分成多个连续的表来实现额外的归一化级别。这可以改善数据完整性并减少数据冗余。
例如,考虑用于电子商务数据库的雪花模式。我们有一个维度表“客户”,其中包含客户信息,例如 ID、姓名和地址。在雪花模式中,我们可以将该表拆分成几个连续的表。客户表可以拆分为一个客户表和一个单独的地址表。客户表将包含特定于客户的属性,例如 ID 和客户姓名。相比之下,地址表将包含地址相关信息,例如 ID 和客户的街道、城市和邮政编码。如果有多个客户具有相同的地址,则我们只需要将地址信息存储一次在地址表中,然后将其链接到相应的客户。
为了从雪花模式中检索数据,我们通常需要对关联表执行多次联接才能获取所需的信息。例如,如果我们想要查询客户姓名和地址,则必须在 ID 列上将客户表与地址表进行联接。
虽然雪花模式提供了更好的数据完整性,但由于额外的链接,它也需要更复杂的查询。但是,对于大型数据集和复杂关系来说,这种模式可能是有益的,因为它提供了更好的规范化和数据管理灵活性。
星型模式和雪花模式都是两种常见的数据仓库架构设计。
在星型模式中,维度表是非规范化的,这意味着它们包含冗余数据。星型模式的优点包括更易于设计和实现,以及由于更少的 JOIN 操作而带来更有效的查询。然而,由于数据冗余,它可能需要更多的存储空间,并且更新和故障排除可能更具挑战性。这也是我们经常看到混合模型的原因之一,公司可以对星型模式进行建模,并经常对一些维度进行规范化以实现不同的优化策略。选择在很大程度上取决于您的独特需求和要求。
如果在数据仓库解决方案中优先考虑简便性和效率,那么星型模式可能是理想的选择。这种模式易于实施且查询效率高,适用于直接的数据分析任务。但是,如果您预计数据需求会频繁变化并且需要更大的灵活性,那么雪花模式可能更好,因为它更容易适应不断变化的数据结构。
假设我们有一个维度来表示全球特定客户的位置。在星型模式中建模它的一种方法是创建一个包含所有位置层次结构非规范化的单一维度表。示例 2-10 展示了星型模式范例下的 dimLocation。
示例 2-10. 星型模式位置维度
CREATE TABLE dimLocation ( locationID INT PRIMARY KEY, country VARCHAR(50), city VARCHAR(50), State VARCHAR(50) );
示例 2-11 遵循雪花模式对位置维度进行建模。
示例 2-11. 雪花模式位置维度
CREATE TABLE dimLocation ( locationID INT PRIMARY KEY, locationName VARCHAR(50), cityID INT );
CREATE TABLE dimCity ( cityID INT PRIMARY KEY, city VARCHAR(50), stateID INT );
CREATE TABLE dimState ( stateID INT PRIMARY KEY, state VARCHAR(50), countryID INT );
CREATE TABLE dimCountry ( countryID INT PRIMARY KEY, country VARCHAR(50), );
在雪花模式示例中,位置维度被拆分为四个表:dimLocation、dimCity、dimState 和 dimCountry。这些表使用主键和外键连接,以建立它们之间的关系。一个重要主题是,虽然我们使用四个表来表示位置维度,但只有层次结构最高的表通过其主键连接到事实表(或事实表)。所有其他层次级别都遵循从最高粒度到最低粒度的层次结构。图 2-4 说明了这种情况。
数据仓库建模方法:Data Vault
Data Vault 2.0 并不是一种维度建模方法,但仍然值得一提。它结合了 3NF 元素和维度建模来创建一个逻辑企业数据仓库。它通过提供灵活且可扩展的模式来处理各种数据类型,包括结构化数据、半结构化数据和非结构化数据。
Data Vault 的一个最突出特性是它专注于构建一个模块化和增量式的 Data Vault 模型,该模型基于业务键集成原始数据。这种方法确保数据仓库能够适应不断变化的业务需求和不断发展的数据集。
更深入地研究,这种建模技术提供了一个可扩展且灵活的数据仓库和分析解决方案。它旨在处理大量数据、不断变化的业务需求和不断演变的数据源。Data Vault 的模型由三个主要部分组成:枢纽 (hubs)、链接 (links) 和卫星 (satellites)。
- 枢纽 (hubs) 代表业务实体,是存储称为业务键的唯一标识符的中心点。每个枢纽对应一个特定实体,例如客户、产品或位置。枢纽表包含业务键列以及任何与实体相关的描述性属性。通过将业务键与描述性属性分开,Data Vault 可以轻松跟踪描述性信息的更改,而不会损害业务键的完整性。
- 链接 (links) 捕获业务实体之间的关系。它们被创建来表示多对多关系或复杂关联。链接表包含来自参与枢纽的外键,这些外键在链接实体之间形成桥梁。这种方法允许建模复杂的关系,而无需复制数据或创建不必要的复杂性。
- 卫星 (satellites) 存储与枢纽和链接相关的特定于上下文的属性。它们包含额外的描述性信息,这些信息不是业务键的一部分,但提供了有关实体的宝贵上下文。卫星通过外键与对应的枢纽或链接相关联,允许存储随时间变化的数据并保留历史记录。多个卫星可以与一个枢纽或链接相关联,每个卫星都捕获不同时间点或不同角度的特定属性。
Data Vault 架构在提供数据集成、分析和数据治理的坚实基础的同时,还提升了可追溯性、可扩展性和可审计性。通过使用枢纽、链接和卫星,组织可以构建一个 Data Vault,该存储库可以支持其分析需求、适应不断变化的业务需求并维护可靠的数据变更历史记录。
回到我们的图书表,让我们遵循三个建模步骤来开发一个简单的数据仓库模型。第一步是识别业务键并创建相应的枢纽和卫星表。在本例中,我们只有一个业务实体,因此不会使用链接。示例 2-12 展示了图书表的 Data Vault 建模。
示例 2-12. 使用 Data Vault 2.0 建模图书表
-- 这是我们的基表
CREATE TABLE books ( book_id INT PRIMARY KEY, title VARCHAR(100), author VARCHAR(100), publication_year INT, genre VARCHAR(50) );
在 Data Vault 建模中,我们首先识别业务键,它们是每个实体的唯一标识符。在本例中,books 表的主键 book_id 就是业务键。现在让我们建模并创建我们的第一个表:枢纽表,它用于存储唯一的业务键及其对应的哈希键以保持稳定性。示例 2-13 创建了枢纽表。
示例 2-13. 创建枢纽表
CREATE TABLE hubBooks ( bookKey INT PRIMARY KEY, bookHashKey VARCHAR(50), Title VARCHAR(100) );
在枢纽表中,我们将每个图书的唯一标识符存储为主键 (bookKey),并存储一个哈希键 (bookHashKey) 以保持稳定性。Title 列包含有关图书的描述性信息。
接下来是我们的卫星表,如示例 2-14 所示,它捕获了额外的图书详细信息并维护历史更改记录。
示例 2-14. 创建卫星表
CREATE TABLE satBooks ( bookKey INT, loadDate DATETIME, author VARCHAR(100), publicationYear INT, genre VARCHAR(50), PRIMARY KEY (bookKey, loaddate), FOREIGN KEY (bookKey) REFERENCES hubBooks(bookKey) );
通过将核心图书信息分离到枢纽表中,并将历史详细信息存储在卫星表中,我们可以确保随着时间的推移捕获作者、出版年份或类型等属性的更改,而无需修改现有记录。在 Data Vault 模型中,我们可能会拥有其他表,例如链接表用于表示实体之间的关系,或者其他卫星表用于捕获特定属性的历史变化。
直到最近,数据建模的主流方法还围绕着创建庞大的 SQL 脚本进行。在这种传统方法中,单个 SQL 文件(通常包含数千行代码)囊括了整个数据建模过程。对于更复杂的工作流程,实践者可能会将文件分成多个 SQL 脚本或存储过程,然后通过 Python 脚本依次执行。更糟糕的是,这些脚本通常在组织内鲜为人知。因此,即使其他人希望以类似的方式进行数据建模,他们也往往会从头开始,放弃利用现有工作的的机会。
这种方法可以恰当地描述为整体式或传统的数据建模方法,每个数据使用者独立地从原始源数据重建他们的数据转换。在这种范例中,存在几个值得注意的挑战,包括脚本版本控制的缺失、管理视图之间依赖关系的艰巨任务以及从原始数据源到最终报告阶段创建新视图或表的常见做法,从而损害了可重用性。此外,幂等性(idempotency)的概念没有统一地应用于大型表,有时会导致冗余,回填(backfill)虽然很常见,但往往会变得复杂且费力。
在当今快速发展的 data engineering 领域,整体式数据模型(尤其是针对 SQL 转换)给工程师带来了重大挑战。考虑以下场景:您发现生产系统中存在问题,但您会发现最初看起来简单的更改会引发一系列错误,并贯穿整个基础架构。这种噩梦般的场景由高度互连的系统和作为级联多米诺效应催化剂的微小改动所 χαρακτηρίζει (chαρακτηrízei, 希腊语“characterize”的过去式),对许多数据专业人员来说都是一个挥之不去的烦恼。
我们在设计数据模型时要避免的就是整体式数据模型带来的风险。您最不想要的是耦合度很高的数据模型,使调试和实施更改成为一项令人生畏的任务,因为每个更改都可能破坏整个数据管道。缺乏模块化会阻碍灵活性、可扩展性和可维护性,而这些都是当今数据驱动领域的关键因素。
在整体式数据模型中,所有组件都紧密相连,使得识别和隔离问题成为一项挑战。从本质上讲,这种传统的数据系统设计方法倾向于将整个系统统一成一个单一的(尽管并不总是内聚的)单元。这种模型的互连性意味着看似无关的更改可能会产生意想不到的后果,从而影响整个系统。这种复杂性不仅使故障排除更加困难,而且还增加了引入错误或忽略关键依赖关系的风险。所有数据和功能都紧密集成并相互依赖,以至于修改或更新系统任何一部分都可能影响整个系统。
此外,数据模型缺乏模块化会阻碍适应不断变化的业务需求的能力。在数据需求不断变化、源不断变化的动态环境中,整体式模型成为前进的瓶颈。引入新的数据源、扩展基础设施或集成新的技术和框架变得越来越具有挑战性。同样,维护和更新整体式数据模型也变得费时费力。由于系统内部复杂的依赖关系,每次更改都伴随着更大的风险。担心意外破坏关键组件会导致过于谨慎的方法,从而减慢开发周期并阻碍创新。
整体式数据模型在当今数据工程领域带来的挑战是巨大的。依赖关系的风险、缺乏灵活性以及维护和可扩展性方面的困难迫使我们转向模块化数据模型。通过采用模块化,数据工程师可以在其数据基础设施中实现更大的灵活性、鲁棒性和适应性,以管理快速变化的数据生态系统的复杂性。
通过摒弃整体式结构,组织可以充分发挥其数据的潜力,推动创新,并在我们所处的这个数据驱动的世界中获得竞争优势。
dbt 在采用模块化方法和克服整体式模型的挑战方面发挥了重要作用。它允许我们通过将单个数据模型分解成独立的模块(每个模块都有自己的 SQL 代码和依赖关系)来提高可维护性、灵活性和可扩展性。这种模块化结构使我们能够独立地处理各个模块,从而更容易开发、测试和调试数据模型的特定部分。这消除了意外更改影响整个系统的风险,从而使引入更改和更新更加安全。
有关 dbt 的模块化主题将在接下来的子部分中得到更多关注,第 4 章将深入探索 dbt。
构建模块化数据模型
前一个例子强调了 dbt 和数据模型模块化如何通常能改善数据开发过程。但是,为什么对于数据工程师和科学家来说这不是理所当然的呢?事实是,在过去的几十年里,软件开发人员和架构师们选择采用新的方式来利用模块化来简化他们的编码过程。模块化不是一次性处理一大段代码,而是将编码过程分解成多个步骤。这种方法比替代策略具有いくつかの优势。
模块化的一大主要优点是它可以提高可管理性。在开发大型软件程序时,专注于单个编码部分可能会带来挑战。但是,通过将它分解成单个任务,可以使这项工作更易于管理。这有助于开发人员保持正轨,并防止他们因项目规模庞大而感到不知所措。
模块化的另一个优点是它支持团队编程。可以将大型任务分配给团队中的每个人,而不是分配给单个程序员。每位程序员都会被分配特定任务作为整个程序的一部分。最后,所有程序员的工作都将组合起来创建最终程序。这种方法可以加速开发过程,并允许团队内部进行 تخصص (zhuān zhuān, 特殊专业)。
模块化还有助于提高代码质量。将代码分解成小部分并分配给单个程序员的责任会提高每个部分的质量。当程序员专注于他们分配的部分而不用担心整个程序时,他们可以确保代码的完美性。因此,当所有部分集成在一起时,整个程序出错误的可能性就更小。
此外,模块化使已经 terbukti ( dokázeno, Чешский, 证明)有效的代码模块重复使用成为可能。通过将程序分模块,基础部分会被分解。如果特定代码段能很好地完成特定任务,则无需重新发明它。相反,可以重复使用相同的代码,从而节省程序员的时间和精力。当需要类似功能时,可以在整个程序中重复此操作,从而进一步简化开发过程。
此外,模块化代码组织良好,可读性强。通过基于任务组织代码,程序员可以根据其组织方案轻松找到并引用特定部分。这可以改善多个开发人员之间的协作,因为他们可以遵循相同的组织方案并更有效地理解代码。
模块化所有优点最终都会带来更高的可靠性。易于阅读、调试、维护和共享的代码运行时错误更少。当处理大型项目时,许多开发人员将来需要共享代码或相互接口代码时,这一点变得至关重要。模块化使以可靠的方式创建复杂软件成为可能。
虽然模块化在软件工程界必不可少,但在数据领域,它被落后了,直到最近几年才被拾起。其原因在于需要数据架构和软件工程之间更加清晰。然而,最近该行业已经发展成为两者的融合,因为之前提到的优势也适用于数据分析和工程。
正如模块化简化了编码过程一样,它也可以简化数据模型的设计和开发。通过将复杂的数据结构分解成模块化组件,数据工程师可以在各种粒度级别更好地管理和处理数据。这种模块化方法使数据集成、可扩展性和灵活性变得高效,从而更容易更新、维护和增强整体数据架构。同时,模块化促进了数据模块的重用,确保了数据模型之间的一致性和准确性,并减少了冗余。
总而言之,模块化原则为有效的数据建模和工程提供了坚实的基础,增强了数据系统的组织性、可访问性和可靠性。因此,模块化数据建模是一种用于设计高效且可扩展的数据系统的重要技术。通过将复杂的数据结构分解成更小的可重用组件,开发人员可以构建更健壮和可维护的系统。这是一种用于设计高效和可扩展的数据系统的重要技术,dbt 和 SQL 都提供了高效的工具来帮助我们实现这一技术。
总之,模块化数据建模的核心原则可以定义如下:
- 分解 - 将数据模型分解成更小、更易于管理的组件
- 抽象 - 隐藏数据模型的实现细节
- 可重用性 - 创建可以在系统多个部分重用的组件
可以使用归一化、数据仓库和数据虚拟化技术来实现这种数据建模。例如,使用归一化技术,数据会根据其特征和关系分离成表,从而形成模块化数据模型。
另一种选择是利用 dbt,因为它有助于自动创建模块化数据模型的过程,并提供了多种支持模块化数据建模原则的功能。例如,dbt 允许我们通过将数据模型拆分成更小的可重用组件来实现分解,这提供了一种创建可重用宏和模块化模型文件的方法。它还通过提供一个简单一致的接口来处理数据源,从而使我们能够抽象数据模型的实现细节。此外,dbt 通过提供一种定义和跨各种模型重用通用代码的方法来鼓励可重用性。
dbt 还通过提供测试和 документировать (dokumentírovat’, 俄语“document”) 数据模型的方式来改善可维护性。最后,dbt 允许您通过定义和测试针对模型的不同物化策略来优化性能,最终使您可以微调数据模型各个组件的性能。
但是, важно认识到 (vážno認識到, 重要认识到) 模块化也存在潜在的缺点和风险。集成系统通常可以比模块化系统更好地优化,原因可能是数据移动和内存使用量最小化,或者数据库优化器能够在幕后改善 SQL。创建视图然后创建表有时可能会导致次优模型。然而,这种权衡通常值得为模块化带来的好处而付出。
模块化会创建更多文件,这意味着需要拥有、管理和可能弃用的对象更多。如果没有成熟的数据治理策略,这可能会导致大量模块化但无人拥有的表激增,当问题出现时可能难以管理。
利用 dbt 实现模块化数据模型
正如我们之前强调的,构建模块化数据模型是开发健壮且可维护的数据基础设施的重要方面。然而,随着项目规模和复杂性的增加,管理和协调这些模型的过程可能会变得复杂。这就是像 dbt 这样的强大数据转换工具发挥作用的地方。通过将模块化数据建模原则与 dbt 的特性相结合,我们可以轻松地在数据基础设施中解锁全新级别的效率和可扩展性。
采用这种模块化方法后,组织内的每个数据生产者或使用者都可以利用其他人完成的基础数据建模工作,从而无需每次都从源数据开始构建。将 dbt 集成到数据建模框架后,数据模型的概念会发生转变,从一个整体的实体转变为一个 distinct component(distinct component 指单独的组件)。模型的每个贡献者都将开始识别可以在各种数据模型中共享的转换。这些共享的转换被提取出来并组织成基础模型,允许在多个上下文中有效地引用它们。
如下图 2-5 所示,在多个实例中使用基础数据模型,而不是每次都从头开始,可以简化数据建模中的 DAG 可视化。这种模块化多级结构阐明了数据建模逻辑层如何相互构建并显示依赖关系。但是,需要注意的是,仅仅采用像 dbt 这样的数据建模框架并不能自动确保模块化数据模型和易于理解的 DAG。
您的 DAG 的结构取决于您的团队的数据建模思想和思维过程,以及它们表达的一致性。为了实现模块化数据建模,请考虑命名约定、可读性和易于调试和优化的原则。这些原则可以应用于 dbt 中的各种模型,包括临时模型、中间模型和事实模型,以改善模块化并保持结构良好的 DAG。
让我们通过理解 dbt 如何通过 Jinja 语法使用引用数据模型来支持模型可重用性 ({{ ref() }}),开始利用 dbt 构建模块化数据模型的旅程。
引用数据模型
通过采用 dbt 的特性(例如模型引用和 Jinja 语法),数据工程师和分析师可以建立清晰的模型依赖关系,提高代码可重用性,并确保数据管道的准确性和一致性。
Jinja 在此 context(上下文)中是一种模板语言,它允许在 SQL 代码中进行动态和程序化的转换,为定制和自动化数据转换提供了强大的工具。模块化与 dbt 功能的这种强大结合使团队能够构建灵活且可维护的数据模型,从而加速开发过程并实现利益相关者之间的无缝协作。
为了充分利用 dbt 的功能并确保模型构建的准确性,关键是要使用 {{ ref() }} 语法进行模型引用。通过这种方式引用模型,dbt 可以根据上游表自动检测并建立模型之间的依赖关系。这可以使数据转换管道顺利可靠地执行。
另一方面,{{ source() }} Jinja 语法应该谨慎使用,通常仅限于从数据库中最初选择原始数据。重要的是避免直接引用非 dbt 创建的表,因为它们会阻碍 dbt 工作流程的灵活性 和模块化。
相反,重点应该放在使用 {{ ref() }} Jinja 语法建立模型之间的关系,确保上游表的变化正确地向下游传播,并保持清晰连贯的数据转换过程。通过坚持这些最佳实践,dbt 可以实现高效的模型管理,并促进分析工作流程的可扩展性和可维护性。
例如,假设我们有两个模型:orders 和 customers,其中 orders 表包含有关客户订单的信息,而 customers 表存储客户详细信息。我们想要联接这两个表,使用客户信息来丰富订单数据(示例 2-15)。
示例 2-15. 引用模型
-- 在 orders.sql 文件中
SELECT
o.order_id, o.order_date, o.order_amount, c.customer_name, c.customer_email
FROM
{{ ref('orders') }} AS o
JOIN
{{ ref('customers') }} AS c ON
o.customer_id = c.customer_id
-- 在 customers.sql 文件中
SELECT
customer_id, customer_name, customer_email
FROM
raw_customers
此示例演示了使用 ref() 函数在 SQL 查询中引用模型。场景涉及两个模型文件:orders.sql 和 customers.sql。在 orders.sql 文件中,编写了一个 SELECT 语句从 orders 模型中检索订单信息。{{ ref('orders') }} 表达式引用 orders 模型,允许查询使用该模型中定义的数据。该查询使用 customer_id 列将 orders 模型与 customers 模型连接起来,检索其他客户信息,例如姓名和电子邮件。
在 customers.sql 文件中,编写了一个 SELECT 语句从 raw_customers 表中提取客户信息。此模型表示任何转换之前的原始客户数据。
dbt 中的这种引用机制允许创建模块化且相互关联的模型,这些模型相互构建以生成有意义的洞察力和报告。为了说明这一点的必要性,让我们考虑一个实际示例:假设您正在处理一个复杂的数据集,例如每周的产品订单。如果没有结构化的方法,管理这些数据会很快变得混乱。您可能会陷入一堆错综复杂的 SQL 查询,从而难以跟踪依赖关系、维护代码和确保数据准确性。
通过将您的数据转换过程组织成从源表到事实表等 distinct 层,您可以获得以下几个好处。这简化了数据管道,使其更易于理解和管理。它还允许进行增量改进,因为每个层都专注于特定的转换任务。这种结构化方法可以加强数据工程师和分析师之间的协作,减少错误,并最终生成更可靠和更具洞察力的报告。
临时数据模型
临时层在数据建模中扮演着重要角色,因为它作为构建更复杂数据模型的模块化基础。每个临时模型对应一个源表,与原始数据源保持 1:1 的关系。保持临时模型的简单性并尽量减少该层内的转换很重要。可接受的转换包括类型转换、列重命名、基本计算(例如单位转换)以及使用条件语句(例如 CASE WHEN)进行分类。临时模型通常物化成视图,以保持数据及时性和优化存储成本。这种方法允许引用临时层的中间模型或事实模型访问最新数据,同时节省空间和成本。建议避免在临时层中进行联接,以防止冗余或重复计算。联接操作更适合后续层,用于建立更复杂的关联关系。此外,也应该避免在临时层进行聚合,因为它们会对有价值的源数据进行分组并可能限制访问。临时层的主要目的是为后续数据模型创建基本构建块,从而在下游转换中提供灵活性和可扩展性。
遵循这些准则,临时层将成为模块化数据架构中构建强大数据模型的可靠高效的起点。
在 dbt 中利用临时模型使我们能够在代码中采用 Don’t Repeat Yourself (DRY) 原则。通过遵循 dbt 的模块化和可重用结构,我们的目标是尽可能将特定组件模型始终需要的任何转换向上游推。这种方法有助于避免重复代码,从而降低复杂性和计算开销。例如,假设我们一直需要将以美分计的货币值从整数转换为以美元计的浮点数。在这种情况下,在临时模型中尽早执行除法和类型转换会更有效。这样,我们就可以引用下游的转换值,而不必多次重复相同的转换。
通过利用临时模型,我们可以优化代码重用,并以可扩展和高效的方式简化数据转换过程。假设我们有一个名为 raw_books 的源表,其中包含原始图书数据。现在我们想要创建一个名为 stg_books 的临时模型来转换和准备数据,以便进一步处理。在我们的 dbt 项目中,我们可以创建一个名为 stg_books.sql 的新 dbt 模型文件,并定义生成临时模型的逻辑,如示例 2-16 所示。
示例 2-16. 临时数据模型
/* 此文件应为 stg_books.sql,它查询原始表以创建新模型 */
SELECT book_id, title, author, publication_year, genre FROM raw_books
此示例中的临时模型 stg_books 从 raw_books 表中选择相关列。它可以包括一些基本转换,例如重命名列或转换数据类型。通过创建临时模型,您可以将初始数据转换与下游处理分开。这可以确保数据的质量、一致性和符合标准,然后再进一步使用。临时模型是数据管道中间层和事实层中更复杂数据模型的基础。它们简化了转换,维护了数据完整性,并提高了 dbt 项目的可重用性和模块化程度。
基础数据模型
在 dbt 中,基础模型通常用作临时模型,但根据项目需求,它们也可以包含额外的转换步骤。这些模型通常设计为直接引用输入数据仓库的原始数据,它们在数据转换过程中扮演着重要角色。一旦创建了临时模型或基础模型,dbt 项目中的其他模型就可以引用它们。dbt 文档中从“基础”模型改为“临时”模型是为了避免受“基础”这个名称的限制,因为该名称暗示着构建数据模型的第一步。新的术语允许更灵活地描述这些模型在 dbt 框架中的作用和目的。
中间数据模型
中间层在数据建模中扮演着重要角色,它通过组合来自临时层的原子构建块来创建更复杂和更有意义的模型。这些中间模型表示对业务有重要意义的结构,但通常不会通过仪表板或应用程序直接暴露给最终用户。
为了保持分离并优化性能,建议将中间模型存储为临时模型。临时模型不会直接创建在数据库或数据集上,而是将它们的代码插入到引用它们的模型中,作为公共表表达式 (CTE)。但是,有时将它们物化成视图会更合适。临时模型无法直接选择,这使得故障排除具有挑战性。此外,通过 run-operation 调用的宏无法引用临时模型。因此,是将特定的中间模型物化成临时模型还是视图取决于具体用例,但建议从临时物化开始。
如果您选择将中间模型物化成视图,则将它们放置在 dbt 配置文件中定义的主模式之外的自定义模式中可能会很有益。这有助于组织模型并有效地管理权限。
中间层的主要目的是汇集不同的实体并吸收最终事实模型的复杂性。这些模型提高了整体数据模型结构的可读性和灵活性。重要的是要考虑在其他模型中引用中间模型的频率。多个模型引用同一个中间模型可能表示设计问题。在这种情况下,将中间模型转换为宏可能是提高模块化并保持更清洁设计的一种合适解决方案。
通过有效利用中间层,可以使数据模型更加模块化和可管理,确保吸收复杂性同时保持组件的可读性和灵活性。
假设我们有两个临时模型 stg_books 和 stg_authors,分别表示图书和作者数据。现在我们想要创建一个名为 int_book_authors 的中间模型,它结合了来自这两个临时模型的相关信息。在我们的 dbt 项目中,我们可以创建一个名为 int_book_authors.sql 的新 dbt 模型文件,并定义生成中间模型的逻辑,如示例 2-17 所示。
示例 2-17. 中间数据模型
-- 此文件应为 int_book_authors.sql
-- 引用临时模型
WITH
books AS ( SELECT * FROM {{ ref('stg_books') }} ),
authors AS ( SELECT * FROM {{ ref('stg_authors') }} )
-- 组合相关的信息
SELECT
b.book_id, b.title, a.author_id, a.author_name
FROM
books b
JOIN
authors a ON b.author_id = a.author_id
在示例 2-17 中,int_book_authors 模型使用 {{ ref() }} Jinja 语法引用临时模型 stg_books 和 stg_authors。这确保了 dbt 可以正确推断模型依赖关系并基于上游表构建中间模型。
事实模型
数据管道的顶层由事实模型组成,事实模型负责将业务定义的实体集成并通过仪表板或应用程序呈现给最终用户。这些模型结合来自多个源的所有相关数据,并将它们转换为一个连贯的视图。
为了确保最佳性能,事实模型通常会物化成表。物化模型可以更快地执行查询,并能更响应地向最终用户提供结果。如果物化表的创建时间或成本是一个问题,可以考虑将模型配置为增量模型,这样可以随着新数据的加入高效地更新数据。
事实模型的关键在于简洁,应避免过度联接。如果事实模型中需要多个联接,请重新考虑设计并重构中间层。通过保持事实模型相对简单,您可以确保查询执行效率并维持数据管道的整体性能。
让我们来看一个用于图书出版分析的数据仓库的示例。我们有一个名为 int_book_authors 的中间模型,其中包含原始图书数据,包括有关每本书的作者信息(示例 2-18)。
示例 2-18. 事实模型
-- 此文件应为 mart_book_authors.sql
{{ config( materialized='table', unique_key='author_id', sort='author_id' ) }}
WITH book_counts AS (
SELECT
author_id, COUNT(*) AS total_books
FROM {{ ref('int_book_authors') }}
GROUP BY author_id
)
SELECT
author_id, total_books
FROM book_counts
我们首先设置模型的配置,指定它应物化成表。将唯一键设置为 author_id 以确保唯一性,排序也基于 author_id 进行。接下来,我们使用一个称为 book_counts 的 CTE 来聚合图书数据。我们从 stg_books 临时模型中选择 author_id 列并计算与每位作者关联的图书数量。最后,SELECT 语句从 book_counts CTE 检索聚合数据,返回每个作者的 author_id 和对应图书数量。由于它是一个物化表,因此该模型可以根据需要随时刷新以反映原始数据中的任何更改。
测试数据模型
dbt 中的测试是确保数据模型和数据源准确性和可靠性的重要环节。dbt 提供了一个全面的测试框架,允许您使用 SQL 查询定义和执行测试。这些测试旨在识别不符合指定断言标准的行或记录,而不是检查特定条件的正确性。
dbt 有两种主要的测试类型:单一测试和通用测试。单一测试是专用于测试特定方面的 SQL 语句,并存储在单独的 SQL 文件中。它们允许您测试数据的特定方面,例如检查事实表中是否存在 NULL 值或验证某些数据转换。通过单一测试,我们可以利用 Jinja 的强大功能根据数据和业务需求动态定义断言。让我们通过分析示例 2-19 来看看 dbt 中的单一测试。
示例 2-19. dbt 中的单一测试示例
version: 2
models:
- name: my_model
tests:
- not_null_columns:
columns:
- column1
- column2
在这个例子中,我们为 dbt 模型 my_model 定义了一个名为 not_null_columns 的单一测试。此测试检查模型中的特定列是否包含 NULL 值。columns 参数指定要检查 NULL 值的列。在本例中,指定了 column1 和 column2。如果这些列中的任何一个包含 NULL 值,则测试失败。
另一方面,通用测试更灵活,可以应用于多个模型或数据源。它们通过使用特殊语法在 dbt 项目文件中定义。这些测试允许我们定义更全面的标准来验证我们的数据,例如检查表之间的数据一致性或确保特定列的完整性。此外,它们提供了一种灵活且可重用的方式来定义可以在 dbt 模型中应用的断言。这些测试以 YAML (.yml) 文件编写和存储,这允许我们对查询进行参数化并轻松地在各种上下文中重用它们。通用测试中查询的参数化使您可以快速将测试适应多种场景。例如,在将通用测试应用于不同的模型或数据集时,您可以指定不同的列名或条件参数。让我们看一下示例 2-20 中的其中一个通用测试。
示例 2-20. dbt 中的通用测试示例
version: 2
tests:
- name: non_negative_values
severity: warn
description: Check for non-negative values in specific columns
columns:
- column_name: amount
assert_non_negative: {}
- column_name: quantity
assert_non_negative: {}
在这个例子中,通用测试被定义为 non_negative_values。在这里,我们可以观察到要测试的列以及每个列的断言标准。该测试检查 amount 和 quantity 列中的值是否非负。通用测试使您可以编写可重用的测试逻辑,该逻辑可以应用于 dbt 项目中的多个模型。为了在多个模型中重用通用测试,我们可以在每个单独模型的 YAML 文件的 tests 部分引用它,如示例 2-21 所示。
示例 2-21. 重用通用测试
version: 2
models:
- name: my_model
columns:
- column_name: amount
- column_name: quantity
tests: ["my_project.non_negative_values"]
在这个例子中,定义了模型 my_model,并指定了 amount 和 quantity 列以及相应的测试。测试引用来自 my_project 命名空间的通用测试 non_negative_values(假设 my_project 是您的 dbt 项目的名称)。通过在每个模型的 tests 部分指定通用测试,您可以在多个模型中重用相同的测试逻辑。这种方法确保了数据验证的一致性,并允许您轻松地将通用测试应用于不同模型中的特定列,而无需复制测试逻辑。请注意,您必须确保通用测试的 YAML 文件位于 dbt 项目结构中的正确目录中,您可能需要修改测试引用以匹配项目的命名空间和文件夹结构。
生成数据文档
数据建模的另一个重要组成部分是文档。具体而言,确保组织中的每个人(包括业务用户)都能轻松理解和访问指标(例如 ARR(年度经常性收入)、NPS(净推荐值)甚至是 MAU(月活跃用户))对于支持数据驱动决策至关重要。通过利用 dbt 的功能,我们可以记录这些指标的定义方式以及它们依赖的特定源数据。此文档成为任何人都可以访问的宝贵资源,可以促进透明度并支持自助式数据探索。随着我们消除这些语义障碍并提供可访问的文档,dbt 使得各个技术水平的用户都可以浏览和探索数据集,确保将宝贵的见解提供给更广泛的受众。
假设我们有一个 dbt 项目,其中包含一个名为 nps_metrics.sql 的模型,用于计算净推荐值 (NPS)。我们可以通过在 SQL 文件中使用包含 Markdown 语法的注释轻松地记录此指标,如示例 2-22 所示。
示例 2-22. 文档
/* nps_metrics.sql -- 此模型根据客户反馈计算我们产品的净推荐值 (NPS)。
依赖项:
- 此模型依赖于 “feedback” 模式中的 “customer_feedback” 表,该表存储客户反馈数据。
- 它还依赖于 “users” 模式中的 “customer” 表,其中包含客户信息。
计算方式:
-- NPS 通过根据客户的评分将客户反馈分类为推荐者、犹豫者和贬损者来计算。
-- 推荐者:评分为 9 或 10 的客户。
-- 犹豫者:评分为 7 或 8 的客户。
-- 贬损者:评分为 0 到 6 的客户。
-- 然后通过将推荐者的百分比减去贬损者的百分比来得出 NPS。 */
-- SQL 查询:
WITH feedback_summary AS (
SELECT
CASE WHEN feedback_rating >= 9 THEN '推荐者'
WHEN feedback_rating >= 7 THEN '犹豫者'
ELSE '贬损者'
END AS feedback_category
FROM
feedback.customer_feedback
JOIN
users.customer
ON customer_feedback.customer_id = customer.customer_id
)
SELECT
(COUNT(*) FILTER (WHERE feedback_category = '推荐者') - COUNT(*) FILTER (WHERE feedback_category = '贬损者')) AS nps
FROM
feedback_summary;
在这个例子中,注释提供了有关 NPS 指标的重要细节。它们指定了 nps_metrics 模式的依赖项,解释了计算过程,并提到了查询中涉及的相关表。
在记录模型后,我们可以使用 dbt 命令行界面 (CLI) 运行以下命令来生成我们 dbt 项目的文档(示例 2-23)。
示例 2-23. 运行文档生成
dbt docs generate
运行该命令会为您的整个 dbt 项目生成 HTML 文档,其中包括经过文档化的 NPS 指标。生成的文档可以托管并在组织中向用户开放,使用户能够轻松找到并理解 NPS 指标。
调试和优化数据模型
优化 dbt 模型性能的一个 valuable 建议 (valuable suggestion) 是仔细分析和优化查询本身。一种方法是利用查询计划程序的功能,例如 PostgreSQL (Postgres) 查询计划程序。了解查询计划程序将帮助您识别查询执行中潜在的瓶颈和低效之处。另一种有效的优化技术是将复杂查询分解成更小的组件,例如 CTE (Common Table Expressions)。然后,根据涉及的操作的复杂性和性质,可以将这些 CTE 转换为视图或表。涉及轻量级计算的简单查询可以 materialization (materialize) 为视图,而更复杂和计算密集型查询可以 materialize 为表。dbt config 块可用于指定每个查询所需的物化方法。
通过选择性地使用适当的物化技术,可以显着提高性能 (significant performance improvements)。这可以减少处理延迟并提高整体数据建模效率,从而带来更快的查询执行时间。特别是,表物化的使用在性能方面显示出令人印象深刻的提升,具体取决于场景,可以大大提高速度。实施这些优化建议将实现更精简更高效的 dbt 工作流。通过优化查询并使用适当的物化策略,可以优化 dbt 模型的性能,从而改善数据处理和提高数据转换效率。
让我们看一下示例 2-24 中的复杂查询。
示例 2-24. 复杂查询 1
SELECT column1, column2, SUM(column3) AS total_sum
FROM table1
INNER JOIN table2 ON table1.id = table2.id
WHERE column4 = 'some_value'
GROUP BY column1, column2
HAVING total_sum > 1000
这个查询涉及连接表、应用过滤器和执行聚合。让我们在创建最终模型之前将其分解为多个 CTE(示例 2-25)。
示例 2-25. 解构复杂查询 1
-- 使用 CTE 进行优化,解构复杂查询
-- CTE 1: 连接所需数据
WITH join_query AS (
SELECT table1.column1, table1.column2, table2.column3
FROM table1
INNER JOIN table2 ON table1.id = table2.id
)
-- CTE 2: 过滤行
, filter_query AS (
SELECT column1, column2, column3
FROM join_query
WHERE column4 = 'some_value'
)
-- CTE 3: 聚合和过滤结果
, aggregate_query AS (
SELECT column1, column2, SUM(column3) AS total_sum
FROM filter_query
GROUP BY column1, column2
HAVING total_sum > 1000
)
-- 检索优化结果的最终查询,这将是我们的模型
SELECT *
FROM aggregate_query;
join_query CTE 侧重于连接所需表,而 filter_query CTE 则应用过滤器条件缩小行范围。然后,aggregate_query CTE 执行聚合并应用最终过滤器条件。通过将复杂查询拆分到单独的 CTE 中,您可以简化和组织逻辑以优化执行。这种方法可提高可读性、可维护性以及潜在的性能改进,因为数据库引擎可以优化每个 CTE 的执行计划。最终查询通过从 aggregate_query CTE 选择列来检索优化后的结果。
现在让我们探讨 dbt 中物化模型的调试过程。这可能一开始会很困难,因为它需要彻底的验证。一个重要方面是确保数据模型看起来符合预期,并且值与非物化版本匹配。为了方便调试和验证,可能需要完全刷新整个表并将其视为非增量模型。这可以通过 dbt run --full-refresh 命令来完成,该命令更新表并运行模型,就像它是第一次执行一样。在某些情况下,在头几天并行执行完整更新模型和增量模型可能会有所帮助。这种比较方法允许验证两个版本之间的一致性,并最大限度地减少未来数据差异的风险。当处理生产环境中成熟可靠的数据模型时,这种技术尤其有效,因为它可以建立对所做的更改的信心。通过比较更新后的模型和增量模型,我们可以确保更改的准确性并减轻潜在的数据相关问题。
考虑一个示例场景,其中一个物化的 dbt 模型根据交易数据计算月度收入。我们想要调试和验证此模型以确保其准确性。我们首先怀疑物化模型生成的值可能与预期结果不匹配。为了进行故障排除,我们决定完全刷新表格,就像它不是增量模型一样。使用 dbt full-refresh 命令,我们触发更新整个表并从头运行模型的过程。
在最初的几天里,我们还可以并行运行一个进程来更新物化模型和增量模型。这允许我们比较这两个版本的結果并确保它们一致。通过检查更新后的模型和增量模型的一致性,我们可以确信所做的更改的准确性。例如,如果我们拥有一个运行良好且被认为可靠的成熟收入模型,那么比较更新后的模型和增量模型就更有意义。这样,我们可以确认模型的更改不会导致计算出的收入数字出现任何意外的差异。此外,全面的测试对于确保数据模型的准确性和可靠性至关重要。在整个工作流中实施测试可以帮助您及早发现问题,并为您提供有关 SQL 查询性能的宝贵见解。
数据湖仓架构模式
数据仓库在决策支持和商业智能领域有着悠久的历史,但在处理非结构化、半结构化和高多样性数据方面存在局限性。与此同时,数据湖作为存储各种数据格式的存储库出现,但缺乏事务支持、数据质量控制和一致性等关键特性。这阻碍了它们兑现承诺,并导致数据仓库相关的优势丧失。
为了满足不断变化的公司需求,需要一个灵活且高性能的系统来支持各种数据应用,例如 SQL 分析、实时监控、数据科学和机器学习。然而,最近人工智能的一些进步侧重于处理各种数据类型,包括半结构化和非结构化数据,而传统数据仓库对此并不擅长。因此,组织通常会使用多个系统,包括数据湖、数据仓库和专用数据库,这由于数据在系统之间移动和复制而增加了复杂性和延迟。
作为将所有这些传统系统组合成一个满足所有新市场需求的系统的必然结果,一种新型的系统正在出现:数据湖仓 (data lakehouse)。数据湖仓结合了数据湖和数据仓库的优势,在开放格式(例如 Apache Delta Lake、Iceberg 或 Apache Hudi)的经济高效的云存储上直接实现了类似于数据仓库的数据结构和管理功能。这些格式相较于传统的 CSV 和 JSON 文件格式提供了多种优势。CSV 缺少列的类型,而 JSON 提供了更灵活的结构,但类型不一致。Parquet、Apache Avro 和 ORC(优化行式列)文件格式通过面向列和更强的类型化改进了这一点,但它们不符合 ACID(原子性、一致性、隔离性、持久性)原则(ORC 在某些情况下除外)。相比之下,Delta Lake、Iceberg 和 Hudi 通过添加 ACID 兼容性以及充当双向数据存储的能力来增强数据存储,既能支持修改的高吞吐量,又能支持大量分析查询。这些格式特别适合现代云计算数据系统,不同于最初为基于 Hadoop 的本地部署系统设计的 Parquet 等传统格式。
数据湖仓提供以下关键特性:支持并发数据读写的事务、模式强制和治理、直接 BI 工具支持、存储和计算分离以实现可扩展性、开放性(具有用于高效数据访问的标准化存储格式和 API)、支持多种数据类型以及兼容各种工作负载,包括数据科学、机器学习和 SQL 分析。它们还经常提供端到端流处理功能,消除了实时数据应用程序对单独系统的需求。企业级数据湖仓系统还包括安全、访问控制、数据治理、数据发现工具以及隐私法规合规等功能。
实施数据湖仓使组织能够将这些 essential 特征整合到单个系统中,供数据工程师、分析工程师、科学家、分析师甚至机器学习工程师共享,然后他们可以协作开发新的数据产品。
Medallion架构是在数据湖仓和新的开放格式的语境下出现的。简单来说,这是一种数据建模范例,用于在数据湖仓环境中战略性地构建数据,旨在通过数据在不同迭代层次的流动来迭代地提高数据质量。这种架构框架通常包含三个明显的层级:青铜层、银层和金层,每一层都代表着数据精炼的上升程度。
- 青铜层
这是来自外部源系统数据的初始目的地。该层中的表会原样镜像源系统表的结构,包括任何额外的元数据列,用于捕获诸如加载日期/时间和进程 ID 之类的信息。此层优先考虑高效的变更数据捕获 (CDC),维护源数据的历史存档,确保数据 lineage(谱系),方便审计,并支持重新处理数据而无需从源系统重新读取数据。
- 银层
在数据湖仓架构中,此层在整合和细化来自青铜层的数据方面发挥着重要作用。银层通过匹配、合并、校准和清洗等过程创建包含关键业务实体、概念和事务的整体视图。这包括主客户、商店、非重复交易和交叉引用表。银层用作自助分析的综合源,使用户能够进行 ad hoc 报告、高级分析和机器学习。银层通常可以采用 3NF 数据模型、星型架构、数据保险库甚至雪花架构的形式。类似于传统数据仓库,对于任何利用数据开展项目和分析以解决业务问题的人来说,银层都是宝贵的资源。
- 金层
该层提供解决业务问题的 valuable insights(宝贵见解)。它会聚合来自银层的数据,并将其提供给 BI ad hoc 报告工具和机器学习应用程序。此层确保了数据湖的可靠性、改进的性能和 ACID 事务,同时还统一了云数据存储上的流式和批处理事务。
图 2-6 展示了数据湖仓环境下的奖章架构,并指出了 dbt 可以支持创建此类系统的位置。
在青铜层到金层的演变过程中,数据会经历一系列步骤,例如摄取、清洗、增强和聚合过程,从而提供无法估量的业务见解。这种方法相比于传统的数据架构(例如具有预处理和维度模型层的数据仓库,甚至仅仅是数据湖,通常更多的是文件组织而不是创建适当的语义层)代表了重大进步。
对分析工程师来说,理解奖章架构的基础知识和数据湖仓背后的概念非常重要,因为在某些情况下,他们可能会花费大量时间在这方面。这可能涉及为奖章架构的某个层级设计模型结构,利用开放格式提供的接口,或者构建转换脚本(例如使用 dbt 等工具)来实现数据在架构层级间的流动。然而,需要注意的是,开放格式和数据湖仓的重要性可能会因所使用的特定数据架构而异。例如,在像 Snowflake 这样的架构中,数据可能主要被摄取到原生表中,而不是像 Iceberg 这样的开放格式中,这使得理解数据湖仓对于分析工程师来说更像是一种锦上添花,而不是必备技能。