Towards Data Science

Getting started with dbt

8.5内容质量

TL;DR · AI 摘要

dbt通过结构化SQL转换和自动化测试解决数据工程中的常见问题,提升数据可靠性与开发效率。

核心要点

  • dbt Core免费支持数据建模、测试和文档生成,适用于中小型团队
  • SQL脚本错误可能导致数据流水线崩溃,dbt通过测试减少此类问题
  • dbt支持CI/CD集成,实现数据转换流程的自动化

结构提纲

按章节快速跳转。

  1. 作者作为数据工程师分享学习dbt的动机与使用场景

  2. 传统SQL脚本管理导致数据流水线脆弱性问题

  3. 数据转换、质量测试、文档生成与环境管理

  4. 通过SQL模型定义和自动化测试保障数据一致性

  5. 支持开发/测试/生产环境分离与版本控制

  6. 展示dbt在实际数据流水线中的应用示例

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • dbt工具
    • 核心功能
      • 数据转换
      • 质量测试
      • 文档生成
      • 环境管理
    • 使用场景
      • 数据流水线自动化
      • CI/CD集成

金句 / Highlights

值得收藏与分享的关键句。

#dbt#数据工程#SQL#数据仓库
打开原文

dbt入门 | Towards Data Science

数据工程

dbt入门

构建、测试和记录SQL转换的实用指南

Thomas Reid

2026年9月9日

22分钟阅读

AI生成图片

作为一名合同数据工程师,我有时会经历——啊,总之吧——一些不活跃的时期。在那些时候,当我浏览在线市场寻找合适的职位时,我看到的最抢手的技能之一是对一个名为dbt的工具的经验。

因此,为了给自己最好的机会获得工作,我决定尽可能多地学习dbt,至少在面试阶段,如果需要的话,能够自信地与同行技术人员用一般术语讨论它。本文总结了这一过程和我学到的内容。当然,你不能仅仅通过阅读来学习一门学科,所以和往常一样,我会展示大量实际代码和真实案例。

为了明确,我与dbt、DuckDB或其创建者没有任何关联或商业联系。dbt Core是根据Apache 2.0许可证发布的免费开源软件,无需dbt账户即可本地运行。DuckDB也根据宽松的MIT许可证免费使用。

dbt提供了广泛的功能,但鉴于这是对该主题的介绍,我将专注于解释基础知识。这包括使用dbt模型和源,以及使用它来测试数据和创建文档。关于所有这些内容,稍后会有更多介绍。

如果你参与过任何规模相当大的分析或数据工程项目,你可能最终会得到一个装满SQL脚本的文件夹。

当你的项目刚开始时,一切似乎都可控。你手动运行脚本或在公司使用的任何编排工具中安排它们。一切都很顺利。

然后项目开始增长。

一个表中的某一列被重命名,突然某个下游报告或仪表板停止工作,更糟糕的是,你的夜间1000万条记录数据摄入作业失败,整个系统陷入停滞。错误应用的SQL片段或表更改对数据库系统造成的问题清单令人不寒而栗。而且你知道的,这经常发生。

问题的一部分在于,传统上SQL被当作一组孤立的脚本,而不是软件项目来处理。

如果这听起来很耳熟,dbt背后的团队认为他们有一个解决方案。

dbt是什么?

dbt(数据构建工具)由现称为dbt Labs的团队于2010年代中期创建。它从内部分析工作流程发展成为广泛使用的开源、免费(开发者计划)命令行工具dbt Core,以及一个名为dbt Platform的完全托管付费版本。我将使用免费版本。

dbt用于转换已存储在数据库、仓库或湖仓中的数据。它通过根据用户提供的SQL创建表或视图来实现这一点,但它还处理以下内容:

  • 测试数据质量
  • 记录数据集和血缘关系
  • 通过宏重用SQL
  • 管理开发、测试和生产环境
  • 通过计划任务或CI/CD管道运行转换

dbt被使用企业级数据存储平台的团队广泛使用,如Snowflake、BigQuery、Redshift和Databricks。但为了我的示例,我将使用本地DuckDB数据库。

为什么数据团队使用dbt?

主要是因为它擅长自己擅长的事情。

Imagine you’re building a sales reporting platform. Raw order data lands in your data warehouse every hour, say. You write one SQL script to clean the data, another to calculate customer totals, another to build daily sales figures, and another to generate executive dashboards.

At first, the project has four or five SQL files, and it’s easy to keep track of them. Six months later, there are fifty, and the order in which they run is no longer obvious.

  • Which script runs in which order?
  • What breaks if someone renames a column?
  • How do you check that the data is still valid?
  • Could a new developer understand the project without opening every SQL file?

Often, analytics teams solved these problems with naming conventions, handwritten notes passed around and a lot of shared systems knowledge.

As organisations became more data-driven, analytics projects started looking more and more like software projects. Teams needed version control, automated testing, documentation and dependency management because they were writing thousands of lines of SQL.

Rather than treating SQL scripts as independent files, dbt treats them as components of a single project, where every transformation has a defined purpose, and every dependency is understood.

Prerequisites

I’m using Windows as my operating system and have Python 3.13 installed. Everything should work in the same way if you're on Linux or macOS but you definitely need to have Python installed. You’ll also need access to a suitable database for dbt to act on. Each database will have differences in how you set it up to use dbt. I'll be using DuckDB as my database and will show you the set up for that. Consult the dbt docs (linked at the end) if you're using a different data store.

