dbt 的主要目的,是帮助你通过简单编写 SQL statements,以一种简单且集成的方式转换 data platforms 中的数据。当我们把 dbt 放在 ELT workflow 中时,它对应的是 transformation stage 中的活动,并为你提供额外 components,例如 version control、documentation、tests 或 automated deployment。这些能力可以简化 data specialist 的整体工作。这是否让你想起 analytics engineer 的实际活动?没错,因为 dbt 正是定义现代 analytics engineers 工作方式的工具之一。它把相关 instruments 与平台集成在一起,减少了为解决特定问题而设置额外 services 的需求,也降低了整体 system complexity。
dbt 支持 analytics engineer 所描述的任务,使他们能够以协作方式在 data platform 中运行代码,为 metrics 和 business definitions 构建 single source of truth。它推动 central 和 modular analytics code,并通过 Jinja templating language、macros 或 packages 来复用 DRY code。与此同时,dbt 也提供了我们通常在 software engineering best practices 中看到的安全性,例如协作构建 data models、对它们进行 versioning,在安全部署到 production 之前测试并记录 queries,同时具备 monitoring 和 visibility。
前面我们已经对 dbt 做了较完整的介绍。不过,在本章中,我们将进一步深入 dbt 的具体细节,并阐明它在 data analytics 世界中的重要性。我们将讨论 dbt design philosophy、这个 transformation tool 背后的原则,以及以 dbt 为核心的 data lifecycle,展示 dbt 如何将 raw data 转换为 structured models,方便消费。我们将探索 dbt project structure,概述其各种 features,例如 building models、documentation 和 tests,同时详细介绍其他 dbt artifacts,例如 YAML files。到本章结束时,你将对 dbt 及其 capabilities 形成全面理解,从而能够在自己的 data analytics workflow 中有效实施它。
dbt Design Philosophy
随着 data engineering 和 analytics workflows 变得越来越复杂,能够在保持 data quality 和 reliability 的同时简化流程的工具变得至关重要。dbt 已经成为一种聚焦的解决方案,并拥有清晰定义的 design philosophy,支撑它对 data modeling 和 analytics engineering 的方法。
总结来说,dbt design philosophy 依赖以下几点。
Code-centric approach
dbt design philosophy 的核心,是对 data modeling 和 transformation 采用 code-centric approach。dbt 不依赖 GUI-based interfaces 或 manual SQL scripts,而是鼓励 users 使用 code 定义 data transformations。这种向 code-driven development 的转变,促进了 collaboration、version control 和 automation。
Modularity for reusability
dbt 推动 modularity,使 data practitioners 能够创建 reusable code components。Models、macros 和 tests 可以被组织为 packages,从而促进 code maintenance 和 scalability。这种 modular approach 与 best practices 对齐,并增强 code reusability。
Transformations as SQL SELECT statements
dbt models 被定义为 SQL SELECT statements,因此对具备 SQL skills 的 analysts 和 engineers 来说非常容易访问。这个设计选择简化了 development,并确保 data modeling closely follows SQL best practices。
Declarative language
dbt 使用 declarative language 定义 data transformations。Analysts 指定 desired outcome,而 dbt 处理底层 implementation。这种 abstraction 降低了编写 complex SQL code 的复杂性,并提升 readability。
Incremental builds
Efficiency 是 dbt 设计中的一个重点。它支持 incremental builds,使 data engineers 只更新 data pipeline 中受影响的部分,而不需要重新处理整个 dataset。这会加速 development,并减少 processing time。
Documentation as code
dbt 提倡将 data models 和 transformations 的 documentation 作为 code 来管理。Descriptions、explanations 和 metadata 与 project code 一起存储,使 team members 更容易理解并有效协作。
Data quality, testing, and validation
dbt 非常强调 data testing。它提供 testing framework,使 analysts 能够定义 data quality checks 和 validation rules。这包括整个 pipeline 中的 data reliability 和 quality,从而确保 data 满足 predefined criteria,并遵守 business rules。
Version control integration
与 Git 等 version control systems 的无缝集成,是 dbt 的基本特性。这一功能支持 collaborative development、change tracking,以及 roll back changes 的能力,确保 data pipelines 始终处于 version control 之下。
Native integration with data platforms
dbt 被设计为可以与 Snowflake、BigQuery 和 Redshift 等流行 data platforms 无缝协作。它利用这些 platforms 的 native capabilities 来获得 scalability 和 performance。
Open source and extensible
dbt 是一个 open source tool,并拥有活跃的社区。Users 可以通过创建 custom macros 和 packages 来扩展其 functionality。这种 extensibility 使组织能够根据自身特定 data needs 定制 dbt。
Separation of transformation and loading
dbt 将 data pipeline 中的 transformation 和 loading steps 分离开来。Data 在 dbt 中完成 transformation,然后 loaded into data platform。
本质上,dbt 的 design philosophy 扎根于为 data engineers、analysts 和 data scientists 创建一个 collaborative、code-centric 和 modular 的环境,使他们能够高效 transform data、确保 data quality,并生成 valuable insights。dbt 通过简化 data modeling 和 analytics engineering 的复杂性,使组织能够充分挖掘自身 data 的潜力。
dbt Data Flow
图 4-1 展示了 data flow 的整体图景。它识别了 dbt 及其 features 在整个 data landscape 中的位置。
图 4-1:使用 dbt 的典型 data flow,帮助你转换来自 BigQuery、Snowflake、Databricks 和 Redshift 等平台的数据。支持的数据平台请参考 dbt documentation。
如前所述,dbt 的主要目的是帮助你转换 data platforms 中的数据。为实现这一目标,dbt 提供了两个工具:
dbt Cloud
dbt Core:由 dbt Labs 维护的 open source CLI tool,你可以在 managed environments 中设置它,也可以在本地运行。
让我们通过一个示例看看 dbt 在真实场景中如何工作,以及它能做什么。想象我们正在处理一个 pipeline,它会周期性地从 BigQuery 等 data platform 中 extract data。然后,它通过组合 tables 来 transform data(图 4-2)。
我们将把前两个 tables 合并为一个,并应用多个 transformation techniques,例如 data cleaning 或 consolidation。这个阶段发生在 dbt 中,因此我们需要创建一个 dbt project 来完成这次 merge。我们稍后会做到这一点,但先熟悉 dbt Cloud,以及如何设置 working environment。
NOTE
本书将使用 dbt Cloud 来编写代码,因为它是从 development 到 writing tests、scheduling、deployments 和 investigating data models 的最快、最可靠的 dbt 入门方式。此外,dbt Cloud 运行在 dbt Core 之上,因此在使用 dbt Cloud 时,我们也会熟悉 dbt Core CLI tool 中使用的相同 commands。
图 4-2:使用 dbt 的 data pipeline use case
dbt Cloud
dbt Cloud 是 dbt 的 cloud-based version,提供广泛 features 和 services,用于编写 analytics code 并将其产品化。dbt Cloud 允许你 schedule dbt jobs、monitor progress,并实时查看 logs 和 metrics。dbt Cloud 还提供高级 collaboration features,包括 version control、testing 和 documentation。此外,dbt Cloud 可以与 Snowflake、BigQuery 和 Redshift 等多种 cloud data warehouses 集成,使你可以轻松 transform data。
你可以使用 dbt Core 实现大多数上述 features,但它需要你在自己的 infrastructure 上完成 configuration 和 setup,类似于为 Airflow 等工具运行自己的 server 或 Amazon Elastic Compute Cloud(EC2)instance。这意味着你需要自主维护和管理它,类似于管理 EC2 上的 virtual machine(VM)。
相比之下,dbt Cloud 像 managed service 一样运行,类似 Amazon Managed Workflows for Apache Airflow(MWAA)。它提供便利性和易用性,因为许多 operational aspects 已经由平台处理,使你能够更多专注于 analytics tasks,而不是 infrastructure management。
Setting Up dbt Cloud with BigQuery and GitHub
学习一项具体技术,最好的方式就是实践。因此,让我们设置将用于应用知识的环境。首先,注册一个 dbt account。
注册之后,我们会进入 Complete Project Setup 页面(图 4-3)。
图 4-3:用于完成 project setup 的 dbt landing page
这个页面有多个 sections,用于正确配置我们的 dbt project,包括连接到目标 data platform 和 code repository。我们将使用 BigQuery 作为 data platform,并使用 GitHub 存储代码。
在 BigQuery 中,第一步是设置一个新 project。在 GCP 中,在 search bar 中搜索 Create a Project,并点击它(图 4-4)。
图 4-4:BigQuery project setup,第 1 步
随后会出现类似图 4-5 的 screen,你可以在其中设置 project。我们将其命名为 dbt-analytics-engineer。
图 4-5:BigQuery project setup,第 2 步
配置完成后,进入 BigQuery IDE,你同样可以使用 search bar。它应该类似图 4-6。
图 4-6:BigQuery IDE
最后,测试 dbt public dataset,确保 BigQuery 正常工作。为此,将 Example 4-1 中的代码复制到 BigQuery 中,然后点击 Run。
Example 4-1:BigQuery 中的 dbt public datasets
select * from `dbt-tutorial.jaffle_shop.customers`;
select * from `dbt-tutorial.jaffle_shop.orders`;
select * from `dbt-tutorial.stripe.payment`;
如果你看到图 4-7 中的页面,那么说明成功了!
NOTE
由于我们同时执行了三个 queries,因此不会直接看到 output results。若要查看结果,请点击 View Results,逐个检查 query output。
图 4-7:BigQuery dataset output
现在,让我们把 dbt 与 BigQuery 连接起来,并在 dbt IDE 中执行这些 queries。为了让 dbt 连接到你的 data platform,你需要生成一个 keyfile,类似于在大多数其他 data platforms 中使用 database username 和 password。
进入 BigQuery console。在继续下一步之前,确保你在 header 中选择了新 project。如果看不到你的 account 或 project,请点击右侧 profile picture,确认你使用的是正确的 email account:
- 进入 IAM & Admin,并选择 Service Accounts。
- 点击 Create Service Account。
- 在 name field 中输入
dbt-user,然后点击 Create and Continue。 - 在 “Grant this service account access to project” 中,在 role field 选择 BigQuery Admin。点击 Continue。
- 在 “Grant users access to this service account” section 中保持 fields 为空,并点击 Done。
屏幕应类似图 4-8。
图 4-8:BigQuery Service Accounts screen
继续执行剩余步骤:
- 点击刚刚创建的 service account。
- 选择 Keys。
- 点击 Add Key;然后选择 “Create new key”。
- 选择 JSON 作为 key type;然后点击 Create。
- 系统会提示你下载 JSON file。将其保存到本地容易记住的位置,并使用清晰 filename,例如
dbt-analytics-engineer-keys.json。
现在回到 dbt Cloud,完成最终设置:
- 在 project setup screen,为你的 project 设置一个更详细的名称。我们选择
dbt-analytics-engineer。 - 在 “Choose a warehouse” screen 中,点击 BigQuery icon,然后点击 Next。
- 上传之前生成的 JSON file。为此,点击图 4-9 中可见的 “Upload a Service Account JSON file” 按钮。
- 最后,上传文件后,完成剩余步骤:滚动到底部并点击 “test”。如果你看到 “Your test completed successfully”,如图 4-10 所示,就可以继续了。现在点击 Next。另一方面,如果 test failed,很可能是 BigQuery credentials 出现了问题。尝试重新生成一次 credentials。
图 4-9:dbt Cloud 提交 BigQuery Service Account screen
图 4-10:dbt 与 BigQuery connection test
最终步骤是设置 GitHub。不过首先,让我们理解这里讨论的内容。GitHub 是一个流行的 version control platform,用于托管 Git repositories,使你可以跟踪 code changes,并与他人有效协作。要正确使用 Git,遵循以下原则和 best practices 非常重要:
Commit often, commit early
频繁 commit,即使是 small changes。这样有助于 tracking progress,并简化 debugging。每个 commit 应代表一个 logical change 或 feature。
Use meaningful commit messages
编写简洁且描述性强的 commit messages。一条好的 commit message 应解释改了什么,以及为什么改。
Follow a branching strategy
为不同 features、bug fixes 或 development tasks 使用 branches。
Pull before push
在 push changes 前,始终先从 remote repository 拉取最新 changes,例如 git pull。这可以减少 conflicts,并确保你的 changes 基于最新 code。
Review code before committing
如果你的 team 执行 code reviews,确保在 committing 前 review 并 test changes。这有助于保持 code quality。
Use .gitignore
创建 .gitignore file,指定应从 version control 中排除的 files 和 directories,例如 build artifacts、temporary files。
Use atomic commits
让 commits 聚焦于一个 single、specific change。避免在同一个 commit 中混入无关 changes。
Rebase instead of merge
使用 git rebase 将 feature branch 中的 changes 集成到 main branch,而不是传统 merge。这会产生更干净的 commit history。
Keep commit history clean
避免提交 “work in progress” 或 debugging statements。使用 git stash 等工具临时保存 unfinished work。
Use tags
创建 tags,例如 version tags,用于标记 project history 中的重要节点,例如 releases 或 major milestones。
Collaborate and communicate
与 team 沟通 Git workflows 和 conventions。建立处理 issues、pull requests 和 conflict resolution 的 guidelines。
Know how to undo changes
学习在需要时如何 revert commits(git revert)、reset branches(git reset),以及恢复 lost work(git reflog)。
Document
在 README 或 contributing guidelines 中记录 project 的 Git workflow 和 conventions,以便有效 onboarding new team members。
Use backup and remote repositories
定期备份 Git repositories,并使用 GitHub 等 remote repositories 进行 collaboration 和 redundancy。
Continue learning
Git 是一个功能丰富的强大工具。持续学习并探索 advanced Git concepts,例如 cherry-picking、interactive rebasing 和 custom hooks,以改进 workflow。
为了在实践中更好理解一些常见 Git terms 和 commands,让我们看表 4-1。
表 4-1:Git terms and commands
| Term / command | Definition | Git command |
|---|---|---|
| Repository(repo) | 类似 project folder,包含 project 的所有 files、history 和 branches。 | - |
| Branch | 独立的 development line。允许你处理 new features 或 fixes,而不影响 main codebase。 | git branch <branch_name> |
| Pull request(PR) | 你希望 merge 到 main branch 的 proposed change。它是与 team 协作和 review code changes 的方式。 | - |
| Stash | git stash 会临时保存你在 working directory 中做出的、但暂时不想 commit 的 changes。 | git stash save "Your stash message here" |
| Commit | 某一时刻 code 的 snapshot,表示你对 files 所做的一组 changes。 | git commit -m "Commit message here" |
| Add | git add 用于将 changes stage 到下一次 commit。修改 files 后,Git 不会自动将它们包含进下一次 commit,你需要明确告诉 Git 哪些 changes 应该包含。 | stage 所有 changes:git add .;也可指定 file 或 directory:git add <path/to/directory/> |
| Fork | Fork repository 是在 GitHub 上创建他人 project 的一份副本。你可以修改 forked repository,而不影响 original。 | - |
| Clone | Clone repository 是创建 remote repository 的本地副本。你可以在本地工作,并将 changes push 到 remote repository。 | git clone <repository_url> |
| Push | git push 将 local changes 上传到 remote repository。 | git push <origin branch_name> |
| Pull | git pull 用 remote repository 中的 changes 更新 local repository。 | git pull |
| Status | git status 显示 working directory 和 staging area 的当前状态。 | git status |
| Log | git log 按时间顺序显示 repository 中的 commits 列表,以及 commit messages、authors 和 commit IDs。 | git log |
| Diff | git diff command 显示两组 code 之间的差异。 | git diff |
| Merge | git merge command 将一个 branch 的 changes 合并到另一个 branch。 | git checkout <target_branch> 或 git merge <source_branch> |
| Rebase | Rebase 允许你将一系列 commits 移动或组合到新的 base commit。 | git rebase base_branch |
| Checkout | Checkout command 用于在 branches 或 commits 之间切换。 | git checkout <branch_name> |
这些 Git commands 和 terms 为 projects 中的 version control 提供了基础。不过,Git commands 通常有许多 additional arguments 和 options,可以对 version control tasks 进行 fine-tuned control。虽然我们在这里覆盖了一些 essential commands,但需要注意,Git 的 versatility 远超上述内容。
如需更完整的 Git commands 列表,以及它们可以接受的各种 arguments,建议参考官方 Git documentation。
现在你已经理解 Git 和 GitHub 是什么,以及它们在 project 中的作用,让我们建立到 GitHub 的连接。为此,你需要执行以下操作:
- 如果还没有 GitHub account,请先注册一个。
- 点击 New 创建一个新的 repository,也就是你将用于 version analytics code 的位置。在 “Create a new repository screen” 中,为 repository 命名;然后点击 “Create repository”。
- Repository 创建完成后,回到 dbt。在 Setup a Repository section 中选择 GitHub,然后连接 GitHub account。
- 点击 Configure GitHub Integration,打开一个新窗口,你可以选择安装 dbt Cloud 的位置。然后选择你想安装的 repository。
- 现在点击 “Start developing in the IDE”。你应该会看到类似图 4-11 的内容。
图 4-11:dbt IDE
我们会在 “Using the dbt Cloud IDE” 中概览 dbt Cloud Integrated Development Environment(IDE),并在 “Structure of a dbt Project” 中更详细覆盖。
点击左上角的 “Initialize dbt project”。现在,你应该能看到类似图 4-12 的 screen。
图 4-12:project initialization 后的 dbt
我们会在 “Structure of a dbt Project” 中详细说明每个 folder 和 file。现在,先看看 queries 是否能工作。复制 Example 4-2 中的代码并点击 Preview,再次运行它们。
Example 4-2:BigQuery 中的 dbt public datasets,dbt test
--select * from `dbt-tutorial.jaffle_shop.customers`;
--select * from `dbt-tutorial.jaffle_shop.orders`;
select * from `dbt-tutorial.stripe.payment`;
如果 output 类似图 4-13,说明 connection 正常。你随后就可以向自己的 data platform 提交 queries,在我们的案例中就是 BigQuery。
NOTE
这里提供的步骤来自 dbt 中 BigQuery adapter 的 documentation。随着技术持续演进和改进,这些步骤和 configurations 也可能发生变化。为了确保获得最新信息,请参考最新的 dbt BigQuery documentation。该资源会提供关于使用 dbt 和 BigQuery 的最新 guidance 和 instructions。
图 4-13:dbt 输出 BigQuery public dataset
最后,通过执行第一次 “Commit and push”,测试 GitHub integration 是否按预期工作。点击左侧带有相同描述的按钮,如图 4-14 所示。随后会出现一个 popup screen,也就是图 4-14 右侧的画面,你可以在其中编写 commit message。点击 Commit Changes。
图 4-14:Commit and push to GitHub
由于我们没有创建 Git branch,它会把代码 version 到 main branch 中。进入刚才 setup 期间创建的 GitHub repository,查看 dbt project 是否存在。图 4-15 应该与你的 GitHub repository 上看到的内容类似。
图 4-15:dbt GitHub repository,first commit check
Using the dbt Cloud UI
当你登录 dbt Cloud 时,初始页面会显示 welcome message 和 job execution history 的摘要。如图 4-16 所示,一开始该页面是空的,但一旦我们创建并运行第一个 jobs,就会开始看到信息。在 “Jobs and Deployment” 中,我们会更详细说明 job execution。
图 4-16:dbt landing page
在 top bar 上,你会看到几个选项。从左侧开始,你可以访问 Develop page,在这里开发所有 analytics code,并创建 models、tests 和 documentation。它是 dbt development 的核心,我们会在 “Using the dbt Cloud IDE” 中介绍更多内容,并在 “Structure of a dbt Project” 中深入每个 component。
紧挨着 Develop option 的是 Deploy menu,如图 4-17 所示。通过这个 menu,你可以配置 jobs,并通过 Run History 监控它们的执行;配置 development environments;还可以通过 Data Sources 验证 snapshots 的 source freshness。
图 4-17:dbt Deploy menu
Deploy menu 的第一个选项是 Run History,它会打开图 4-18 所示页面。在这里你可以看到 job run history。在 dbt context 中,jobs 是你配置的 automated tasks 或 processes,用于执行特定 actions,例如 running models、tests 或 generating documentation。这些 jobs 是 orchestrating dbt 的重要组成部分,而 orchestration 涉及管理并自动化各种 data transformation 和 analytics tasks。
图 4-18:dbt Run History page
假设你已经配置了 jobs,并且它们在该 section 中已经执行过,你就可以检查每个 job 的 invocation 和 status。Job run history 中有大量信息,包括 status、duration、job 执行的 environment,以及其他有用 details。你可以访问 job 经历的 steps 信息,包括每个 step 的相应 logs。此外,也可以找到 job 生成的 artifacts,例如 models、tests 或 documentation。
Deploy menu 的下一个选项是 Jobs。它会打开一个页面,用于配置所有 automation,包括 CI/CD pipelines、run tests 和其他令人兴奋的 behaviors,而不需要从 command line 手动运行 dbt commands。
图 4-19 展示了空的 Jobs landing page。我们在 “Jobs and Deployment” 中有完整 section 专门介绍 Jobs。
图 4-19:dbt Jobs page
Deploy menu 的第三个选项是 Environments。在 dbt 中,我们有两种主要 environment:development 和 deployment。开箱即用时,dbt 会为你配置 development environment,在你设置 dbt project 后就能看到。图 4-20 展示了 Environments landing page,如果你按照 “Setting Up dbt Cloud with BigQuery and GitHub” 中的步骤操作,你看到的页面应该类似。
图 4-20:dbt Environments page
最后是 Data Sources 选项。该页面如图 4-21 所示,一旦你配置 job 来 snapshot source-data freshness,dbt Cloud 就会自动填充这里。在这里你会看到最新 snapshots 的状态,使你能够分析 source data freshness 是否满足你与组织定义的 service-level agreements(SLAs)。我们会在 “Source freshness” 中进一步说明 data freshness,并在 “Testing sources” 中说明如何测试它。
图 4-21:dbt Data Sources page
接下来是 Documentation 选项。只要你和 team 建立 routines,确保 dbt project 被正确 documented,这一步就会具有特别重要的意义。Proper documentation 可以回答如下问题:
- 这份 data 是什么意思?
- 这份 data 来自哪里?
- 这些 metrics 是如何计算的?
图 4-22 展示了 project 的 Documentation page。我们会在 “Documentation” 中解释如何在编写代码时,在 dbt project 内利用并编写 documentation。
图 4-22:dbt Documentation page
右上角 menu 允许你选择 dbt project(图 4-23)。这个短 menu 让你可以简单地在 dbt projects 之间切换。
图 4-23:dbt Select Account menu
点击 question mark 符号可以找到 dbt Help menu(图 4-24)。在这里你可以通过 chat 直接与 dbt team 沟通、提供 feedback,并访问 dbt documentation。最后,通过 Help menu,你还可以加入 Slack dbt community 或 GitHub dbt discussions。
图 4-24:dbt Help menu
Settings menu 如图 4-25 所示,用于配置与 account、profile,甚至 notifications 相关的所有内容。
图 4-25:dbt Settings menu
点击三个选项中的任意一个后,你会进入 Settings page,类似图 4-26。在第一个页面 Account Settings 中,你可以 edit 和 create new dbt projects,管理 users 和他们的 access control level(如果你是 owner),并管理 billing。
图 4-26:dbt Account Settings page
第二个 menu option 是 Profile Settings,它会访问 Your Profile page(图 4-27)。在这个页面中,你可以查看所有 personal information,并管理 linked accounts,例如 GitHub 或 GitLab、Slack 和 single sign-on(SSO)tools。你也可以查看和编辑为 data platform 定义的 credentials 以及 API access key。
图 4-27:dbt Your Profile page
最后,Notification Settings 选项会访问 Notifications center(图 4-28),在那里你可以配置 alerts,当 job run succeeds、fails 或 is canceled 时,在指定 Slack channel 或 email 中接收通知。
图 4-28:dbt Notifications center
Using the dbt Cloud IDE
dbt Cloud 的关键部分之一是 IDE。在这里可以编写所有 analytics code,以及 tests 和 documentation。图 4-29 展示了 dbt IDE 的主要 sections。
图 4-29:dbt IDE—annotated
下面可以找到每个 section 的详细解释,以及它在 integrated development environment 中的相关性:
Git controls and documentation
这个 menu 是你与 Git 交互的位置。在这里可以看到自上次 commit 以来发生了什么变化,以及哪些内容是新的。IDE 中所有 Git commands 都在这里,你可以决定是否 commit and push,或 revert 代码。此外,在这个窗口的右上角,可以看到 documentation icon。一旦 documentation 被生成,你可以点击这个快捷方式访问 project documentation。
File Explorer
File Explorer 提供 dbt project 的主要概览。你可以在这里查看 dbt project 如何构建,通常以 .sql、.yml 和其他兼容 file types 的形式呈现。
Text editor
IDE 的这个 section 是编写并成熟化 analytics code 的地方。你也可以在这里 edit 和 create project 所需的其他 relevant files,例如 YAML files。如果你从 File Explorer 中选择这些 files,它们会出现在这里。多个 files 可以同时打开。
Information window and code Preview, Compile, and Build
当你点击 Preview 或 Compile buttons 后,这个 menu 会显示 results。Preview 会 compile 并对 data platform 运行 query,然后在屏幕底部的 Results tab 中显示结果。另一方面,Compile 会把所有 Jinja 转换成 pure SQL。结果会显示在 information window 底部的 Compiled Code tab 中。Preview 或 Compile buttons 适用于 statements 和 SQL files。
Build 是一个特殊按钮,只会在特定 files 中出现。根据你选择的 build 类型,run results 会包含所有 selected to build 的 models、tests、seeds 和 snapshots 信息,并合并到一个 file 中。
Information window 对 development 期间 troubleshooting errors 也很有帮助,或者可以使用 Lineage tab 查看当前在 text editor 中打开的 model 的 data lineage,以及它的 ancestors 和 dependencies。
Command line
Command line 是执行特定 dbt commands 的位置,例如 dbt run 或 dbt test。在 command 执行期间或之后,它也会显示一个 pop-up screen,以展示正在处理的结果。为此,可以点击 command line 开头的箭头。Logs 也可以在这里查看。图 4-30 展示了展开后的 command line:待执行 command 在顶部,随后是 execution log。
图 4-30:dbt command line expanded
Structure of a dbt Project
dbt project 是一个由 folders 和 files、programming patterns 以及 naming conventions 组成的 directory。你的所有 analytics code、tests、documentation 和告诉 dbt 如何运行的 parametrizations 都会放在这些 files 和 folders 中。它会使用这些 naming conventions 和 programming patterns。你组织 folders 和 file directory 的方式,就是你的 dbt project structure。
构建一个合适的 dbt project 需要投入。为了良好实现,它需要把 company domains 和 departments 结合起来,利用它们各自的专业知识,映射整个公司的 goals 和 needs。因此,定义一套清晰、全面且一致的 conventions 和 patterns 非常重要。做到这一点将确保随着公司扩展,project 仍然 accessible 和 maintainable,同时通过 dbt 赋能并惠及尽可能多的人。
你如何组织 dbt project 可能会有所不同,也可能会受你或 company guidelines 定义的 changes 影响。这不是问题。重要的是,你要以严格且 accessible 的方式向所有 contributors 明确声明这些 changes,并且最重要的是,保持一致。为了本书的目的,我们会保留初始化后看到的 dbt project 基础结构(Example 4-3)。
Example 4-3:dbt project 的初始结构
root/
├─ analyses/
├─ dbt_packages/
├─ logs/
├─ macros/
├─ models/
│ ├─ example/
│ │ ├─ schema.yml
│ │ ├─ my_second_dbt_model.sql
│ │ ├─ my_first_dbt_model.sql
├─ seeds/
├─ snapshots/
├─ target/
├─ tests/
├─ .gitignore
├─ dbt_project.yml
├─ README.md
每个 folder 和 file 会在本章后续 sections 和第 5 章中解释。有些会更重点强调,也会更经常使用。不过,先对它们的 purpose 有一个整体了解很重要。
analyses folder
该 folder 在 “Analyses” 中详细介绍,通常用于存储 auditing purposes 的 queries。例如,在从另一个 system 向 dbt 迁移 logic 时,你可能想查找 discrepancies,同时仍然利用 dbt 的能力,例如使用 Jinja 和 version control,但不将其包含在 data platform 中 built models 里。
dbt_packages folder
这是安装 dbt packages 的地方。我们会在 “dbt Packages” 中介绍 packages 的概念。基本想法是,packages 是 standalone dbt projects,用于解决特定问题,并可在组织之间复用和共享。这会促进更 DRY 的 code,因为你不必一遍遍实现同样 logic。
logs folder
默认情况下,所有 project logs 都会写入这里,除非你在 dbt_project.yml 中另行配置。
macros folder
这里存储用于 DRY-ing up transformations code 的内容。Macros 类似其他 programming languages 中的 functions,是可以复用多次的 Jinja code。我们会在 “Using SQL Macros” 中用完整 section 详细说明它们。
models folder
这是 dbt 中的 mandatory folders 之一。一般来说,model 是一个 SQL file,包含一个 SELECT statement 和一段 modular logic,它会将 raw data 构建成最终 transformed data。在 dbt 中,model 的名称表示未来 table 或 view 的名称;如果配置为 ephemeral model,则不会成为 table 或 view。这个主题会在 “Models” 中详细说明。
seeds folder
这里存放 lookup tables。我们会在 “Seeds” 中讨论这一点。一般来说,seeds 是变化不频繁的 CSV files,用于建模不存在于任何 source system 中的数据。一些有用的 use cases 包括将 zip codes 映射到 states,或维护需要从 analysis 中排除的 test emails 列表。
snapshots folder
包含 project 的所有 snapshot models,它们必须与 models folder 分开。dbt snapshot feature 会记录 mutable table 随时间发生的变化。它应用 type 2 slowly changing dimension(SCDs),用于识别 table 中某一行在时间中的变化。这会在 “Snapshots” 中详细覆盖。
target folder
包含运行 dbt run、dbt compile 或 dbt test commands 时生成的 compiled SQL files。你也可以在 dbt_project.yml 中选择配置为写入其他 folder。
tests folder
用于同时测试多个 specific tables。它不会是你编写 tests 的唯一 folder。大量 tests 仍然会写在 model folder 内的 YAML files 中,或者通过 macros 编写。不过,tests folder 更适合 singular tests,用于报告几个 specific models 如何相互 interact 或 relate 的结果。我们会在 “Tests” 中深入覆盖这个话题。
dbt_project.yml
这是每个 dbt project 的核心。dbt 通过它知道某个 directory 是 dbt project,它包含重要信息,告诉 dbt 如何在 project 上运行。我们会在本书过程中持续覆盖这个 file。它也会在 “dbt_project.yml” 中介绍。
.gitignore and README.md
这些 files 通常用于 Git projects。.gitignore 指定 Git 在 commit 和 push 期间应忽略的 intentional files,而 README file 是一份重要 guide,会为其他 developers 提供 Git project 的详细描述。
我们会在本章和第 5 章中更详细覆盖这些 folders,同时更深入介绍 dbt project 和 features。
Jaffle Shop Database
在本书中,我们将提供一组 practical examples,展示如何使用 dbt 的 components 和 features。在大多数情况下,我们需要开发 SQL queries,以便清楚表达我们想展示的内容。因此,有一个可以工作的 database 非常重要。这个 database 就是 Jaffle Shop。
Jaffle Shop database 是一个简单 database,由 customers 和 orders 两张 tables 组成。为了提供更多 context,我们还会有一个来自 Stripe 的 side database,其中 payments 与 orders 连接。这三张 tables 都是 raw data。
使用这个 database 的原因是,它已经由 dbt Labs 在 BigQuery 中公开提供。这是 dbt documentation 和 courses 中使用的主要 databases 之一,因此希望它能在本书当前阶段简化 dbt platform 的整体学习曲线。
图 4-31 展示了表示 raw data 的 ERD,包含 customers、orders 和 payments。
图 4-31:Jaffle Shop raw data ERD。读法如下:单个 customer(1)可以有多个 orders(N),单个 order(1)可以有多个 processing payments(N)。
YAML Files
YAML 是一种 human-readable data-serialization language,常用于 configuration files,以及需要存储或传输 data 的 applications。在 dbt 中,YAML 用于定义 dbt project components 的 properties 和部分 configurations,这些 components 包括 models、snapshots、seeds、tests、sources,甚至实际 dbt project,也就是 dbt_project.yml。
除了必须有特定名称并位于特定位置的 top-level YAML files,例如 dbt_project.yml 和 packages.yml,你如何在 dbt project 内组织其他 YAML files,取决于你自己。请记住,和 dbt project structure 的其他方面一样,最重要的原则是保持一致、清楚表达意图,并记录它为何这样组织。重要的是在 centralization 和 file size 之间取得平衡,使特定 configurations 尽可能容易找到。下面是关于如何组织、结构化和命名 YAML files 的一组建议:
如前所述,在 configuration centralization 和 file size 之间取得平衡尤其重要。把所有 configurations 放到一个 file 中,随着 project 扩展,可能会很难找到特定 configuration,尽管技术上你可以只用一个 file。由于文件的重复性,使用 Git 做 change management 也会变得复杂。
根据上一点,如果遵循 config per folder approach,从长期看更容易维护所有 configurations。换句话说,在每个 model folder directory 中,建议放一个 YAML file,用于配置该 directory 中的所有 models。可以进一步扩展这个规则,把 model configuration file 分开,在同一 directory 中为 sources configurations 设置一个特定 file(Example 4-4)。
在这个结构中,我们使用 staging models 表示正在讨论的内容,因为它覆盖了大多数情况,例如 sources 和 YAML files。这里可以看到 config per folder system,其中 source 和 model configurations 被分开。它还引入了用于 documentation 的 Markdown files,我们会在 “Documentation” 中更详细讨论。最后,文件开头的 underscore 会把这些 files 放在对应 directory 的顶部,使它们更容易找到。
Example 4-4:model directory 中的 dbt YAML files
root/
├─ models/
│ ├─ staging/
│ │ ├─ jaffle_shop/
│ │ │ ├─ _jaffle_shop_docs.md
│ │ │ ├─ _jaffle_shop_models.yml
│ │ │ ├─ _jaffle_shop_sources.yml
│ │ │ ├─ stg_jaffle_shop_customers.sql
│ │ │ ├─ stg_jaffle_shop_orders.sql
│ │ ├─ stripe/
│ │ │ ├─ _stripe_docs.md
│ │ │ ├─ _stripe_models.yml
│ │ │ ├─ _stripe_sources.yml
│ │ │ ├─ stg_stripe_order_payments.sql
├─ dbt_project.yml
使用 documentation blocks 时,也应遵循同样方法,为每个 models directory 创建一个 Markdown file(.md)。在 “Documentation” 中,我们会更好地了解这类 file。
建议你在 dbt_project.yml file 中以 directory level 设置 dbt project 的 default configurations,并使用 cascading scope priority 定义这些 configurations 的 variations。这可以帮助你简化 dbt project management,并确保 configurations 保持一致且易维护。例如,利用 Example 4-4,假设我们所有 staging models 默认都配置为 materialized as a view。这会写在 dbt_project.yml 中。但如果某个特定 use case 需要修改 jaffle_shop staging models 的 materialization configuration,就可以通过修改 _jaffle_shop_models.yml file 完成。这样,你可以为这组特定 models 自定义 materialization configuration,同时保持 project 其他 configurations 不变。
之所以能够 override 特定 models 的 default configurations,是因为 dbt project build 使用了 cascading scope priority。虽然所有 staging models 会因默认 configuration 而 materialized as views,但 staging jaffle_shop models 会因为我们更新了特定 _jaffle_shop_models.yml YAML file 而 materialized as tables。
dbt_project.yml
dbt 中最关键的 files 之一是 dbt_project.yml。这个 file 必须位于 project root,它是 project 的 main configuration file,包含 dbt 正确运行所需的相关信息。
dbt_project.yml file 在编写更 DRY 的 analytics code 时也有相关性。一般来说,project default configurations 会存储在这里,除非在 model level override,否则所有 objects 都会继承这些配置。
以下是你会在这个 file 中遇到的一些最重要 fields:
name
Mandatory。dbt project 的名称。建议将这个 configuration 改为你的 project name。同时记得在 model section 和 dbt_project.yml file 中都修改它。在我们的案例中,将其命名为 dbt_analytics_engineer_book。
version
Mandatory。Project 的 core version,不同于 dbt version。
config-version
Mandatory。Version 2 是当前可用版本。
profile
Mandatory。dbt 中的 profile 用于连接到 data platform。
[folder]-paths
Optional。其中 [folder] 是 dbt project 中 folders 的列表。可以是 model、seed、test、analysis、macro、snapshot、log 等。例如,model-paths 会声明 models 和 sources 的 directory。macro-paths 是 macros code 所在位置,依此类推。
target-path
Optional。这个 path 会存储 compiled SQL file。
clean-targets
Optional。包含 dbt clean command 要移除 artifacts 的 directories 列表。
models
Optional。Models 的 default configuration。在 Example 4-5 中,我们希望 staging folder 内的所有 models 都 materialized as views。
Example 4-5:dbt_project.yml,model configuration
models:
dbt_analytics_engineer_book:
staging:
materialized: view
packages.yml
Packages 是 standalone dbt projects,用于处理特定问题,并可在组织之间复用和共享。它们是包含 models 和 macros 的 projects。将它们添加到你的 project 后,这些 models 和 macros 会成为 project 的一部分。
要访问这些 packages,你首先需要在 packages.yml file 中定义它们。详细步骤如下:
- 确保
packages.ymlfile 位于你的 dbt project 中。如果没有,请在与dbt_project.ymlfile 同一级别的位置创建它。 - 在 dbt
packages.ymlfile 中,定义你希望在 dbt project 内可用的 packages。你可以从 dbt Hub、Git repositories(例如 GitHub 或 GitLab),甚至本地存储的 packages 中安装 packages。Example 4-6 展示了这些场景所需的 syntax。 - 运行
dbt deps安装已定义 packages。除非你另行配置,否则默认情况下这些 packages 会安装在dbt_packagesdirectory 中。
Example 4-6:从 dbt hub、Git 或本地安装 packages 的 syntax
packages:
- package: dbt-labs/dbt_utils
version: 1.1.1
- git: "https://github.com/dbt-labs/dbt-utils.git"
revision: 1.1.1
- local: /opt/dbt/bigquery
profiles.yml
如果你决定使用 dbt CLI,并在本地运行 dbt project,就需要设置 profiles.yml。如果使用 dbt Cloud,则不需要该 file。这个 file 包含 dbt 将用于连接 data platform 的 database connection。由于其内容敏感,该 file 位于 project 之外,以避免 credentials 被 version 到 code repository 中。如果 credentials 存储在 environment variables 中,则可以安全使用 code versioning。
当你从本地 environment 调用 dbt 时,dbt 会 parse dbt_project.yml file 并获取 profile name,dbt 需要它来连接到 data platform。你可以按需设置多个 profiles,不过通常是每个 dbt project 或每个 data platform 一个 profile。即使本书使用 dbt Cloud,因此不需要 profiles configuration,我们仍展示一个 profiles.yml sample,方便你了解,或者你更喜欢用 dbt CLI 连接 BigQuery。
profiles.yml 的典型 YAML schema 如 Example 4-7 所示。本书使用 dbt Cloud,因此不需要 profiles configuration。不过,如果你感兴趣,或更想使用 dbt CLI 配合 BigQuery,这里展示了一个 profiles.yml sample。
Example 4-7:profiles.yml
dbt_analytics_engineer_book:
target: dev
outputs:
dev:
type: bigquery
method: service-account
project: [GCP project id]
dataset: [the name of your dbt dataset]
threads: [1 or more]
keyfile: [/path/to/bigquery/keyfile.json]
<optional_config>: <value>
profiles.yaml 最常见结构包含以下 components:
profile_name
Profile 的名称必须等于 dbt_project.yml 中的名称。在我们的案例中,命名为 dbt_analytics_engineer_book。
target
这是为不同 environments 设置不同 configurations 的方式。例如,在本地开发时,你可能希望使用独立 datasets/databases;但在部署到 production 时,最好让所有 tables 位于一个 single dataset/database 中。默认情况下,target 设置为 dev。
type
你要连接的 data platform 类型:BigQuery、Snowflake、Redshift 等。
database-specific connection details
Example 4-7 包含 method、project、dataset 和 keyfile 等 attributes,这些是使用这种方式建立 BigQuery connection 所需的内容。
threads
dbt project 将运行的线程数。它会创建 models 之间 links 的 DAG。Threads 数量表示 dbt 可以并行处理 graph 中的最大 paths 数量。例如,如果指定 threads: 1,dbt 会开始构建一个 resource(models、tests 等),完成后再进入下一个。另一方面,如果设置为 threads: 4,dbt 可以在不违反 dependencies 的情况下同时处理最多四个 models。
NOTE
这里展示的是 profiles.yml file 的总体思路。我们不会进一步展开,也不会提供如何配置本地 dbt project 与 BigQuery 的详细 setup guide。大多数任务已经在 “Setting Up dbt Cloud with BigQuery and GitHub” 中介绍过,例如 keyfile generation,但可能还有一些细微差异。如果想了解更多,dbt 提供了完整 guide。
Models
在 dbt ecosystem 中,models 是你作为 data specialist 花费最多时间的地方。它们通常写成 select statements,并保存为 .sql,是 dbt 中最重要的部分之一,帮助你在 data platform 内 transform data。
要正确构建 models,并创建清晰一致的 project structure,你需要熟悉 data modeling concept 和 techniques。如果你的目标是成为 analytics engineer,或者更一般地说,成为想从事 data 工作的人,这是核心知识。
正如第 2 章所见,data modeling 是一个通过分析和定义 data requirements,创建 data models 来支持组织 business processes 的过程。它将 source data,也就是公司收集和产生的数据,塑造成 transformed data,以回答公司 domains 和 departments 的 data needs,并创造 added value。
与 data modeling 一致,并且也如第 2 章所介绍,modularity 是另一个关键概念,对正确组织 dbt project 和 models,同时让代码更 DRY 至关重要。从概念上说,modularity 是将问题分解为一组可以被 separated 和 recombined 的 modules 的过程,它能降低 system 的整体复杂性,并通常带来 flexibility 和 variety of use。在 analytics 中也一样。构建 data product 时,我们不会一次性写完所有代码,而是 piece by piece 构建,直到达到最终 data artifacts。
由于我们希望从一开始就体现 modularity,因此初始 models 也会以 modularity 为思路构建,并遵循第 2 章讨论的内容。按照典型 dbt data transformation flow,我们的 model directory 中会有三层:
Staging layer
Initial modular building blocks 位于 dbt project 的 staging layer 中。在这一层,我们建立与 source systems 的接口,类似 API 与 external data sources 交互。这里 data 会被 reordered、cleaned up,并为 downstream processing 做准备。这包括 data standardization 和 minor transformations 等任务,为 downstream 更高级 data processing 打基础。
Intermediate layer
这一层由位于 staging layer 和 marts layer 之间的 models 组成。这些 models 构建在 staging models 之上,用于执行 extensive data transformations,以及来自多个 sources 的 data consolidation,从而创建服务于不同目的的 intermediate tables。
Marts layer
根据你采用的 data modeling technique,marts 会将所有 modular pieces 汇总起来,提供公司关心的 entities 的更广阔视角。例如,如果选择 dimensional modeling technique,marts layer 会包含 fact 和 dimension tables。在这个 context 中,facts 是随着时间持续发生的 occurrences,例如 orders、page clicks 或 inventory changes,以及对应 measures。Dimensions 是可以描述这些 facts 的 attributes,例如 customers、products 和 geography。Marts 可以被描述为 data platform 内面向特定 domains 或 departments 的 data subsets,例如 finance、marketing、logistics、customer service 等。也可以创建一个名为 “core” 的 mart,例如它并不面向特定 domain,而是包含 core business facts 和 dimensions。
完成介绍后,现在让我们构建第一个 models,初始只在 staging layer 中。请在 models folder 中创建一个新 folder,命名为 staging,并在 staging folder 中为每个 source 创建相应 folders:jaffle_shop 和 stripe。然后创建必要 SQL files:stg_stripe_order_payments.sql(Example 4-8)、stg_jaffle_shop_customers.sql(Example 4-9),以及 stg_jaffle_shop_orders.sql(Example 4-10)。最后,删除 models 中的 example folder。它没有必要,而且在编码时会造成不必要 visual noise。Folder structure 应类似 Example 4-11。
Example 4-8:stg_stripe_order_payments.sql
select
id as payment_id,
orderid as order_id,
paymentmethod as payment_method,
case
when paymentmethod in ('stripe'
, 'paypal'
, 'credit_card'
, 'gift_card')
then 'credit'
else 'cash'
end as payment_type,
status,
amount,
case
when status = 'success'
then true
else false
end as is_completed_payment,
created as created_date
from `dbt-tutorial.stripe.payment`
Example 4-9:stg_jaffle_shop_customers.sql
select
id as customer_id,
first_name,
last_name
from `dbt-tutorial.jaffle_shop.customers`
Example 4-10:stg_jaffle_shop_orders.sql
select
id as order_id,
user_id as customer_id,
order_date,
status,
_etl_loaded_at
from `dbt-tutorial.jaffle_shop.orders`
Example 4-11:Staging models 的 folder structure
root/
├─ models/
│ ├─ staging/
│ │ ├─ jaffle_shop/
│ │ │ ├─ stg_jaffle_shop_customers.sql
│ │ │ ├─ stg_jaffle_shop_orders.sql
│ │ ├─ stripe/
│ │ │ ├─ stg_stripe_order_payments.sql
├─ dbt_project.yml
现在执行并验证我们所做的内容。通常,在 command line 输入 dbt run 就足够了,但在 BigQuery 中,可能需要输入 dbt run --full-refresh。之后,使用 command line 左侧的箭头查看 logs。Logs 应该类似图 4-32。
图 4-32:dbt system logs
TIP
如果出了问题,你的 logs 也应该能很好地提示问题。在图 4-32 中,我们展示了 logs summary,但你也可以查看 detailed logs,获得更多 verbosity。
假设你已经收到 “Completed successfully” message,现在来看 BigQuery,你应该看到所有三个 models 都已经 materialized,如图 4-33 所示。
图 4-33:dbt BigQuery models
默认情况下,dbt 会在 data platform 中把 models materialize 为 views。不过,你可以在 model file 顶部的 configuration block 中轻松配置这一点(Example 4-12)。
Example 4-12:model file 内的 materialization config
{{
config(
materialized='table'
)
}}
SELECT
id as customer_id,
first_name,
last_name
FROM `dbt-tutorial.jaffle_shop.customers`
现在我们已经创建了第一个 models,进入下一步:使用 YAML files 重新整理 code,并遵循 “YAML Files” 中建议的 best practices。让我们使用那里提到的 code block,并在 YAML files 中配置 materializations(Example 4-12)。第一个要修改的是 dbt_project.yml。它应该是 default configurations 的 core YAML file。因此,让我们用 Example 4-13 中的代码修改其中的 models configuration,然后再次执行 dbt run。
Example 4-13:Materialize models as views and as tables
models:
dbt_analytics_engineer_book:
staging:
jaffle_shop:
+materialized: view
stripe:
+materialized: table
NOTE
+ prefix 是 dbt syntax enhancement,从 dbt v0.17.0 引入,用于在 dbt_project.yml files 中更清晰地表达 resource paths 和 configurations。
由于 Example 4-13 强制所有 staging Stripe models materialized as table,因此 BigQuery 应该类似图 4-34。
图 4-34:dbt BigQuery models with materialized table
Example 4-13 展示了如何在 dbt_project.yml 中按 folder 配置特定 desired materializations。你的 staging models 默认会保持为 views,因此可以在 model folder level 利用 project build 中的 cascading scope priority 来 override 这个 configuration。首先,让我们修改 dbt_project.yml,将所有 staging models 设置为 materialized as views,如 Example 4-14 所示。
Example 4-14:Staging models materialized as views
models:
dbt_analytics_engineer_book:
staging:
+materialized: view
现在,为 stg_jaffle_shop_customers 创建单独的 YAML file,声明它需要 materialized as table。为此,在 staging/jaffle_shop directory 中创建相应 YAML file,名称为 _jaffle_shop_models.yml,并复制 Example 4-15 中的代码。
Example 4-15:定义 model materialized as table
version: 2
models:
- name: stg_jaffle_shop_customers
config:
materialized: table
重新运行 dbt 后,查看 BigQuery。它应该类似图 4-35。
图 4-35:dbt BigQuery customers model materialized into a table
这是一个使用 YAML files、尝试 table materializations,并理解 cascading scope priority 在实践中含义的简单示例。后面还有许多内容要做和看,其中一些讨论内容随着继续推进会更有适用性。目前,只需要你将 _jaffle_shop_models.yml 中的 model 改为 materialized as view。这将作为你的 default configuration。
希望到这个阶段,你已经开发了第一个 models,并大致理解 YAML files 的整体目的以及 cascading scope priority。接下来的步骤是创建 intermediate 和 mart models,同时学习 ref() functions。这将是我们第一次使用 Jinja,后面会在 “Dynamic SQL with Jinja” 中更详细介绍。
首先是我们的 use case。有了 staging area 中的 models 之后,我们需要知道要用它们做什么。正如本节开头所说,你需要定义支持组织 business processes 的 data requirements。作为 business user,我们可以从 data 中衍生多个 streams。其中一个 stream,也是我们的 use case,是分析每个 customer 的 orders,展示每个 successful order 的 total amount paid,以及每个 successful order type(cash 和 credit)的 total amount。
由于这里有一些 transformations 需要从 payment type level 转换到 order grain,这证明了在进入 marts layer 之前隔离这个 complex operation 的合理性。这就是 intermediate layer 的作用。在 models folder 中创建一个名为 intermediate 的新 folder。在其中创建一个名为 int_payment_type_amount_per_order.sql 的 SQL file,并复制 Example 4-16 中的代码。
Example 4-16:int_payment_type_amount_per_order.sql
with order_payments as (
select * from {{ ref('stg_stripe_order_payments') }}
)
select
order_id,
sum(
case
when payment_type = 'cash' and
status = 'success'
then amount
else 0
end
) as cash_amount,
sum(
case
when payment_type = 'credit' and
status = 'success'
then amount
else 0
end
) as credit_amount,
sum(case
when status = 'success'
then amount
end
) as total_amount
from order_payments
group by 1
可以看到,在创建 order_payments CTE 时,我们使用 ref() function 从 stg_stripe_order_payments 收集 data。这个 function 引用了构建 data platform 的 upstream tables 和 views。我们会把这个 function 作为实现 analytics code 时的标准做法,因为它带来以下好处:
它允许你以灵活方式在 models 之间建立 dependencies,并可在 common codebase 中共享,因为它会在 dbt run 期间编译 database object 的名称,并从你创建 project 时的 environment configuration 中收集该名称。这意味着,在你的 environment 中,code 会基于你的 environment configurations 编译,这些配置存在于你自己的 development environment 中,但可能不同于使用不同 development environment、但共享相同 codebase 的 teammate。
你可以构建 lineage graphs,在其中可视化某个 specific model 的 data flow 和 dependencies。我们稍后会在本章讨论这个内容,并且它也在 “Documentation” 中覆盖。
最后,需要说明的是,尽管前面的代码因为重复的 CASE WHEN conditions 看起来可能像 anti-pattern,但必须澄清,整个 dataset 包含所有 orders,不论其 payment status 如何。不过,在这个示例中,我们选择只对与已达到 “success” status 的 orders 相关 payments 做 financial analysis。
Intermediate table 构建完成后,让我们进入最终 layer。考虑前面描述的 use case,我们需要从 customer perspective 分析 orders。这意味着必须创建一个 customer dimension,并将其与 fact table 连接。由于当前 use case 可以服务多个 departments,我们不会创建 specific department folder,而是创建一个名为 core 的 folder。因此,首先在 models folder 中创建 marts/core directory。然后将 Example 4-17 复制到一个名为 dim_customers.sql 的新 file 中,将 Example 4-18 复制到一个名为 fct_orders.sql 的新 file 中。
Example 4-17:dim_customers.sql
with customers as (
select * from {{ ref('stg_jaffle_shop_customers')}}
)
select
customers.customer_id,
customers.first_name,
customers.last_name
from customers
Example 4-18:fct_orders.sql
with orders as (
select * from {{ ref('stg_jaffle_shop_orders' )}}
),
payment_type_orders as (
select * from {{ ref('int_payment_type_amount_per_order' )}}
)
select
ord.order_id,
ord.customer_id,
ord.order_date,
pto.cash_amount,
pto.credit_amount,
pto.total_amount,
case
when status = 'completed'
then 1
else 0
end as is_order_completed
from orders as ord
left join payment_type_orders as pto ON ord.order_id = pto.order_id
所有 files 创建完成后,在 dbt_project.yml 中设置 default configurations,如 Example 4-19 所示,然后执行 dbt run,或在 BigQuery 中可能执行 dbt run --full-refresh。
Example 4-19:dbt_project.yml 中按 layer 的 model configuration
models:
dbt_analytics_engineer_book:
staging:
+materialized: view
intermediate:
+materialized: view
marts:
+materialized: table
TIP
如果你收到类似 “Compilation Error in rpc request… depends on a node named int_payment_type_amount_per_order which was not found” 的 error message,这意味着有一个 model 依赖你正在 preview 的 model,但它还没有进入 data platform。在我们的案例中是 int_payment_type_amount_per_order。要解决这个问题,进入该 particular model,并执行 dbt run --select MODEL_NAME command,将 MODEL_NAME 替换为相应 model name。
如果一切运行成功,你的 data platform 应该已经用所有 dbt models 完全更新。查看 BigQuery,它应类似图 4-36。
图 4-36:包含所有 models 的 dbt BigQuery
最后,打开 fct_orders.sql,查看 information window 中的 Lineage 选项(图 4-37)。这是我们会在 “Documentation” 中覆盖的优秀功能之一,它能让我们很好地了解 feed 某个 specific model 的 data flow,以及它的 upstream 和 downstream dependencies。
图 4-37:dbt fct_orders data lineage
Sources
在 dbt 中,sources 是 data platform 中可用的 raw data,由通用 extract-and-load(EL)tool 捕获。区分 dbt sources 和传统 data sources 非常重要。传统 data source 可以是 internal 或 external。Internal data sources 提供支持组织日常 business operations 的 transactional data。Customer、sales 和 product data 都是 internal data source 中可能包含的内容。另一方面,external data sources 提供源自组织外部的数据,例如来自 business partners、internet 和 market research 等的数据。通常这些 data 与 competitors、economics、customer demographics 等相关。
dbt sources 根据 business demand 依赖 internal 和 external data,但在定义上不同。如前所述,dbt sources 是 data platform 内的 raw data。这些 raw data 通常由 data engineering teams 使用 EL tool 带入 data platform,并成为 analytical platform 运行的基础。
在 “Models” 中,我们的 models 通过 hardcoded strings 引用 sources,例如 dbt-tutorial.stripe.payment 或 dbt-tutorial.jaffle_shop.customers。即使这样可以工作,也要考虑:如果 raw data 发生变化,例如 location 或 table name 因符合特定 naming conventions 而改变,那么在多个 files 中做修改会很困难且耗时。这正是 dbt sources 发挥作用的地方。它们允许你在 YAML file 中记录这些 source tables,并在其中引用 source database、schema 和 tables。
让我们把它付诸实践。遵循 “YAML Files” 中建议的 best practices,在 models/staging/jaffle_shop directory 中创建一个新的 YAML file,命名为 _jaffle_shop_sources.yml,并复制 Example 4-20 中的代码。然后,在 models/staging/stripe directory 中创建另一个 YAML file,命名为 _stripe_sources.yml,并复制 Example 4-21 中的代码。
Example 4-20:_jaffle_shop_sources.yml,Jaffle Shop schema 下所有 tables 的 sources parametrization file
version: 2
sources:
- name: jaffle_shop
database: dbt-tutorial
schema: jaffle_shop
tables:
- name: customers
- name: orders
Example 4-21:_stripe_sources.yml,stripe schema 下所有 tables 的 sources parametrization file
version: 2
sources:
- name: stripe
database: dbt-tutorial
schema: stripe
tables:
- name: payment
YAML files 配置完成后,我们需要对 models 做最后修改。不要 hardcode sources,而是使用一个名为 source() 的新 function。它的工作方式类似于 “Referencing data models” 中介绍的 ref() function,但不是 {{ ref("stg_stripe_order_payments") }},配置 source 时我们现在传入类似 {{ source("stripe", "payment") }} 的内容。在这个特定案例中,它会引用 Example 4-21 中创建的 YAML file。
现在动手操作。将之前创建的所有 SQL staging model code 替换为 Example 4-22 中的相应代码。
Example 4-22:使用 source() function 的 payments、orders 和 customers staging models
-- REPLACE IT IN stg_stripe_order_payments.sql
select
id as payment_id,
orderid as order_id,
paymentmethod as payment_method,
case
when paymentmethod in ('stripe'
,'paypal'
, 'credit_card'
, 'gift_card')
then 'credit'
else 'cash'
end as payment_type,
status,
amount,
case
when status = 'success'
then true
else false
end as is_completed_payment,
created as created_date
from {{ source('stripe', 'payment') }}
-- REPLACE IT IN stg_jaffle_shop_customers.sql file
select
id as customer_id,
first_name,
last_name
from {{ source('jaffle_shop', 'customers') }}
-- REPLACE IT IN stg_jaffle_shop_orders.sql
select
id as order_id,
user_id as customer_id,
order_date,
status,
_etl_loaded_at
from {{ source('jaffle_shop', 'orders') }}
将 models 切换为使用 source() function 后,可以运行 dbt compile,或在 IDE 中点击 Compile 按钮,查看代码在 data platform 中如何执行。在 backend,dbt 会查看 referenced YAML file,并将 source() function 替换为直接 table reference,如图 4-38 所示。
图 4-38:dbt customers staging model 使用 source() function 及对应 compiled code。Compiled code 是将在 data platform 中运行的代码。
使用 source() function 的另一个好处是,现在可以在 lineage graph 中看到 sources。以 fct_orders.sql lineage 为例。图 4-37 中显示的相同 lineage 现在应类似图 4-39。
图 4-39:带 sources 的 dbt fct_orders data lineage
Source freshness
Data freshness 是 data quality 的关键方面。如果 data 不是 up-to-date,它就是 obsolete,这可能会给公司的 decision-making process 造成重大问题,因为它可能导致 inaccurate insights。
dbt 允许你通过 source freshness test 缓解这种情况。为此,我们需要一个 audit field,用于声明 data platform 中某个 specific data artifact 的 loaded timestamp。借助这个 field,dbt 就能测试 data 有多旧,并根据指定 conditions 触发 warning 或 error。
为了实现这一点,让我们回到 source YAML files。在这个特定示例中,我们会使用 data platform 中的 orders data,因此按推断,我们将用 Example 4-23 中的代码替换 _jaffle_shop_sources.yml 的代码。
Example 4-23:_jaffle_shop_sources.yml,Jaffle Shop schema 下所有 tables 的 sources parametrization file,带 source freshness test
version: 2
sources:
- name: jaffle_shop
database: dbt-tutorial
schema: jaffle_shop
tables:
- name: customers
- name: orders
loaded_at_field: _etl_loaded_at
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
可以看到,我们使用了 data platform 中的 _etl_loaded_at field。我们不需要把它带入 transformation process,因为它对 forward models 没有 added value。这不是问题,因为我们测试的是 upstream data,也就是本案例中的 raw data。在 YAML file 中,我们创建了两个 additional properties:loaded_at_field 表示 source freshness test 下要监控的 field;freshness 表示监控 source freshness 的实际 rules。在 freshness property 内,我们配置为:如果 data 过时 12 小时,则通过 warn_after property 触发 warning;如果 data 在过去 24 小时内没有刷新,则通过 error_after property 触发 actual error。
最后,看看执行 dbt source freshness command 会发生什么。在我们的案例中,出现了 warning,如图 4-40 所示。
图 4-40:dbt orders raw data 和 source freshness test logs
如果查看 log details,可以看到在 data platform 中执行的 query,并进行 troubleshoot。这个 particular warning 是预期中的。_etl_loaded_at 被构造为距离当前时间 16 小时,因此任何低于该值的设置都会触发 warning。如果想继续尝试,可以把 warn_after 改得更高,比如 17 小时。这样所有 tests 都应通过。
希望到这里 source freshness 概念已经清楚了。后面本书还会回到这个话题,并展示如何自动化和 snapshot source freshness tests。与此同时,重要的是理解它在整体 test landscape 中的 purpose、如何配置,以及这个 test 对缓解 data quality issues 有多重要。
Tests
作为 analytics engineer,你必须确保 data accurate 和 reliable,从而建立对所交付 analytics 的信任,并为组织提供 objective insights。所有人都同意这一点。不过,即使你遵循所有 engineering state-of-the-art best practices,也总会存在 exceptions。尤其是在处理 data 这种 volatile 对象时,例如 variations、type、structure 等。
捕获这些 exceptions 有很多方式。然而,当你处理大量 data 时,需要思考一种 scalable approach,能够分析 large datasets 并快速识别这些 exceptions。这正是 dbt 发挥作用的地方。
dbt 允许你在 data workflow 中快速且轻松地 scale tests,使你能够在其他任何人发现问题之前识别 things break。在 development environment 中,你可以使用 tests 确保 analytics code 产生 desired output。在 deployment / production environment 中,你可以自动化 tests,并设置 alert,在某个 specific test failed 时通知你,从而快速响应并修复,避免它产生更严重后果。
作为 data practitioner,理解 dbt 中的 tests 可以总结为对 data 的 assertions 非常重要。当你在 data models 上运行 tests 时,你是在断言这些 data models 产生 expected output,这是确保 data quality 和 reliability 的关键步骤。这些 tests 是一种 verification,类似确认你的 data 遵循 specific patterns,并满足 predefined criteria。
然而,也必须注意,dbt tests 只是更广泛 data testing landscape 中的一种 testing。在 software testing 中,tests 通常区分为 verification 和 validation。dbt tests 主要聚焦 verification,即确认 data 遵守已建立的 patterns 和 structures。它们并不是为测试 data transformations 中更细粒度的 logic details 而设计的,这一点类似 software development 中 unit tests 的作用范围。
此外,dbt tests 可以在一定程度上协助 data components 的 integration,特别是当多个 components 一起运行时。不过,必须认识到 dbt tests 有其 limitations,不能覆盖所有 testing use cases。对于 data projects 中的 comprehensive testing,你可能需要使用其他 testing methods 和 tools,以适配特定 validation 和 verification needs。
带着这一点,让我们聚焦 dbt 可以使用哪些 tests。dbt 中 tests 有两大主要分类:singular 和 generic。让我们进一步了解这两类 tests、它们的目的,以及如何利用它们。
Generic tests
dbt 中最简单但高度 scalable 的 tests 是 generic tests。使用这些 tests 时,通常不需要写任何新的 logic,不过 custom generic tests 也是一个选项。通常你只需写几行 YAML code,然后根据 test 类型测试某个 particular model 或 column。dbt 内置四种 generic tests:
unique test
验证特定 column 中每个 value 都是唯一的。
not_null test
验证特定 column 中每个 value 都不是 null。
accepted_values test
确保特定 column 中每个 value 都存在于给定 predefined list 中。
relationships test
确保特定 column 中每个 value 都存在于另一个 model 的某个 column 中,从而保证 referential integrity。
现在我们对 generic tests 有了一些 context,让我们试一下。我们可以选择任意 model,但为了简化,选择一个可以应用所有 tests 的 model。为此,我们选择 stg_jaffle_shop_orders.sql model。这里我们可以在 customer_id 和 order_id 等 fields 上测试 unique 和 not_null。可以用 accepted_values 检查所有 order status 是否在 predefined list 中。最后,我们会使用 relationships test 检查 customer_id 的所有 values 是否存在于 stg_jaffle_shop_customers.sql model 中。首先,用 Example 4-24 中的代码替换 _jaffle_shop_models.yml。
Example 4-24:带 generic tests 的 _jaffle_shop_models.yml parametrizations
version: 2
models:
- name: stg_jaffle_shop_customers
config:
materialized: view
columns:
- name: customer_id
tests:
- unique
- not_null
- name: stg_jaffle_shop_orders
config:
materialized: view
columns:
- name: order_id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values:
- completed
- shipped
- returned
- placed
- name: customer_id
tests:
- relationships:
to: ref('stg_jaffle_shop_customers')
field: customer_id
现在,在 command line 中输入 dbt test 并查看 logs。如果 test 在 accepted_values 上 failed,说明你做对了。它本来就应该 fail。让我们 debug,理解 failure 的潜在 root cause。打开 logs 并展开 failed 的 test。然后点击 Details。你会看到用于测试 data 的 query,如图 4-41 所示。
图 4-41:Generic test,dbt logs 中 accepted_values failed test
将这个 query 复制到 text editor 中,只保留 inner query,然后执行它。你应该得到类似图 4-42 的 output。
图 4-42:Generic test debug
Voilà。我们找到了问题。额外的 status return_pending 缺失于 test list。将它添加进去,并重新运行 dbt test command。现在所有 tests 都应该通过,如图 4-43 所示。
图 4-43:Generic test,所有 tests 成功执行
NOTE
除了 dbt Core 内置的 generic tests,dbt ecosystem 中还有更多 tests。这些 tests 存在于 dbt packages 中,因为它们是 dbt 内置 generic tests 的扩展。“dbt Packages” 会详细介绍 packages 的概念和安装方式。对于 extended testing capabilities,dbt team 的 dbt_utils,或 Python library Great Expectations 的 dbt_expectations 等 packages,是 packages 优秀使用方式的典型示例,也是任何 dbt project 中的 must-have。最后,custom generic tests 是 dbt 的另一个 feature,使你能够定义适合特定 project requirements 的自定义 data validation rules 和 checks。
Singular tests
不同于 generic tests,singular tests 定义在 tests directory 下的 .sql files 中。通常,当你想测试 particular model 内的 specific attribute,但 dbt 内置 traditional tests 不符合需求时,这类 tests 很有用。
观察我们的 data model,一个好的 test 是检查没有任何 order 的 total amount 为负数。我们可以在三层之一执行这个 test:staging、intermediate 或 marts。我们选择 intermediate layer,因为其中做了一些可能影响 data 的 transformations。首先,在 tests directory 中创建一个名为 assert_total_payment_amount_is_positive.sql 的 file,并复制 Example 4-25 中的代码。
Example 4-25:assert_total_payment_amount_is_positive.sql singular test,用于检查 int_payment_type_amount_per_order 内的 total_amount attribute 是否只有非负值
select
order_id,
sum(total_amount) as total_amount
from {{ ref('int_payment_type_amount_per_order') }}
group by 1
having total_amount < 0
现在可以执行以下 commands 之一来运行 test,它应当通过:
dbt test
执行所有 tests。
dbt test --select test_type:singular
只执行 singular tests。
dbt test --select int_payment_type_amount_per_order
执行 int_payment_type_amount_per_order model 的所有 tests。
dbt test --select assert_total_payment_amount_is_positive
只执行我们创建的 specific test。
这些 commands 提供了根据需求选择性运行 tests 的能力。无论是运行所有 tests、某种 specific type 的 tests、某个 particular model 的 tests,甚至单个 specific test,dbt 都允许你在 commands 中使用多种 selection syntax options。各种选择确保你能精确定位希望执行的 tests 以及其他 dbt resources。在 “dbt Commands and Selection Syntax” 中,我们会全面介绍可用 dbt commands,并研究如何高效使用 selection syntax 指定 resources。
Testing sources
要测试 dbt project 中的 models,也可以将这些 tests 扩展到 sources。你已经在 “Source freshness” 中通过 source freshness test 做过这件事。不过,也可以为此增强 generic 和 singular tests。对 sources 使用 test capabilities,可以让我们有信心相信 raw data 是按预期构建的。
与在 models 中配置 tests 的方式相同,也可以为 sources 配置 tests。无论是 YAML files 中的 generic tests,还是 .sql files 中的 singular tests,规范都保持一致。让我们分别看一个示例。
从 generic tests 开始,你需要编辑 sources 的特定 YAML file。让我们保持与 customers 和 orders staging tables 上相同的 unique、not_null 和 accepted_values tests,但现在测试它们的 sources。为此,将 _jaffle_shop_sources.yml 的代码替换为 Example 4-26 中的代码。
Example 4-26:带 generic tests 的 _jaffle_shop_sources.yml parametrizations
version: 2
sources:
- name: jaffle_shop
database: dbt-tutorial
schema: jaffle_shop
tables:
- name: customers
columns:
- name: id
tests:
- unique
- not_null
- name: orders
loaded_at_field: _etl_loaded_at
freshness:
warn_after: {count: 17, period: hour}
error_after: {count: 24, period: hour}
columns:
- name: id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values:
- completed
- shipped
- returned
- placed
- return_pending
将新代码放入 YAML file 后,可以运行 dbt test。更精确地说,执行只测试我们为其创建 tests 的 source 的 command:dbt test --select source:jaffle_shop。所有 tests 都应通过。
最后,你也可以像之前一样实现 singular tests。让我们复制 Example 4-25 中之前执行的 singular test。在 tests directory 中创建一个名为 assert_source_total_payment_amount_is_positive.sql 的新 file,并复制 Example 4-27 中的代码。这个 test 会检查 payment source table 中每个 order 的 amount attribute 总和是否只有非负值。
Example 4-27:assert_source_total_payment_amount_is_positive.sql singular test
select
orderid as order_id,
sum(amount) as total_amount
from {{ source('stripe', 'payment') }}
group by 1
having total_amount < 0
执行 dbt test,或因为这里查看的是 Stripe source,所以执行 dbt test --select source:stripe。所有内容也应通过。
Analyses
analyses folder 可以存储 ad hoc queries、audit queries、training queries 或 refactoring queries,例如用于检查代码在影响 models 之前会是什么样子。
Analyses 是 templated SQL files,不能在 dbt run 期间执行。但由于你可以在 analyses 中使用 Jinja,仍然可以使用 dbt compile 查看代码最终会是什么样,同时将代码保存在 version control 之下。基于它的 purpose,让我们看一个可以利用 analyses folder 的 use case。
想象你不想构建一个完整的新 model,但仍希望保存一段 information 以备未来使用,并利用 code versioning。借助 analyses,就可以做到。对于我们的 use case,让我们基于 total amount paid 分析 top 10 most valuable customers,并且只考虑 “completed” status 的 orders。为了实现这一点,在 analyses directory 内创建一个名为 most_valuable_customers.sql 的新 file,并复制 Example 4-28 中的代码。
Example 4-28:most_valuable_customers.sql analyses,基于 completed orders 输出按 total amount paid 排名前 10 的 most valuable customers
with fct_orders as (
select * from {{ ref('fct_orders')}}
),
dim_customers as (
select * from {{ ref('dim_customers' )}}
)
select
cust.customer_id,
cust.first_name,
SUM(total_amount) as global_paid_amount
from fct_orders as ord
left join dim_customers as cust ON ord.customer_id = cust.customer_id
where ord.is_order_completed = 1
group by cust.customer_id, first_name
order by 3 desc
limit 10
现在执行代码并查看结果。如果一切顺利,它会给出 top 10 most valuable customers,如图 4-44 所示。
图 4-44:基于 completed orders 的 total global amount paid 得到的 top 10 most valuable customers
Seeds
Seeds 是 dbt platform 中的 CSV files,包含少量 nonvolatile data,用于在 data platform 中 materialized as table。只需在 command line 中输入 dbt seed,seeds 就可以像所有其他 models 一样,使用 ref() function 以标准方式在 models 中使用。
Seeds 有多种应用场景,例如映射 country codes(例如 PT 到 Portugal,US 到 United States)、zip codes 到 states、需要从 analyses 中排除的 dummy email addresses,甚至其他 complex analyses,例如 price range classification。重要的是记住,seeds 不应包含大量数据或频繁变化的数据。如果出现这种情况,请重新思考 data capture approach,例如使用 SFTP(SSH File Transfer Protocol)或 API。
为了更好理解如何使用 seeds,让我们跟随下一个 use case。考虑我们在 “Analyses” 中所做的内容,现在不仅想看到基于 paid orders completed 的 top 10 most valuable customers,还想根据 total_amount paid,将所有有 orders 的 customers 分类为 regular、bronze、silver 或 gold。首先创建 seed。为此,在 seeds folder 中创建一个名为 customer_range_per_paid_amount.csv 的新 file,并复制 Example 4-29 的数据。
Example 4-29:seed_customer_range_per_paid_amount.csv,ranges mapping
min_range,max_range,classification
0,9.999,Regular
10,29.999,Bronze
30,49.999,Silver
50,9999999,Gold
完成后,执行 dbt seed。它会将 CSV file materialize 成 data platform 中的一张 table。最后,在 analyses directory 中创建一个名为 customer_range_based_on_total_paid_amount.sql 的新 file,并复制 Example 4-30 中的代码。
Example 4-30:customer_range_based_on_total_paid_amount.sql,根据 completed orders 和 total amount paid 显示 customer classification range
with fct_orders as (
select * from {{ ref('fct_orders')}}
),
dim_customers as (
select * from {{ ref('dim_customers' )}}
),
total_amount_per_customer_on_orders_complete as (
select
cust.customer_id,
cust.first_name,
SUM(total_amount) as global_paid_amount
from fct_orders as ord
left join dim_customers as cust ON ord.customer_id = cust.customer_id
where ord.is_order_completed = 1
group by cust.customer_id, first_name
),
customer_range_per_paid_amount as (
select * from {{ ref('seed_customer_range_per_paid_amount' )}}
)
select
tac.customer_id,
tac.first_name,
tac.global_paid_amount,
crp.classification
from total_amount_per_customer_on_orders_complete as tac
left join customer_range_per_paid_amount as crp
on tac.global_paid_amount >= crp.min_range
and tac.global_paid_amount <= crp.max_range
现在执行代码并查看结果。它会给每个 customer 展示 total amount paid 以及对应 range(图 4-45)。
图 4-45:基于 completed orders 的 total global amount paid 得到的 customers range
Documentation
Documentation 在整个 software engineering landscape 中非常关键,但它似乎像一个 taboo。有些 teams 会做,有些不会,或者做得不完整。它可能变得过于 bureaucratic 或 complex,也可能被视为 developer to-do list 上的 overhead,因此被不惜一切代价避免。你可能听到一长串理由,用来证明不创建 documentation 或把它推迟到没那么忙的时候是合理的。没有人说 documentation 不重要。只是 “我们不会做”、“现在不做”,或者 “我们没时间”。
以下是创建和使用 documentation 的几个理由:
它促进 onboarding、handover 和 hiring processes。有了 proper documentation,任何新 team member 都不会觉得自己被 “thrown to the wolves”。新同事会获得书面的 onboarding process 和 technical documentation,这会减少他们学习当前 team processes、concepts、standards 和 technological developments 的曲线。Employee turnover 和 knowledge-sharing transition 同样适用。
它会赋能 single source of truth。从 business definitions、processes 和 how-to articles,到让 users 自助回答问题,documentation 会节省 team 寻找信息的时间和精力。
通过 documentation 共享 knowledge,可以减少 duplicate 或 redundant work。如果 documentation 已经完成,就可以复用,而无需从零开始。
它推动 shared responsibility 的意识,确保 critical knowledge 不被限制在某一个人手中。这种 shared ownership 对于防止 key team members 不可用时产生 disruption 至关重要。
当你想建立 quality、process control,并满足 compliance regulations 时,它是必要的。Documentation 能使 team 朝 company-wide cohesion 和 alignment 方向工作。
缺乏创建 documentation 动力的一个理由,是它与实际 development flow 是并行 stream,例如用一个 tool 做 development,另一个 tool 做 documentation。dbt 则不同。你在开发 analytics code、tests、连接 sources 等任务的同时,就可以构建 project documentation。所有内容都在 dbt 内部,而不是分散在 separate interface 中。
dbt 处理 documentation 的方式,使你能在构建代码时创建 documentation。通常,很大一部分 documentation 已经是 dynamically generated 的,例如我们之前介绍的 lineage graphs,只要求你正确配置 ref() 和 source() functions。另一部分则是 partially automated,需要你手动输入某个 particular model 或 column 表示什么。不过同样,一切都在 dbt 内完成,直接写在 YAML 或 Markdown files 中。
让我们开始编写 documentation。我们想实现的 use case,是记录 fct_orders 和 dim_customers 的 models 及对应 columns。我们将使用 models 的 YAML files,并为了更丰富的 documentation,在 Markdown files 中使用 doc blocks。由于 marts directory 中的 core models 还需要创建 YAML file,我们创建一个名为 _core_models.yml 的 file。
复制 Example 4-31。然后,在同一 directory folder 中创建一个名为 _code_docs.md 的 Markdown file,并复制 Example 4-32。
Example 4-31:_core_models.yml,带 description parameter 的 YAML file
version: 2
models:
- name: fct_orders
description: Analytical orders data.
columns:
- name: order_id
description: Primary key of the orders.
- name: customer_id
description: Foreign key of customers_id at dim_customers.
- name: order_date
description: Date that order was placed by the customer.
- name: cash_amount
description: Total amount paid in cash by the customer with "success" payment
status.
- name: credit_amount
description: Total amount paid in credit by the customer with "success"
payment status.
- name: total_amount
description: Total amount paid by the customer with "success" payment status.
- name: is_order_completed
description: "{{ doc('is_order_completed_docblock') }}"
- name: dim_customers
description: Customer data. It allows you to analyze customers perspective linked
facts.
columns:
- name: customer_id
description: Primary key of the customers.
- name: first_name
description: Customer first name.
- name: last_name
description: Customer last name.
Example 4-32:_core_doc.md,带 doc block 的 markdown file
{% docs is_order_completed_docblock %}
Binary data which states if the order is completed or not, considering the order
status. It can contain one of the following values:
| is_order_completed | definition |
|--------------------|-----------------------------------------------------------|
| 0 | An order that is not completed yet, based on its status |
| 1 | An order which was completed already, based on its status |
{% enddocs %}
在生成 documentation 前,先理解我们做了什么。分析 YAML file _core_models.yml 可以看到,我们添加了一个新 property:description。这个基础 property 允许你用 manual inputs 补充 documentation。这些 manual inputs 可以是 text,正如大多数场景中那样;也可以引用 Markdown files 中的 doc blocks,就像 fct_orders 的 is_order_completed column 那样。我们首先在 Markdown file _code_docs.md 中创建 doc block,并命名为 is_order_completed_docblock。这个名称就是在 description field 中引用 doc block 时使用的名称:"{{ doc('is_order_completed_docblock') }}"。
在 command line 中输入 dbt docs generate 生成 documentation。成功完成后,你就可以浏览 documentation page。
访问 documentation page 很简单。在 IDE 中成功执行 dbt docs generate 后,可以在屏幕左上方点击 Git branch information 旁边的 Documentation site book icon(图 4-46)。
图 4-46:View documentation
进入 documentation page 后,会看到 overview page,类似图 4-47。现在这里显示的是 dbt 提供的默认信息,但这个页面也完全可以 custom。
图 4-47:Documentation landing page
在 overview page 左侧,可以看到 project structure(图 4-48),其中包含 tests、seeds 和 models 等,你可以自由导航。
图 4-48:documentation 内的 dbt project structure
现在选择我们开发的某个 model,并查看相应 documentation。这里选择 fct_orders model。点击该 file 后,screen 会显示该 model 的多层信息,如图 4-49 所示。
图 4-49:fct_orders documentation page
顶部的 Details section 提供 table metadata 信息,例如 table type,也称为 materialization。使用的 language、rows 数量和 approximate table size 也是可用 details。
随后是 model 的 Description。你可能还记得,这是我们在 _core_models.yml file 中为 fct_orders table 配置的内容。
最后是与 fct_orders 相关的 Columns information。这部分 documentation 是 partially automated 的,例如 column type,但也接收 manual inputs,例如 column descriptions。我们已经通过填写 description properties 提供了这些 inputs,并通过 doc blocks 为 is_order_completed attribute 提供了全面信息。要在 documentation page 中查看写好的 doc block,请点击 is_order_completed field,它应会展开并显示所需信息(图 4-50)。
图 4-50:is_order_completed column 展示配置的 doc block
Columns information 之后,是 model 的 downstream 和 upstream dependencies,分别位于 Referenced By 和 Depends On sections。这些 dependencies 也显示在图 4-51 中。
图 4-51:documentation 中的 fct_orders dependencies
在 fct_orders documentation page 底部,是生成该 specific model 的 Code。你可以以 raw format、with Jinja 或 compiled code 的形式查看 source code。图 4-52 显示的是 raw form。
图 4-52:fct_orders source code
最后,如果看 documentation page 右下角,会看到一个 blue button。点击该按钮可以访问当前正在查看 model 的 lineage graph。我们选择了 fct_orders lineage graph,可以看到 upstream dependencies,例如 source tables 或 intermediate tables,也可以看到 downstream dependencies,例如图 4-53 中显示的 analysis files。Lineage graph 非常强大,因为它提供了 holistic view,展示 data 如何从被消费开始,到被 transform 和 serve 的全过程。
dbt documentation 另一个值得提及的有趣方面,是可以使用 persist_docs configuration,直接将 column- 和 table-level descriptions 持久化到 database 中。这个 feature 对 data warehouse 的所有 users 都有价值,包括可能无法访问 dbt Cloud 的 users。它确保 essential metadata 和 descriptions 对 data consumers 随时可用,从而促进更好理解和利用 data assets。
图 4-53:fct_orders lineage graph
dbt Commands and Selection Syntax
我们已经介绍了多个 dbt commands,例如 dbt run 和 dbt test,以及如何与 CLI 交互执行它们。在本节中,我们将探索 essential dbt commands 和 selection syntax,使你能够执行、管理和控制 dbt project 的各个方面。无论是运行 transformations、执行 tests,还是生成 documentation,这些 commands 都是有效 project management 的工具箱。
从头开始。在核心上,dbt 是一个 command-line tool,旨在简化 data transformation workflows。它提供一组 commands,使你能高效地与 dbt project 交互。让我们更详细地探索每个 command。
dbt run
dbt run command 是执行 dbt models 中定义的 data transformations 的主要工具。它会使用 project configuration files,例如 dbt_project.yml,来理解应该运行哪些 models 以及运行顺序。该 command 会基于 dependencies 识别必须执行的 models,并按适当顺序运行它们。
dbt test
确保 data 的质量和可靠性至关重要。dbt test command 允许你在 data models 上定义和执行 tests,验证它们是否满足 business rules 和 expectations。
dbt docs
充分 documentation 对 collaborative data projects 至关重要。dbt docs 会自动为 dbt project 生成 documentation,包括 model descriptions、column descriptions,以及 models 之间的 relationships。要生成 documentation,需要执行 dbt docs generate。
dbt build
在运行 dbt project 前,通常需要 compile。dbt build command 会执行这个任务,创建 execution 所需 artifacts。这一步对优化 execution process 并确保一切位于正确位置非常重要。Project 成功 compile 后,就可以更有信心地执行 dbt run 等其他 commands。
Other commands
虽然前面这些 commands 可能最常用,但你也应该了解其他 dbt commands,例如:
dbt seed
将 raw data 或 reference data 加载到 project 中。
dbt clean
删除由 dbt build 生成的 artifacts。
dbt snapshot
对 data 进行 snapshot,用于 versioning。
dbt archive
将 tables 或 models 归档到 cold storage。
dbt deps
安装 packages.yml 中定义的 project dependencies。
dbt run-operation
运行 project 中定义的 custom operation。
dbt source snapshot-freshness
检查 source data 的 freshness。
dbt ls
列出 dbt project 中定义的 resources。
dbt retry
从 failure point 重新运行上一次 dbt command。
dbt debug
以 debug mode 运行 dbt,提供详细 debugging information。
dbt parse
Parse dbt models 但不运行它们,这对 syntax checking 很有帮助。
dbt clone
从指定 state 克隆 selected models。
dbt init
在当前 directory 创建新的 dbt project。
Selection syntax
随着 dbt projects 增长,你需要在执行、测试或生成 documentation 时,定位 specific models、tests 或其他 resources,而不是每次都运行所有内容。这就是 selection syntax 发挥作用的地方。
Selection syntax 允许你在运行 dbt commands 时,精确指定要 include 或 exclude 哪些 resources。Selection syntax 包括多种 elements 和 techniques,例如以下内容。
Wildcard *
Asterisk(*)表示任意 character 或 character sequence。让我们看 Example 4-33。
Example 4-33:使用 * wildcard 的 selection syntax
dbt run --select models/marts/core/*
这里,我们把 * wildcard 与 --select flag 结合使用,定位 core directory 内的所有 resources 或 models。该 command 会执行该 directory 内的所有 models、tests 或其他 resources。
Tags
Tags 是可以分配给 dbt project 中 models、macros 或其他 resources 的 labels,尤其是在 YAML files 中。你可以使用 selection syntax 定位带有特定 tags 的 resources。例如,Example 4-34 展示如何基于 marketing tag 选择 resources。
Example 4-34:使用 tag 的 selection syntax
dbt run --select tag:marketing
Model name
你可以在 selection syntax 中使用 model name 精确选择单个 model,如 Example 4-35 所示。
Example 4-35:使用 model 的 selection syntax
dbt run --select fct_orders
Dependencies
使用 + 和 - symbols 选择依赖于其他 models 的 models,或被其他 models 依赖的 models。例如,fct_orders+ 选择依赖于 fct_orders 的 models,而 +fct_orders 选择 fct_orders 所依赖的 models(Example 4-36)。
Example 4-36:使用 dependencies 的 selection syntax
# run fct_orders upstream dependencies
dbt run --select +fct_orders
# run fct_orders downstream dependencies
dbt run --select fct_orders+
# run fct_orders both up and downstream dependencies
dbt run --select +fct_orders+
Packages
如果你将 dbt project 组织为 packages,可以使用 package syntax 选择某个 specific package 内的所有 resources,如 Example 4-37 所示。
Example 4-37:使用 package 的 selection syntax
dbt run --select my_package.some_model
Multiple selections
你可以组合 selection syntax 的多个 elements 来创建 complex selections,如 Example 4-38 所示。
Example 4-38:使用多个 elements 的 selection syntax
dbt run --select tag:marketing fct_orders
在这个示例中,我们组合了 tagging 和 model selection 等 elements。它只会在名为 fct_orders 的 dbt model 带有 marketing tag 时运行该 model。
Selection syntax 允许你基于多种 criteria 控制哪些 dbt resources 运行,包括 model names、tags 和 dependencies。你可以结合 --select flag 使用 selection syntax,使 dbt operations 适配 project 中特定 subsets。
此外,dbt 还提供多个与 selection 相关的 flags 和 options,例如 --selector、--exclude、--defer 等,它们提供更细粒度的 control,帮助你与 dbt project 交互。这些 options 让管理和执行 dbt models 与 resources 更容易,并使其符合 project 的 requirements 和 workflows。
Jobs and Deployment
到目前为止,我们一直在介绍如何使用 dbt 进行 development。我们学习了 models,以及如何 implement tests、write documentation 和其他 dbt 提供的重要 components。我们利用 development environment 并手动执行 dbt commands,完成并测试了所有这些内容。
使用 development environment 的重要性不应被低估。它允许你继续构建 dbt project,而不会影响 deployment / production environment,直到你准备好为止。但现在,我们已经进入需要 productionize 和 automate code 的阶段。为此,需要将 analytics code 部署到 production branch,通常命名为 main branch,并部署到 dedicated production schema,例如 BigQuery 中的 dbt_analytics_engineering.core,或你的 data platform 中等价的 production target。
最后,我们需要配置并 schedule 一个 job,以自动化希望 roll into production 的内容。配置 job 是 CI/CD process 的重要部分。它允许你按符合 business needs 的 cadence 自动执行 commands。
首先,让我们 commit 并 sync 到目前为止所做的一切到 development branch,然后 merge 到 main branch。点击 “Commit and sync” 按钮(图 4-54)。不要忘记写一条 comprehensive message。
图 4-54:“Commit and sync” 按钮
你可能需要创建 pull request。正如 “Setting Up dbt Cloud with BigQuery and GitHub” 中简要解释的,pull requests(PRs)在 collaborative development 中发挥重要作用。它们是向 team 传达 proposed changes 的基本机制。不过,必须理解,PRs 不只是通知同事你做了什么,它们也是 review 和 integration process 中的关键步骤。
当你创建 PR 时,本质上是在邀请 team review code、provide feedback,并共同决定这些 changes 是否符合 project goals 和 quality standards。
回到我们的代码,在 PR 之后,将其 merge 到 GitHub 中的 main branch。GitHub 中的最终 screen 应类似图 4-55。
图 4-55:merge 到 main branch 后的 Pull request screen
到这个阶段,main branch 应该与 development branch 相同。现在是时候把它部署到 data platform 了。在创建 job 之前,需要设置 deployment environment:
- 从 Deploy menu 中点击 Environments option,然后点击 Create Environment 按钮。会弹出一个 screen,用于配置 deployment environment。
- 保持最新 dbt Version,不要勾选 run on a custom branch,因为我们已经把代码 merge 到 main branch。
- 将 environment 命名为 “Deployment”。
- 在 Deployment Credentials section 中,写入连接 deployment / production environment 的 dataset。我们命名为
dbt_analytics_engineer_prod,但你可以使用最适合自己需要的名称。
如果一切顺利,你应拥有一个 deployment environment,其 configurations 类似图 4-56。
图 4-56:Deployment environment settings
现在是配置 job 的时候。在 dbt Cloud UI 中,点击 Deploy menu 中的 Jobs 选项,然后点击 Create New Job 按钮。创建 job 可以从简单概念到更复杂配置。让我们设置一个 job,覆盖前面讨论的主要想法:
为 job 命名(图 4-57)。
图 4-57:定义 job name
在 Environment section 中,指向 Deployment environment。配置 dbt version,使其继承 Deployment environment 中定义的 version。然后将 Target Name 保持为 default。如果你想基于 work environment 定义条件,这会很有帮助,例如:如果在 deployment environment,就这样做;如果在 development,就那样做。最后,我们在 “profiles.yml” 中已经介绍过 Threads。这里保持 default configuration。我们没有创建任何 Environment Variables,因此该 section 保持为空。图 4-58 展示了整体 Environment section configuration。
图 4-58:定义 job environment
图 4-59 展示了 Execution Settings 的全局 configurations。我们将 Run Timeout 设置为 0,因此如果 job 运行超过某个时间,dbt 永远不会 kill 它。然后,我们也选择了 “do not defer to another run”。最后,我们选择了 “Generate docs on run” 和 “Run source freshness” 两个 boxes。这个 configuration 会减少 Commands section 中需要编写的 commands 数量。对于这个 use case,我们只保留默认的 dbt build。
图 4-59:定义 job settings
最后一个 configuration setting 是 Triggers,用于配置如何 launch job。有三种触发 job 的方式:
- dbt 内配置 schedule
- 通过 Webhooks
- 通过 API call
对于这个 use case,我们选择 Schedule option,并将 schedule 设置为 hourly basis,如图 4-60 所示。
图 4-60:定义 job trigger
现在可以执行并查看会发生什么。保存 job;然后选择 Run Now,或者等待 job 在达到配置的 schedule 后自动触发。
Job 运行时或运行完成后,你随时可以检查 status 和执行内容。从 Deploy menu 中选择 Run History 选项。你会看到 job executions。选择其中一个并查看 Run Overview。你应该看到类似图 4-61 的内容。
图 4-61:job 的 Run Overview screen
进入 Run Overview 后,你会看到关于该 specific job execution 的相关信息,这些信息对 potential troubleshooting 很有帮助。顶部是 job execution status summary、触发 job 的 person 或 system、与该 job execution 关联的 Git commit、生成的 documentation、sources,以及 job 运行的 environment。
Job summary 后面是 execution details,例如 job 执行耗时、开始和完成时间。最后,Run Overview 提供的关键信息之一是 Run Steps,它详细列出 job execution 期间执行的所有 commands,并允许你检查每个 isolated step 及其 logs,如图 4-62 所示。探索每个 step 的 logs,可以帮助你理解每一步运行了什么,并在 execution 期间查找问题。
图 4-62:job 的 Run Steps details
借助 dbt jobs,你可以轻松自动化 transformations,并以高效、可扩展的方式将 projects 部署到 production。无论你是 data analyst、data engineer,还是 analytics engineer,dbt 都可以帮助你应对 data transformations 的复杂性,并确保 data models 始终 accurate 和 up-to-date。
Summary
本章展示了 analytics engineering 是一个不断演进的领域,并始终受到 innovations 的影响。dbt 不只是这个故事中的一部分;它是该领域中的关键工具。
Analytics engineering 的主要目标是将 raw data 转换为 valuable insights,而 dbt 在简化 data transformation 的复杂性,并促进广泛 stakeholders 之间的 cooperation 方面发挥关键作用。dbt 确保 data transformation 不只是 technical change,也非常强调 openness、inclusivity 和 knowledge sharing。
dbt 因其能够通过与 large data warehouses 轻松集成来 streamline complicated processes 而闻名。它还通过确保 optimal traceability 和 accuracy,推动一种 collaborative approach to data transformation。此外,它强调彻底 testing data processes 的重要性,以保证 dependability。它的 user-friendly interface 强化了这样一种观念:analytics engineering 是一个 inclusive field,欢迎各种 competency levels 的 individuals 做出贡献。
最后,我们强烈鼓励希望站在行业前沿的 analytics engineers 深入研究这个 transformational tool。随着 dbt 变得越来越重要且明显有益,熟练掌握这个工具不仅可以提升你的 skill set,也可以让未来的 data transformations 更流畅、更协作。