Installing dbt

Now that we have a better understanding of dbt, in the rest of this article I’ll show you how to install it and, by way of example code, demonstrate the most common dbt commands you’ll use in your day-to-day job.

The first thing we should do is set up a separate Python development environment to keep our projects siloed.

powershell

code
PS C:\Users\thoma> cd projects
PS C:\Users\thoma\projects> mkdir dbt-demo
Directory: C:\Users\thoma\projects
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        03/08/2026     16:21                dbt-demo
PS C:\Users\thoma\projects> cd dbt-demo
PS C:\Users\thoma\projects\dbt-demo> python3 -m venv .venv
Actual environment location may have moved due to redirects, links or junctions.
Requested location: "C:\Users\thoma\projects\dbt-demo\.venv\Scripts\python3.exe"
Actual location:    "D:\Users\thoma\projects\dbt-demo\.venv\Scripts\python3.exe"
PS C:\Users\thoma\projects\dbt-demo> .\.venv\Scripts\Activate.ps1
(.venv) PS C:\Users\thoma\projects\dbt-demo>
(.venv) PS C:\Users\thoma\projects\dbt-demo>
(.venv) PS C:\Users\thoma\projects\dbt-demo>

You can install dbt using a simple pip command like the one shown below. To connect dbt to a data source, we use something called an adapter. dbt has many different types of adapters, for example, BigQuery, AWS Redshift, Snowflake, etc. For this demo, I’m going to be using a local DuckDB database.

Most adapters have to be installed separately from the dbt-core product, but for DuckDB, dbt provides a one-file install.

code
(.venv) PS C:\Users\thoma\projects\dbt-demo> python3 -m pip install dbt-duckdb
Collecting dbt-duckdb
Downloading dbt_duckdb-1.10.1-py3-none-any.whl.metadata (38 kB)
Collecting dbt-common<2,>=1 (from dbt-duckdb)
Using cached dbt_common-1.38.0-py3-none-any.whl.metadata (5.0 kB)
Collecting dbt-adapters<2,>=1 (from dbt-duckdb)
Using cached dbt_adapters-1.24.5-py3-none-any.whl.metadata (4.6 kB)
Collecting duckdb>=1.0.0 (from dbt-duckdb)
Downloading duckdb-1.5.5-cp313-cp313-win_amd64.whl.metadata (4.2 kB)
Collecting dbt-core>=1.8.0 (from dbt-duckdb)
Using cached dbt_core-1.12.0-py3-none-any.whl.metadata (4.5 kB)
Collecting agate<2.0,>=1.0 (from dbt-adapters<2,>=1->dbt-duckdb)
Using cached agate-1.14.2-py3-none-any.whl.metadata (3.1 kB)
Collecting dbt-protos<2.0,>=1.0.291 (from dbt-adapters<2,>=1->dbt-duckdb)
Using cached dbt_protos-1.0.541-py3-none-any.whl.metadata (859 bytes)
Collecting mashumaro<3.18,>=3.9 (from mashumaro[msgpack]<3.18,>=3.9->dbt-adapters<2,>=1->dbt-duckdb)
...
...
...
Using cached typing_inspection-0.4.2-py3-none-any.whl (14 kB)
Using cached tzdata-2026.3-py2.py3-none-any.whl (348 kB)
Using cached zipp-4.1.0-py3-none-any.whl (10 kB)
Installing collected packages: text-unidecode, pytz, pytimeparse, parsedatetime, leather, daff, zipp, urllib3, tzdata, typing-extensions, tabulate, sqlparse, sqlglot, six, rpds-py, rapidfuzz, pyyaml, python-slugify, python-dotenv, protobuf, pathspec, packaging, orderly-set, networkx, msgpack, more-itertools, MarkupSafe, isodate, idna, duckdb, dbt-extractor, dbt-core-experimental-parser, colorama, charset_normalizer, certifi, Babel, attrs, annotated-types, typing-inspection, requests, referencing, python-dateutil, pydantic-core, mashumaro, jinja2, importlib-metadata, deepdiff, dbt-protos, click, agate, snowplow-tracker, pydantic, jsonschema-specifications, jsonschema, metricflow, dbt-common, dbt-adapters, dbt-core, dbt-duckdb
Successfully installed Babel-2.18.0 MarkupSafe-3.0.3 agate-1.9.1 annotated-types-0.8.0 attrs-26.1.0 certifi-2026.7.22 charset_normalizer-3.4.9 click-8.4.2 colorama-0.4.6 daff-1.4.2 dbt-adapters-1.24.5 dbt-common-1.38.0 dbt-core-1.12.0 dbt-core-experimental-parser-2.0.0a5 dbt-duckdb-1.10.1 dbt-extractor-0.6.0 dbt-protos-1.0.541 deepdiff-8.6.2 duckdb-1.5.5 idna-3.18 importlib-metadata-9.0.0 isodate-0.7.2 jinja2-3.1.6 jsonschema-4.26.0 jsonschema-specifications-2025.9.1 leather-0.4.1 mashumaro-3.17 metricflow-0.211.0 more-itertools-10.8.0 msgpack-1.2.1 networkx-3.6.1 orderly-set-5.5.0 packaging-26.2 parsedatetime-2.6 pathspec-1.0.4 protobuf-6.33.6 pydantic-2.13.4 pydantic-core-2.46.4 python-dateutil-2.9.0.post0 python-dotenv-1.2.2 python-slugify-8.0.4 pytimeparse-1.1.8 pytz-2026.3.post1 pyyaml-6.0.3 rapidfuzz-3.14.5 referencing-0.37.0 requests-2.34.2 rpds-py-2026.6.3 six-1.17.0 snowplow-tracker-1.1.0 sqlglot-30.14.0 sqlparse-0.5.5 tabulate-0.10.0 text-unidecode-1.3 typing-extensions-4.16.0 typing-inspection-0.4.2 tzdata-2026.3 urllib3-2.7.0 zipp-4.1.0
[notice] A new release of pip is available: 26.1.2 -> 26.2
[notice] To update, run: python3.exe -m pip install --upgrade pip
(.venv) PS C:\Users\thoma\projects\dbt-demo>

初始化 dbt 项目

接下来我们要做的就是初始化一个 dbt 项目。我们通过使用 dbt init 命令来完成这个操作。

code
(.venv-core) PS C:\Users\thoma\projects\dbt-demo> dbt init
15:48:36  使用 dbt=1.12.0 运行
请输入项目名称(字母、数字、下划线):my-dbt-demo
my-dbt-demo 不是有效的项目名称。
请输入项目名称(字母、数字、下划线):my_dbt_demo
15:49:02  正在设置您的配置文件。
您想使用哪个数据库?
[1] duckdb
(没有找到您需要的数据库?请访问 https://docs.getdbt.com/docs/available-adapters)
请输入数字:1
15:49:05  配置文件 my_dbt_demo 已写入 C:\Users\thoma\.dbt\profiles.yml,使用目标配置的示例配置。更新后,您就可以开始使用 dbt 进行开发了。
15:49:05  正在运行 dbt debug 验证项目...
15:49:05  dbt 版本:1.12.0
15:49:05  Python 版本:3.13.14
15:49:05  Python 路径:C:\Users\thoma\projects\dbt-demo\.venv-core\Scripts\python3.exe
15:49:05  操作系统信息:Windows-11-10.0.22621-SP0
15:49:05  使用配置文件目录:C:\Users\thoma\.dbt
15:49:05  使用配置文件:C:\Users\thoma\.dbt\profiles.yml
15:49:05  使用项目配置文件:C:\Users\thoma\projects\dbt-demo\my_dbt_demo\dbt_project.yml
15:49:05  适配器类型:duckdb
15:49:05  适配器版本:1.10.1
15:49:05  配置:
15:49:05    配置文件 [OK 已找到且有效]
15:49:05    项目配置文件 [OK 已找到且有效]
15:49:05  必需依赖项:
15:49:05   - git [OK 已找到]
15:49:05  连接信息:
15:49:05    数据库:dev
15:49:05    模式:main
15:49:05    路径:dev.duckdb
15:49:05    配置选项:None
15:49:05    扩展:None
15:49:05    设置:{}
15:49:05    外部根目录:.
15:49:05    使用凭证提供程序:None
15:49:05    连接:None
15:49:05    文件系统:None
15:49:05    远程:None
15:49:05    插件:None
15:49:05    禁用事务:False
15:49:05  注册适配器:duckdb=1.10.1
15:49:05    连接测试:[OK 连接成功]
15:49:05  所有检查通过!
15:49:05  您的新 dbt 项目 "my_dbt_demo" 已创建!
在 C:\Users\thoma\projects\dbt-demo\my_dbt_demo\my_dbt_demo 初始化了新项目
如需了解更多关于如何配置 profiles.yml 文件的信息,
请参阅 dbt 官方文档:
https://docs.getdbt.com/docs/configure-your-profile
另外:
需要帮助?请随时通过 GitHub Issues 或 Slack 联系我们:
https://community.getdbt.com/
开始建模吧!

运行上述命令将创建多个文件夹和文件。生成的结构大致如下,

python

code

MY_DBT_DEMO/
analyses/
data/
macros/
models/
example/
my_first_dbt_model.sql
my_second_dbt_model.sql
schema.yml
seeds/
snapshots/
tests/
.gitignore
dbt_project.yml
duckdb.exe
README.md

models/example 文件夹包含两个示例模型文件和一个 schema 文件。我们稍后会详细讨论模型文件,但目前您可以安全地删除整个 example 文件夹及其内容。

dbt init 过程创建的最重要的文件之一是 profiles.yml。该文件保存了您的数据库连接属性,但您不会在 dbt 项目结构中看到它。相反,在 Windows 系统中,它的完整路径是,

code
$HOME\.dbt\profiles.yml

在我的设置中,该文件包含以下内容。

yaml

code

my_dbt_demo:
outputs:
dev:
type: duckdb
path: dev.duckdb
threads: 1
prod:
type: duckdb
path: prod.duckdb
threads: 4
target: dev

现在我们可以看到 dbt 期望我们的数据库名称以及它应该存储的位置。当然,如果你想修改这些信息,可以编辑这个文件。路径是相对于你的 HOME 目录而言的。我希望我的 duckDB 数据文件存储在以下位置:

code
C:\Users\thoma\projects\dbt-demo\data\my_dbt_demo

因此我更新了 profiles.yml 文件,内容如下:

code
my_dbt_demo:
outputs:
dev:
type: duckdb
path: "{{ env_var('USERPROFILE') }}/projects/dbt-demo/data/duckdb.dev"
schema: raw
threads: 1
prod:
type: duckdb
path: "{{ env_var('USERPROFILE') }}/projects/dbt-demo/data/duckdb.prod"
schema: raw
threads: 4
target: dev

创建我们的 DuckDB 数据库

现在我们可以创建 DuckDB 数据库了。为此我们需要安装 DuckDB CLI。点击下方链接并按照适用于你环境的说明进行操作。

code
https://duckdb.org/install/?environment=cli&platform=win&download_method=direct

运行 duckdb CLI 并传递一个合适的文件名作为参数,用于永久存储数据库。如果你不介意退出时数据丢失,也可以不带参数运行。输入以下命令:

code
(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> .\duckdb $HOME\projects\dbt-demo\my_dbt_demo\data\duckdb.dev
DuckDB v1.5.5 (Variegata)
输入 ".help" 获取使用提示。
duckdb D CREATE SCHEMA IF NOT EXISTS raw;
duckdb D
duckdb D CREATE OR REPLACE TABLE raw.orders (
order_id       INTEGER,
customer_name  VARCHAR,
product_name   VARCHAR,
order_date     DATE,
quantity       INTEGER,
unit_price     DECIMAL(10, 2),
order_status   VARCHAR
);
duckdb D INSERT INTO raw.orders VALUES
(1,  'Alice',   'Laptop Stand', '2026-01-03', 1,  39.99, 'completed'),
(2,  'Bob',     'USB-C Hub',    '2026-01-04', 2,  29.99, 'completed'),
(3,  'Charlie', 'Webcam',       '2026-01-05', 1,  74.50, 'returned'),
(4,  'Alice',   'Keyboard',     '2026-01-08', 1,  89.00, 'completed'),
(5,  'Diana',   'Mouse',        '2026-01-10', 2,  24.99, 'completed'),
(6,  'Bob',     'Monitor',      '2026-01-12', 1, 249.00, 'processing'),
(7,  'Alice',   'USB-C Hub',    '2026-02-02', 1,  29.99, 'completed'),
(8,  'Charlie', 'Keyboard',     '2026-02-06', 1,  89.00, 'completed'),
(9,  'Diana',   'Webcam',       '2026-02-09', 2,  74.50, 'completed'),
(10, 'Bob',     'Mouse',        '2026-02-14', 1,  24.99, 'cancelled'),
(11, 'Alice',   'Monitor',      '2026-03-01', 1, 249.00, 'completed'),
(12, 'Diana',   'Laptop Stand', '2026-03-05', 2,  39.99, 'completed');
duckdb D
duckdb D SHOW ALL TABLES;
┌──────────┬─────────┬─────────┬─────────────────────────────────────┬─────────────────────────────────────┬───────────┐
│ database │ schema  │  name   │            column_names             │            column_types             │ temporary │
│ varchar  │ varchar │ varchar │              varchar[]              │              varchar[]              │  boolean  │
├──────────┼─────────┼─────────┼─────────────────────────────────────┼─────────────────────────────────────┼───────────┤
│ duckdb   │ raw     │ orders  │ [order_id, customer_name,           │ [INTEGER, VARCHAR, VARCHAR, DATE,   │ false     │
│          │         │         │  product_name, order_date,          │  INTEGER, 'DECIMAL(10,2)', VARCHAR] │           │
│          │         │         │  quantity, unit_price,              │                                     │           │
│          │         │         │  order_status]                      │                                     │           │
└──────────┴─────────┴─────────┴─────────────────────────────────────┴─────────────────────────────────────┴───────────┘
duckdb D
duckdb D SELECT *
FROM raw.orders
ORDER BY order_id;
┌──────────┬───────────────┬──────────────┬────────────┬──────────┬───────────────┬──────────────┐
│ order_id │ customer_name │ product_name │ order_date │ quantity │  unit_price   │ order_status │
│  int32   │    varchar    │   varchar    │    date    │  int32   │ decimal(10,2) │   varchar    │
├──────────┼───────────────┼──────────────┼────────────┼──────────┼───────────────┼──────────────┤
│        1 │ Alice         │ Laptop Stand │ 2026-01-03 │        1 │         39.99 │ completed    │
│        2 │ Bob           │ USB-C Hub    │ 2026-01-04 │        2 │         29.99 │ completed    │
│        3 │ Charlie       │ Webcam       │ 2026-01-05 │        1 │         74.50 │ returned     │
│        4 │ Alice         │ Keyboard     │ 2026-01-08 │        1 │         89.00 │ completed    │
│        5 │ Diana         │ Mouse        │ 2026-01-10 │        2 │         24.99 │ completed    │

│ 6 │ Bob │ 显示器 │ 2026-01-12 │ 1 │ 249.00 │ 处理中 │ │ 7 │ Alice │ USB-C集线器 │ 2026-02-02 │ 1 │ 29.99 │ 已完成 │ │ 8 │ Charlie │ 键盘 │ 2026-02-06 │ 1 │ 89.00 │ 已完成 │ │ 9 │ Diana │ 摄像头 │ 2026-02-09 │ 2 │ 74.50 │ 已完成 │ │ 10 │ Bob │ 鼠标 │ 2026-02-14 │ 1 │ 24.99 │ 已取消 │ │ 11 │ Alice │ 显示器 │ 2026-03-01 │ 1 │ 249.00 │ 已完成 │ │ 12 │ Diana │ 笔记本支架 │ 2026-03-05 │ 2 │ 39.99 │ 已完成 │ └──────────┴───────────────┴──────────────┴────────────┴──────────┴───────────────┴──────────────┘ 12 行 7 列 duckdb D

code

## 使用源创建和运行 dbt 模型

现在我们已经在数据库中有了数据,可以开始使用 dbt 了。在 dbt 中,最重要的两个概念是模型(models)和源(sources)。

模型只是一个包含 SQL 代码片段的文件,dbt 会用它在目标数据库中创建新表或视图。

源则是数据存储中已有的表或视图,不是 dbt 创建的,例如通过应用程序或数据摄入工具加载的原始数据。源是模型引用数据库/模式中现有表的方式。你可以通过 YAML 配置文件定义源。由于我们正在处理一个订单表,我们将文件命名为 orders.yml。

在我们的示例中,我们将创建一个模型来构建一个存储已完成订单的表。由于这会引用我们现有的订单数据库表,因此为它创建一个源 YAML 文件是有意义的。文件内容如下:

orders.yml

version: 2 sources:

  • name: raw

schema: raw tables:

  • name: orders
code

我们的模型 SQL 文件如下所示:

sql

-- customer_orders_summary.sql {{ config(materialized='table') }} with completed_orders as ( select order_id, customer_name, order_date, quantity, quantity * unit_price as order_value from {{ source('raw', 'orders') }} where lower(order_status) = 'completed' ) select customer_name, count(*) as completed_order_count, sum(quantity) as total_units_purchased, round(sum(order_value), 2) as total_revenue, round(avg(order_value), 2) as average_order_value, min(order_date) as first_order_date, max(order_date) as most_recent_order_date from completed_orders group by customer_name

code

在 dbt 项目中的 models 文件夹下创建模型 SQL 文件和源 YAML 文件。

希望你能立即看到在模型文件中使用源的好处。由于 SQL 的 FROM 子句使用了引用而不是实际的表名,如果源表名将来发生更改,你只需在一个地方(源文件)更新这个更改。所有使用源文件的 SQL 都可以保持不变地运行。

好的,现在这些文件已经准备就绪,我们可以运行 dbt 转换。你可以使用 dbt run 命令来执行此操作。

(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> dbt run 20:33:39 使用 dbt=1.12.0 运行 20:33:40 注册适配器:duckdb=1.10.1 20:33:40 由于配置文件已更改,无法进行部分解析 20:33:41 [警告]:dbt_project.yml 文件中存在配置路径,这些路径不适用于任何资源 存在 1 个未使用的配置路径:

  • models.my_dbt_demo.example

20:33:41 共发现 1 个模型,1 个源,486 个宏 20:33:41

20:33:41 并发数:1 线程 (目标='dev') 20:33:41

20:33:41 1 of 1 START sql 表模型 raw.customer_order_summary ........................ [RUN] 20:33:41 1 of 1 OK 创建 sql 表模型 raw.customer_order_summary ................... [OK in 0.11s] 20:33:41

20:33:41 已完成运行 1 个表模型,耗时 0 小时 0 分钟 0.23 秒 (0.23s) 20:33:41

20:33:41 成功完成 20:33:41

20:33:41 已完成。PASS=1 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=1 (.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> .\duckdb $HOME\projects\dbt-demo\my_dbt_demo\data\duckdb.dev DuckDB v1.5.5 (Variegata) 输入 ".help" 获取使用提示。 duckdb D show all tables; ┌──────────┬─────────┬────────────────────────┬─────────────────────────────┬──────────────────────────────┬───────────┐ │ database │ schema │ name │ column_names │ column_types │ temporary │ │ varchar │ varchar │ varchar │ varchar[] │ varchar[] │ boolean │ ├──────────┼─────────┼────────────────────────┼─────────────────────────────┼──────────────────────────────┼───────────┤ │ duckdb │ raw │ customer_order_summary │ [customer_name, │ [VARCHAR, BIGINT, HUGEINT, │ false │ │ │ │ │ completed_order_count, │ 'DECIMAL(38,2)', DOUBLE, │ │ │ │ │ │ total_units_purchased, │ DATE, DATE] │ │ │ │ │ │ total_revenue, │ │ │ │ │ │ │ average_order_value, │ │ │ │ │ │ │ first_order_date, │ │ │ │ │ │ │ most_recent_order_date] │ │ │ ├──────────┼─────────┼────────────────────────┼─────────────────────────────┼──────────────────────────────┼───────────┤ │ duckdb │ raw │ orders │ [order_id, customer_name, │ [INTEGER, VARCHAR, VARCHAR, │ false │ │ │ │ │ product_name, order_date, │ DATE, INTEGER, │ │ │ │ │ │ quantity, unit_price, │ 'DECIMAL(10,2)', VARCHAR] │ │ │ │ │ │ order_status] │ │ │ └──────────┴─────────┴────────────────────────┴─────────────────────────────┴──────────────────────────────┴───────────┘ duckdb D select * from raw.customer_order_summary; ┌───────────────┬───────────────────────┬───┬─────────────────────┬──────────────────┬────────────────────────┐ │ customer_name │ completed_order_count │ … │ average_order_value │ first_order_date │ most_recent_order_date │

code

│    varchar    │         int64         │ … │       double        │       date       │          date          │
├───────────────┼───────────────────────┼───┼─────────────────────┼──────────────────┼────────────────────────┤
│ Charlie       │                     1 │ … │                89.0 │ 2026-02-06       │ 2026-02-06             │
│ Alice         │                     4 │ … │               102.0 │ 2026-01-03       │ 2026-03-01             │
│ Bob           │                     1 │ … │               59.98 │ 2026-01-04       │ 2026-01-04             │
│ Diana         │                     3 │ … │               92.99 │ 2026-01-10       │ 2026-03-05             │
└───────────────┴───────────────────────┴───┴─────────────────────┴──────────────────┴────────────────────────┘

/think

输出结果符合预期。一个新的汇总表被创建,并包含了所需的记录。关于模型和数据源的内容我就说到这里。你可能会觉得,仅仅为了一个表就做这么多工作似乎有些繁琐,确实如此,但请相信我,如果你要处理数十甚至数百个表和转换操作,投入时间创建模型和数据源是值得的。

使用 dbt 进行数据测试

使用 dbt 的另一个优势是其能够自动化你的 SQL 测试流程。测试定义(在 YAML 文件中)与模型和数据源并存,可以独立执行,也可以在项目构建时随时执行。你可以编写自己的 SQL 测试,但 dbt 也提供了四种内置的测试条件:

  • unique
  • not_null
  • relationships
  • accepted_values

我们将演示其中两种测试,让你了解它们的使用方式。

not null 测试

我们的测试将针对 customer_order_summary 表的 customer_name 列。由于我们正在测试 dbt 创建的表,我们将测试 YAML 添加到 orders.yml 文件的模型部分。现在文件内容如下:

code
# orders.yml
version: 2
sources:
- name: raw
schema: raw
tables:
- name: orders
models:
- name: customer_order_summary
columns:
- name: customer_name
data_tests:
- not_null

由于原始订单表中没有空值的 customer_name,我特意创建了一个包含空值的记录,以便演示测试失败时的情况。

code
duckdb D update raw.orders set customer_name = NULL where order_id = 1;
duckdb D select * from raw.orders;
┌──────────┬───────────────┬──────────────┬────────────┬──────────┬───────────────┬──────────────┐
│ order_id │ customer_name │ product_name │ order_date │ quantity │  unit_price   │ order_status │
│  int32   │    varchar    │   varchar    │    date    │  int32   │ decimal(10,2) │   varchar    │
├──────────┼───────────────┼──────────────┼────────────┼──────────┼───────────────┼──────────────┤
│        1 │ NULL          │ Laptop Stand │ 2026-01-03 │        1 │         39.99 │ completed    │
│        2 │ Bob           │ USB-C Hub    │ 2026-01-04 │        2 │         29.99 │ completed    │
│        3 │ Charlie       │ Webcam       │ 2026-01-05 │        1 │         74.50 │ returned     │
│        4 │ Alice         │ Keyboard     │ 2026-01-08 │        1 │         89.00 │ completed    │
│        5 │ Diana         │ Mouse        │ 2026-01-10 │        2 │         24.99 │ completed    │
│        6 │ Bob           │ Monitor      │ 2026-01-12 │        1 │        249.00 │ processing   │
│        7 │ Alice         │ USB-C Hub    │ 2026-02-02 │        1 │         29.99 │ completed    │
│        8 │ Charlie       │ Keyboard     │ 2026-02-06 │        1 │         89.00 │ completed    │
│        9 │ Diana         │ Webcam       │ 2026-02-09 │        2 │         74.50 │ completed    │
│       10 │ Bob           │ Mouse        │ 2026-02-14 │        1 │         24.99 │ cancelled    │
│       11 │ Alice         │ Monitor      │ 2026-03-01 │        1 │        249.00 │ completed    │
│       12 │ Diana         │ Laptop Stand │ 2026-03-05 │        2 │         39.99 │ completed    │
└──────────┴───────────────┴──────────────┴────────────┴──────────┴───────────────┴──────────────┘
12 rows                                                                              7 columns

现在,要运行测试,我们只需像这样输入 dbt build 命令,该命令会按照依赖顺序运行并验证 dbt 项目的选定部分。

code
(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> dbt build
08:43:19  使用 dbt=1.12.0 运行
08:43:20  注册适配器:duckdb=1.10.1
08:43:20  [警告]:dbt_project.yml 文件中存在不适用于任何资源的配置路径
存在 1 个未使用的配置路径:
- models.my_dbt_demo.example
08:43:20  发现 1 个模型,1 个测试,1 个源,486 个宏
08:43:20

08:43:20  并发数:1 线程(目标='dev')
08:43:20

08:43:20  1 of 2 START sql 表模型 raw.customer_order_summary ........................ [RUN]
08:43:20  1 of 2 OK 创建 sql 表模型 raw.customer_order_summary ................... [OK 在 0.14s]
08:43:20  2 of 2 START 测试 not_null_customer_order_summary_customer_name ................ [RUN]
08:43:20  2 of 2 FAIL 1 not_null_customer_order_summary_customer_name .................... [FAIL 1 在 0.02s]
08:43:20

08:43:20  完成运行 1 个表模型,1 个测试,耗时 0 小时 0 分钟和 0.24 秒(0.24s)
08:43:20

08:43:20  以 1 个错误、0 个部分成功和 0 个警告完成:
08:43:20

08:43:20  [错误]:在测试 not_null_customer_order_summary_customer_name (models\orders.yml) 中
08:43:20    获得 1 个结果,配置为当不等于 0 时失败
08:43:20

08:43:20    编译代码位于 target\compiled\my_dbt_demo\models\orders.yml\not_null_customer_order_summary_customer_name.sql
08:43:20

08:43:20  完成。PASS=1 WARN=0 ERROR=1 SKIP=0 NO-OP=0 REUSED=0 TOTAL=2

问题已被检测并报告。当后续数据测试失败时,dbt 不会删除或回滚模型。不过,失败测试下游的模型在构建过程中通常会被跳过。如果你想在不重新创建任何表等操作的情况下运行测试,只需使用 dbt test 命令。

允许值测试

这正是其名称所暗示的功能。它使你能够测试某一列是否仅包含特定值。如果我们查看 orders 表,可以看到 order_status 列应仅包含 completed、processing、returned 或 cancelled 这四个值。现在让我们更新表并将其中一个值更改为其他内容。

code
duckdb D select * from raw.orders where order_id = 10;
┌──────────┬───────────────┬──────────────┬────────────┬──────────┬───────────────┬──────────────┐
│ order_id │ customer_name │ product_name │ order_date │ quantity │  unit_price   │ order_status │
│  int32   │    varchar    │   varchar    │    date    │  int32   │ decimal(10,2) │   varchar    │
├──────────┼───────────────┼──────────────┼────────────┼──────────┼───────────────┼──────────────┤
│       10 │ Bob           │ Mouse        │ 2026-02-14 │        1 │         24.99 │ cancelled    │
└──────────┴───────────────┴──────────────┴────────────┴──────────┴───────────────┴──────────────┘
duckdb D update raw.orders set order_status = 'invalid' where order_id = 10;
duckdb D select * from raw.orders where order_id = 10;
┌──────────┬───────────────┬──────────────┬────────────┬──────────┬───────────────┬──────────────┐
│ order_id │ customer_name │ product_name │ order_date │ quantity │  unit_price   │ order_status │
│  int32   │    varchar    │   varchar    │    date    │  int32   │ decimal(10,2) │   varchar    │
├──────────┼───────────────┼──────────────┼────────────┼──────────┼───────────────┼──────────────┤
│       10 │ Bob           │ Mouse        │ 2026-02-14 │        1 │         24.99 │ invalid      │
└──────────┴───────────────┴──────────────┴────────────┴──────────┴───────────────┴──────────────┘
code

由于我们正在测试一个源表,因此应将测试的 YAML 配置放在 YAML 文件的 sources 部分。如果你想的话,可以保留或删除原始的 null 测试。我保留了它。

orders.yml

version: 2 sources:

  • name: raw

schema: raw tables:

  • name: orders

columns:

  • name: order_status

data_tests:

  • accepted_values:

arguments: values:

  • completed
  • processing
  • returned
  • cancelled

models:

  • name: customer_order_summary

columns:

  • name: customer_name

data_tests:

  • not_null
code

我们正在对现有表运行测试,因此不需要运行构建命令。我们可以直接使用 dbt test。

(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> dbt test 09:08:04 Running with dbt=1.12.0 09:08:04 Registered adapter: duckdb=1.10.1 09:08:04 [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources. There are 1 unused configuration paths:

  • models.my_dbt_demo.example

09:08:04 Found 1 model, 2 data tests, 1 source, 486 macros 09:08:04 09:08:04 Concurrency: 1 threads (target='dev') 09:08:04 09:08:04 1 of 2 START test not_null_customer_order_summary_customer_name ................ [RUN] 09:08:04 1 of 2 FAIL 1 not_null_customer_order_summary_customer_name .................... [FAIL 1 in 0.03s] 09:08:04 2 of 2 START test source_accepted_values_raw_orders_order_status__completed__processing__returned__cancelled [RUN] 09:08:04 2 of 2 FAIL 1 source_accepted_values_raw_orders_order_status__completed__processing__returned__cancelled [FAIL 1 in 0.02s] 09:08:04 09:08:04 Finished running 2 data tests in 0 hours 0 minutes and 0.11 seconds (0.11s). 09:08:04 09:08:04 Completed with 2 errors, 0 partial successes, and 0 warnings: 09:08:04 09:08:04 [ERROR]: in test not_null_customer_order_summary_customer_name (models\orders.yml) 09:08:04 Got 1 result, configured to fail if != 0 09:08:04 09:08:04 compiled code at target\compiled\my_dbt_demo\models\orders.yml\not_null_customer_order_summary_customer_name.sql 09:08:04 09:08:04 [ERROR]: in test source_accepted_values_raw_orders_order_status__completed__processing__returned__cancelled (models\orders.yml) 09:08:04 Got 1 result, configured to fail if != 0 09:08:04 09:08:04 compiled code at target\compiled\my_dbt_demo\models\orders.yml\source_accepted_values_raw_ord_0932c13ab9fb3a73a9e3e3c87c81af50.sql 09:08:04 09:08:04 Done. PASS=0 WARN=0 ERROR=2 SKIP=0 NO-OP=0 REUSED=0 TOTAL=2 (.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo>

code

另外两种内置测试类型同样易于设置和运行,因此我在这里就不再继续介绍了。

## 使用 dbt 文档化你的系统

我们最后要介绍的 dbt 入门主题可以说是其最出色的功能之一。大多数文档最初都带着良好的意图,但最终会逐渐变得过时。dbt 的文档方式有所不同。

由于你的模型、测试和元数据都与 SQL 代码并存,dbt 可以自动生成项目文档。更重要的是,它还会创建一个可视化血缘图,清晰展示模型之间的依赖关系。

当新成员加入项目时,这个功能非常有价值,因为他们无需逆向工程数百个 SQL 文件,几乎可以立即看到整个转换流水线。

这是那种在你接手别人的分析项目之前,似乎并不特别令人兴奋的功能。

一开始,dbt 就可以为你生成一些自动化文档,但这也是一种投入越多,产出越好的事情。在不对项目做任何额外操作的情况下,这就是你获得的基础文档。我们使用 `dbt docs generate` 命令来生成如下所示的文档。

(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> dbt docs generate 09:21:19 正在使用 dbt=1.12.0 运行 09:21:19 已注册适配器:duckdb=1.10.1 09:21:19 [警告]:dbt_project.yml 文件中存在配置路径,这些路径不适用于任何资源。 存在 1 个未使用的配置路径:

  • models.my_dbt_demo.example

09:21:19 找到 1 个模型,2 个数据测试,1 个源,486 个宏 09:21:19 09:21:19 并发:1 线程(目标='dev') 09:21:19 09:21:19 正在构建目录 09:21:19 目录已写入 C:\Users\thoma\projects\dbt-demo\my_dbt_demo\target\catalog.json

code

生成文档后,我们可以使用 `dbt docs serve` 命令在浏览器中查看文档。

(.venv-core) PS C:\Users\thoma\projects\dbt-demo\my_dbt_demo> dbt docs serve 09:24:48 正在使用 dbt=1.12.0 运行 正在 8080 端口提供文档服务 要从浏览器访问,请导航到:http://localhost:8080 按 Ctrl+C 退出。 127.0.0.1 - - [04/Aug/2026 10:24:48] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [04/Aug/2026 10:24:48] "GET /manifest.json?cb=1785835488811 HTTP/1.1" 200 - 127.0.0.1 - - [04/Aug/2026 10:24:48] "GET /catalog.json?cb=1785835488811 HTTP/1.1" 200 - 127.0.0.1 - - [04/Aug/2026 10:24:49] 代码 404,消息 文件未找到 127.0.0.1 - - [04/Aug/2026 10:24:49] "GET /%7B%7B%20getIcon(item.type,%20'on')%20%7D%7D HTTP/1.1" 404 - 127.0.0.1 - - [04/Aug/2026 10:24:49] 代码 404,消息 文件未找到 127.0.0.1 - - [04/Aug/2026 10:24:49] "GET /%7B%7B%20getIcon(item.type,%20'off')%20%7D%7D HTTP/1.1" 404 -

code

你应该会看到一个浏览器窗口打开,显示如下内容:

如我所说,它看起来非常基础,但仍然很有用。要看到真正的强大功能,你需要在 `orders.yml` 文件中以 YAML 格式添加自己的描述性文档文本。以下是一个示例。

版本: 2 源数据:

  • 名称: raw

描述: "在 dbt 转换运行之前直接在 DuckDB 中创建的原始演示数据。" 模式: raw 表:

  • 名称: orders

描述: "用作客户订单摘要模型输入的示例客户订单。" 列:

  • 名称: order_id

描述: "分配给每个订单的唯一标识符。"

  • 名称: customer_name

描述: "下单客户的名称。"

  • 名称: product_name

描述: "客户购买的产品。"

  • 名称: order_date

描述: "下单日期。"

  • 名称: quantity

描述: "订购的产品单位数量。"

  • 名称: unit_price

描述: "下单时每个产品单位的价格。"

  • 名称: order_status

描述: "订单当前状态;仅限四个支持的状态值。" 数据测试:

  • 允许的值:

参数: 值:

  • completed
  • processing
  • returned
  • cancelled

模型:

  • 名称: customer_order_summary

描述: > 由 dbt 创建的表格,每行代表一个客户。仅包含已完成的订单,并汇总订单数量、购买单位数、收入和订单日期。 列:

  • 名称: customer_name

描述: "摘要行所代表的客户。" 数据测试:

  • 非空
  • 名称: completed_order_count

描述: "客户下达的已完成订单数量。"

  • 名称: total_units_purchased

描述: "客户所有已完成订单的总购买单位数。"

  • 名称: total_revenue

描述: "客户所有已完成订单的总收入。"

  • 名称: average_order_value

描述: "客户所有已完成订单的平均订单价值。"

  • 名称: first_order_date

描述: "客户最早的已完成订单日期。"

  • 名称: most_recent_order_date

描述: "客户最近的已完成订单日期。"

code

现在,当我们运行这两个 dbt 文档命令时,会得到更丰富的输出,如下所示。

## 后续阶段

dbt 是一个庞大的生态系统,正如我所解释的,我只想简要介绍其运作的一些基础知识。目前,我对使用 dbt 的知识感到满意。如果你想更深入地了解,可以进一步研究以下主题,这些主题建立在我在这里讨论的内容之上。

- 增量模型:仅处理新记录或更改的记录,而不是在每次运行时重建整个表。

- Jinja:一种模板语言,允许你在 SQL 中添加变量、条件、循环和可重用的函数。

- 宏:可重用的 Jinja 和 SQL 逻辑片段,可以接受参数并生成 SQL。

- 快照:记录源记录随时间的变化,使你能够保留它们的历史值。

- 可复用的包:使用其他 dbt 项目创建的模型、宏和测试,而不是自己构建所有内容。

这是官方 dbt Labs 主页的链接,你可以在那里找到有关 dbt 的所有必要信息。

https://www.getdbt.com

学习愉快。