想象你在一家餐厅的后厨当主管,每天清晨,生鲜、冻品、半成品从不同供应商涌入后厨——这就是原始数据。传统做法是让厨师在仓库外先把菜全部做熟再端进餐厅(ETL),而现在流行的做法是把食材先搬进厨房(数仓),再在厨房里现场加工(ELT)。可问题来了:菜谱零散、谁先做哪道菜全凭默契、食材新鲜度无人追踪——整个后厨一片混乱。 dbt(data build tool)正是这套“后厨调度系统”。它不亲自掌勺(不移动数据),而是把每道菜的菜谱(SQL模型)集中管理,自动识别“红烧肉需要提前炖高汤”这类依赖关系,按正确顺序调度厨师开工;它还支持把菜提前备好放保温柜(物化表)、只补做新点的份量(增量更新)、记录食材每天的保鲜状态(快照)。本文就用这套“厨房经济学”,帮你零基础掌握 dbt 的核心转换功能与建模方法。
对 AI 从业者而言,dbt 是数据工程领域的“通用语法”。掌握它,你就拥有了把原始数据高效加工成特征表和指标层的核心能力——这是构建任何 AI 应用都绕不开的数据地基。
可视化演示
动态演示dbt数据转换核心功能与建模的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
dbt数据转换核心功能与建模:dbt核 dbt核心架构与SQL转换模型 物化策略与table、view 核心机制可视化 dbt数据转换核心功能与建模整体架构与关键组件
dbt是什么
后厨里最资深的不是颠勺的厨师,而是那个拿着进度板调度全场的“行政总厨”。他清楚地知道:哪道菜需要先炖高汤、哪道菜可以等客人点了再起锅、哪位厨师现在手上没活。这个行政总厨,就是 dbt 在数据厨房里的角色。它不搬运食材、不直接烹饪,却让整个厨房高效运转。
传统 ETL 工具好比“中央厨房”,食材在外面全部加工成真空料理包再送入餐厅;而 dbt 代表的 ELT 理念恰好相反——先把食材(原始数据)搬进数仓这个大厨房,再在厨房内根据不同菜品需求现场加工。这样一来,数据只被移动一次,之后全部靠 SQL 在仓库里就地取材,成本和延迟都大幅下降。
这个角色的诞生催生了一个新职位——分析工程师:既懂业务指标又懂数据工程,用代码化的方式管理数据转换。dbt 的核心价值也正在于此:所有模型(菜谱)纳入版本控制(Git),依赖关系自动解析,测试可以随时运行,数据团队从“作坊式”迈向“现代化流水线”。
理解了这层定位,你就明白了 dbt 为什么能成为现代数据团队的基础设施——它像行政总厨那样,把“做什么、按什么顺序做”的调度权集中起来,剩下的交给标准化的菜谱(SQL 模型)去执行。
- 源系统数据旅程的起点,可能是订单库、日志文件或第三方 API。
- Load 阶段dbt 不负责这步,通常由 Fivetran、Airbyte 等工具把数据“搬运”进数仓。
- dbt Transform 阶段dbt 登场,在数仓内部执行 SQL 完成清洗、关联、聚合。
- 分析结果产出为 BI 报表、特征宽表或指标服务直接使用的数据表。
项目结构
一间正规厨房,食材要分冰箱、冷藏柜、调料架摆放,厨师才能随手拿到所需。dbt 项目的目录结构也是同样的“厨房分区学”:每种文件有它固定的位置,井然有序。
最顶层的 dbt_project.yml 是厨房的“消防平面图”——它声明了项目的名字、数仓连接方式、默认的物化策略和目录映射规则。所有刀(模型)放在 models/ 目录——这是菜谱柜,每个 .sql 文件就是一道菜的菜谱。snapshots/ 目录存放“食材台账本”(快照),tests/ 是质检工位,macros/ 放可复用的“调味配方”(Jinja 宏)。
菜谱柜内部还要继续分区:staging/ 放“洗菜切菜”的菜谱(对源表做最基础的清洗重命名)、intermediate/ 放“半成品加工”的菜谱(业务中间逻辑)、marts/ 放“摆盘出品”的菜谱(面向业务的分析宽表)。此外,packages.yml 负责管理外部“调料包”——社区贡献的 dbt 扩展包,像引入开源库一样复用别人的菜谱。
这套分区法在实践中非常好用:新同事入职,看一眼目录结构就知道该去哪里找菜谱、改哪个文件,这正是 dbt 项目“约定优于配置”的精髓——把厨房规则写在布局里,而不是靠口头传承。
- dbt_project.yml项目配置文件,声明项目名、目标数仓、目录到 schema 的映射。
- models 目录所有 SQL 模型的存放位置,dbt 自动递归读取其子目录。
- snapshots 目录存放快照配置,记录维度表的变更历史。
- tests 与 macros前者是质量测试用例,后者是可复用的 Jinja 宏代码。
SQL建模
后厨里每道菜都要有一张标准菜谱:主料、辅料、步骤、火候、出锅标准。dbt 的模型(model)就是数据菜谱,每张菜谱的核心只有一条 SELECT 语句——这就是 dbt 建模的第一性原则:你不需要写 CREATE TABLE,不需要写 INSERT/UPDATE,只需要用 SELECT 描述“这道菜长什么样”,dbt 会自动帮你完成建表和写入。
菜谱的写作推荐用 CTE(Common Table Expression)拆解步骤,就像菜谱写“第一步焯水,第二步爆香,第三步勾芡”。一段复杂的逻辑被拆成 with 清洗 as (...), 关联 as (...), 聚合 as (...),每一步都是一段独立的 SELECT,读起来一目了然。Jinja 模板则是菜谱里的“万能调料公式”:{% raw %}{{ ref('订单') }}{% endraw %} 引用其他模型,{% if is_incremental() %} 处理增量逻辑,{% raw %}{{ config(materialized='table') }}{% endraw %} 声明烹饪方式。
借助宏(macro),你还能把一段常用逻辑封装成“预制调料包”:比如统一的日期格式转换、统一的金额四舍五入规则,在多个模型里一行调用,避免每个菜谱都重复写一遍相同步骤。dbt 的 ref() 不只是简单的字符串引用,它同时建立依赖关系和自动处理环境切换——开发环境跑开发库的 stg_orders,生产环境自动切到生产库的同名模型,你不需要改一行代码。
把复杂逻辑拆成 CTE 的每一层,就像把一道大菜拆成多个可独立检验的步骤——某一步出错了,定位到那层 CTE 单独排查即可。这就是 dbt 建模方法论的核心:让复杂逻辑变得可读、可测、可复用。
- 清洗层(内层 SELECT)对源表做类型转换、去重、列重命名。
- 关联层(中层 SELECT)通过 JOIN 把多张表按业务键拼接。
- 聚合层(外层 SELECT)按维度分组计算指标,如 SUM、COUNT、AVG。
- Result 产出最终结果以物化策略写入数仓,供下游模型或 BI 使用。
物化策略
同一道红烧肉,可以提前批量烧好放保温柜里(客人来了直接盛);也可以等客人点了再下锅(新鲜热乎但要等);还可以只备好肉和调料,有人点才现烧。物化策略(materialization)就是“这道数据菜如何准备”的四种模式。
table 是“保温柜模式”:模型执行时全量重算一张物理表,查询直接读表、速度极快,代价是每次构建都要把整道菜从零做一遍,适合数据量适中(如百万行内)、查询频繁的中间表和结果表。view 是“现点现炒模式”:模型只保存一段 SQL,每次查询都实时计算,不占存储空间、永远新鲜,但大查询很慢,适合开发调试和底层轻量模型。incremental 是“常备菜补新模式”:首次运行建全表,之后每次只计算新增数据再合并进来,是大数据量模型的性能救星。ephemeral 是“内部配菜模式”:模型不落库,其他模型引它时其 SQL 被直接内联进去,适合只被复用一两次的中间逻辑。
\text{Incremental 策略} = O(\text{首跑全量}) + O(\Delta \text{每次})
- table 全量重算每次 dbt run 都 DROP 后重建,成本随数据量线性增长。
- view 实时计算不存储结果,把 SELECT 保存为视图,查询时即时执行。
- incremental 增量合并首次建表耗时 O(全量),后续只计算新到数据耗时 O(Δ)。
依赖管理
G = (V,\ E),\quad E = \{(u, v) \mid v \text{ 中调用 } ref(u)\}
\text{执行顺序} = \text{topological\_sort}(G)
> 第一行:图 G 由节点集 V(所有模型)和边集 E(ref 依赖)构成;第二行:dbt 按图的拓扑排序确定执行顺序,保证每个模型跑之前它的上游已经就绪。1. 节点集 V:每个 dbt 模型对应图中的一个节点。$$\displaystyle V = \{\text{model}_1, \text{model}_2, \ldots, \text{model}_n\}$$2. 边集 E:模型 A 中写了 ref(B),就产生一条从 B 指向 A 的边。$$\displaystyle E = \{(B, A) \mid A \text{ 调用 } ref(B)\}$$3. 拓扑排序:把图中的节点排成一个线性序列,边的方向总指向序列后方。$$\displaystyle L = \text{topo\_sort}(G),\ \forall (u,v) \in E:\ u \text{ 排在 } v \text{ 前}$$4. 执行调度:dbt 按序列 L 依次执行模型,同层无依赖可并行。$$\text{run} = \text{exec}(L)$$有了依赖图这张"开工单",后厨就不会出现"菜做好了却发现没买菜"的尴尬。更重要的是,DAG 是 dbt 文档、测试、增量更新的共同地基——它让整个数据管道可以被整体理解、被智能调度。增量更新
每天营业结束,后厨不会把没用完的食材全扔了重新采购——那太浪费了。更聪明的做法是:昨天备好的菜继续用,今天只需要补充新采购的食材、补做新增的份量。dbt 的增量更新(incremental)正是这套“只做增量”的思路。
实现增量更新的模型,需要在 config 中声明 materialized='incremental',在 SQL 中用 {% if is_incremental() %} 包裹过滤条件。首次运行时,is_incremental() 返回 False,dbt 执行全量计算建表;后续运行时返回 True,只对新增时间段的数据做处理。经典的写法是用时间戳过滤:WHERE created_at > (SELECT MAX(created_at) FROM 目标表)——即“只处理上次算完之后新来的数据”。
增量更新还要考虑去重和更新。如果源流水允许修改(比如订单状态从“待支付”变成“已支付”),单纯追加新行会导致老数据永远是旧的。此时需要配置 unique_key,让 dbt 在底层生成 MERGE 语句:匹配到相同主键就更新旧行,匹配不到就插入新行。但要注意:增量模型默认感知不到源表里被删除的记录——如果数据被物理删除,增量表里它还会“赖着不走”,这时需要额外的软删除标记或定期全量重建。
增量更新把“每天全量重算几亿行”变成了“每天只算几千行新增”,节省的时间是数量级的。它像后厨的常备菜制度:既有现成菜品的速度,又不会因为每天推倒重来而浪费食材——但前提是记得处理“改单”(更新)和“退货”(删除),否则账目就会失真。
- 增量条件过滤通过时间戳筛选出“上次跑完之后”的新记录集合 Δ。
- 变换处理对 Δ 应用与全量相同的数据清洗、关联、聚合逻辑。
- 主键合并dbt 生成 MERGE 语句,按 unique_key 决定 INSERT 还是 UPDATE。
- 游标推进更新 last_run 时间戳,下次运行时基于新的边界继续增量。
数据快照
食材价格每天在变,后厨想追溯“上周三的牛肉进价是多少”,如果只记当前价格就无从查起。dbt 的快照(snapshot)就是数据世界里的“进货台账”——它记录每条数据每一段历史时期的样子,让你能还原任意时间点的状态。
快照基于SCD Type 2(慢变维度类型二)原理:当源表中的一条记录发生变化时,快照表不会覆盖旧记录,而是把旧记录的 valid_to 设为变更时间(“关门”),同时插入一条新记录,其 valid_from 为变更时间(“开门”)、valid_to 为 NULL(表示当前有效)。这样一条业务记录的全部历史版本都躺在快照表里,任意时刻的状态通过时间区间就能查出来。
使用快照很简单:在 snapshots/ 目录建一个 .sql 文件,用 {% snapshot 快照名 %} 定义目标表、唯一键和检查变更的列。dbt 每次运行 dbt snapshot 命令时,会比较源表中指定列的值,发现变化就自动执行“关旧开新”。这个机制非常适合追踪商品价格变化、用户等级变化、订单状态流转等“维度的历史足迹”。
快照表就是数据的“时光机”:分析“调价前后销售额对比”、重建“用户上个月所处等级”这类问题,一条 SQL 按时间区间关联即可。它和增量更新是绝配——增量让大表跑得快,快照让历史查得回。
[r.\text{valid\_from},\ r.\text{valid\_to}),\quadr.\text{valid\_to} =\begin{cases}t_{\text{变更}}, & \text{该版本已被后续版本替换} \\\text{NULL}, & \text{该版本为当前有效版本}\end{cases}- 快照记录结构每条记录额外携带 valid_from 和 valid_to 两个时间列。
- 首次快照第一次运行时把源表全量写入,设置 valid_from 为当前时间。
- 检测变更对比唯一键相同的记录,业务列取值是否发生变化。
- 版本操作旧版本关闭(valid_to 置为 now),新版本开启(valid_from 为 now)。
最佳实践
一间米其林后厨能长期稳定出品,靠的绝不是某个厨师的个人手感,而是标准化的流程、严格的质检和成文的规范。dbt 的最佳实践,也是把这套“后厨管理哲学”落成制度。
分层建模是第一条铁律:staging 层只做轻量清洗和标准化(类型转换、列重命名、去重),intermediate 层实现核心业务逻辑(多表关联、指标预聚合),marts 层面向分析场景输出最终宽表。分层让每一层职责单一,下游报错时可以顺藤摸瓜快速定位。测试验证是第二道防线:not_null(主键不能为空)、unique(唯一值)、accepted_values(字段取值枚举)、relationships(外键关联完整性)四类内置测试覆盖了主要质量场景,还可以用 tests/ 目录自定义更复杂的 SQL 测试。文档生成让菜谱自动可读:dbt docs generate 生成全项目文档站,包含每个模型的字段说明、血缘关系图(lineage)和测试结果。CI 集成则是把 dbt test 放进代码合并的强制关卡——每次 Pull Request 都自动跑测试,问题代码根本进不了主分支。
这套最佳实践带给团队的收益是指数级的:新同事能快速接手任何模型,业务方能在文档站自助查指标口径,错误在进入生产前就被测试拦截。就像米其林后厨的“出品检查单”——每道菜出锅前都要过秤、摆盘、复核,稳定输出是靠制度而非运气。
- 每层测试兜底staging/marts 每层都配测试,错误率被逐层拦截。
- 串联乘积效应多环节可靠性相乘,单一环节失控会拖垮全局。
- 小概率近似展开当各环节错误率都很低时,总错误率近似为各环节错误率之和。
- 文档与 CI 护航文档让团队理解模型、CI 保证每次变更经过验证。
AI 应用场景
机器学习特征加工管道
阶段:数据准备与特征工程
解决的问题:特征工程最耗时的不是模型调参,而是把散落在各业务库的原始日志加工成模型可用的特征宽表。传统做法是写一堆临时 Python 脚本或存储过程,逻辑散落、无版本控制、依赖关系混乱,改一个特征要花半天排查。dbt 将特征加工拆成 staging → intermediate → marts 三层,每个特征一个模型,通过 ref() 声明依赖,天然形成可追溯的特征血缘图。
具体案例:某消费金融公司的风控团队用 dbt 构建贷前特征平台——用 incremental 模型每日增量聚合用户的交易流水特征(近 7 天/30 天消费频次、金额波动等),配合快照记录额度调整历史,最终产出 200+ 特征的宽表供 XGBoost 模型离线训练和在线打分。特征迭代只需新增一个 model 并跑 dbt run,半天即可上线新特征。
指标体系口径统一
阶段:BI 指标服务层
解决的问题:业务方问“用户数”和"GMV",数仓里可能有五套口径的 SQL 在跑,各说各话。dbt 的 marts 层把核心指标定义为唯一的模型,如 marts/gmv_daily.sql,所有下游统一引用这个模型,口径天然收敛。配合 dbt 测试做 uniqueness 和 not_null 校验,保证指标数据永远可信。
具体案例:一家电商公司用 dbt 重建了指标层后,把原先分散在 8 个报表里的 GMV 计算逻辑合并为 3 个 marts 模型,并在文档站生成指标口径说明。此后业务侧的日活、GMV、转化率三大指标全部引用同一数据源,跨部门对数的争论彻底消失。
Python 演示
from datetime import datetime, timedelta class MiniDBT: """迷你版 dbt:演示分层转换与增量更新思想""" def __init__(self): self.raw_orders = [] # 源数据层:原始订单 self.cleaned_rows = [] # staging 层:清洗后记录 self.product_stats = {} # marts 层:商品聚合统计 self.last_run = datetime(2024, 1, 1) def generate_raw(self, days=20): """模拟生成 days 天的原始订单数据,模拟源系统""" products = ['咖啡', '奶茶', '蛋糕'] for i in range(days * 10): day = datetime(2024, 1, 1) + timedelta(days=i % days) self.raw_orders.append({ 'order_date': day, 'product': products[i % 3], 'amount': 25.0 + (i * 3) % 20, 'status': 'paid' if i % 5 != 0 else 'cancelled' }) def transform_row(self, row): """staging 层:清洗单条记录(剔除取消单、格式化金额)""" if row['status'] != 'paid': return None return { 'order_date': row['order_date'], 'product': row['product'].strip(), 'amount': round(float(row['amount']), 2) } def run_incremental(self): """增量更新:只处理 last_run 之后的新订单,模拟 is_incremental()""" new_orders = [ row for row in self.raw_orders if row['order_date'] > self.last_run ] added = 0 for order in new_orders: clean = self.transform_row(order) if clean is None: continue self.cleaned_rows.append(clean) key = clean['product'] if key not in self.product_stats: self.product_stats[key] = {'orders': 0, 'revenue': 0.0} self.product_stats[key]['orders'] += 1 self.product_stats[key]['revenue'] += clean['amount'] added += 1 if new_orders: self.last_run = max(row['order_date'] for row in new_orders) return added def generate_report(self): """marts 层:输出最终聚合报表""" print('商品 | 订单数 | 总营收') print('-' * 30) for product, stat in self.product_stats.items(): print(f"{product} | {stat['orders']} 笔 | ¥{stat['revenue']:.2f}") if __name__ == '__main__': dbt = MiniDBT() dbt.generate_raw() first = dbt.run_incremental() print(f'首轮增量处理: {first} 笔订单') second = dbt.run_incremental() print(f'第二轮增量处理: {second} 笔订单(无新数据)') dbt.generate_report()
self.raw_orders = []模拟“源数据层”,存放从业务系统同步来的原始订单。def generate_raw(self, days=20)模拟源系统每天产生 10 条新订单,含日期、商品、金额、状态字段。def transform_row(self, row)staging 层逻辑:过滤掉 cancelled 的订单、去除商品名空格、金额四舍五入,返回 None 表示该行被清洗剔除。if row['order_date'] > self.last_run增量更新的核心:只取上次运行之后的新订单,等价于 dbt 中is_incremental()配合时间戳过滤。self.last_run = max(...)推进游标:每次跑完把 last_run 更新为最新订单时间,下一次增量以此为界,与 dbt 中MAX(updated_at)异曲同工。self.product_stats[key]['orders'] += 1marts 层聚合:按商品维度累计订单数和营收,对应 SQL 中的GROUP BY加SUM/COUNT。
常见误区
误区dbt 跟 DataStage、Informatica 一样,负责把数据从业务库抽取出来,清洗后再加载到数据仓库——它是一个 ETL 工具。
正解dbt 是 ELT 工具,它明确不负责抽取和加载。数据先由 Fivetran/Airbyte 等工具搬到数仓(Load),dbt 只做数仓内部的转换(Transform 的 T)。它不移动数据、不连接生产库,只执行 SQL 把数仓里的表变成新的表/视图。用厨房比喻说:搬食材的库管(EL)和炒菜的厨师(dbt)是两个人,dbt 只在厨房里干活。
为何常见:传统数仓时代 ETL 工具大包大揽,人们习惯用一个工具走完整个流程;而 dbt 诞生于云数仓时代,计算能力集中在仓库内,T 单独拆出来更灵活。
误区既然模型最终要产出物理表,那我得写 CREATE TABLE、INSERT INTO 这些完整 SQL 吧?存储过程怎么写我就怎么写。
正解dbt 模型的核心只需要一条 SELECT。dbt 根据你配置的物化策略,自动生成 CREATE TABLE AS、CREATE VIEW AS 或 MERGE 语句并执行。你只管描述“结果长什么样”,不用关心“怎么建出来的”——就像点菜只管说“要红烧肉”,不用管厨师怎么烧。
为何常见:长期写存储过程和手写 DDL 的思维惯性,让人难以相信“一条 SELECT 就够了”。这正是 dbt 的设计哲学:声明式建模,而非命令式。
误区view 每次查询都要现算,性能肯定差,所以所有模型都应该用 table,一劳永逸。
正解table 有代价——每次 dbt run 都要全量重算,数据量大时非常耗时。view 不占存储、永远查的是最新数据且构建成本为零,非常适合开发调试期和底层小表。incremental 则是大表场景的折中。物化策略没有绝对好坏,只有“当前场景合不合适”。
为何常见:性能至上思维让人只盯着“查询快不快”,忽略了“构建贵不贵”和“数据鲜不鲜”的权衡。
误区用了 incremental 后,源表里被删除的记录应该也会从目标表消失,因为它是“同步”的。
正解incremental 的本质是“新增和更新”,默认不感知删除。源表删除的行会永远残留在目标表中,除非你配置软删除列并处理,或者定期做全量重建。它更像“每天补新货”而不是“每日盘点对账”。
为何常见:人们容易把批处理增量误当成实时同步,忽略了它只在新增集合 Δ 上操作的事实。
误区快照就是给表做个副本存起来,防止数据被误删了可以恢复。
正解dbt snapshot 是 SCD Type 2 慢变维度实现,它的目标是追踪维度属性(如商品价格、用户等级)的历史变化过程,让你能回答“3 月 5 日这个商品卖多少钱”这类时间旅行问题。它不是灾备工具,而是一种数据建模策略。
为何常见:“快照”这个中文词天然让人联想到数据库存储备份,而忽略了 dbt 语境下它指的是“历史版本管理”。
面试题
Q1. dbt 在 ELT 架构中承担哪一环?
- A.从业务库抽取数据
- B.将数据加载进数仓
- C.数仓内做数据转换← 正确答案
- D.对结果做报表展示
答案:C。dbt 全称 data build tool,定位是 ELT 中的"T"——数据已经在数仓里,dbt 通过执行 SQL 完成转换。抽取(E)和加载(L)由 Fivetran/Airbyte 等工具承担。就像行政总厨只管做菜,买菜和搬货是别人的事。
Q2. dbt 模型文件的核心内容是什么?
- A.一段 INSERT 语句
- B.一条 SELECT 查询← 正确答案
- C.整段 CREATE TABLE
- D.一组 Python 脚本
答案:B。dbt 模型的本质是“一条 SELECT + 头部配置”。dbt 根据物化策略自动生成建表和写库语句,模型作者只负责描述结果。选 C 是传统 ETL 思维——写菜谱的人不需要亲自烧火。
Q3. 下列哪些属于 dbt 支持的物化策略?
- A.table 实体表策略← 正确答案
- B.view 视图策略← 正确答案
- C.index 索引策略
- D.incremental 增量策略← 正确答案
答案:ABD。dbt 提供四种物化策略:table(物理表)、view(视图)、incremental(增量表)、ephemeral(临时内联)。index 是数据库索引概念,不属于 dbt 物化策略——索引是数据库的查询加速手段,而物化策略回答的是“模型结果以什么形态存储”。
Q4. ref() 函数的核心作用是什么?
- A.刷新数据结果缓存
- B.声明模型间的依赖← 正确答案
- C.直接引用外部数据库
- D.删除掉过期历史数据
答案:B。ref('模型名') 声明当前模型依赖另一个 dbt 模型,同时自动处理环境切换。dbt 基于所有 ref() 构建 DAG,按拓扑序执行。这不是缓存刷新也不是删数操作——它是“菜谱之间的引用”,驱动后厨确定开工顺序。
Q5. 以下哪种场景最适合用 view 物化策略?
- A.数据量极大且查询频繁
- B.开发期频繁调整逻辑← 正确答案
- C.需要增量更新的历史大表
- D.跨库同步大批量数据
答案:B。view 不占存储、构建零成本、永远实时,非常适合开发调试期频繁改 SQL 的场景——改完 dbt run 立即生效,不用等全量重算。数据量大且查询频繁适合 incremental,历史大表适合增量更新,跨库同步不是 dbt 的职责。
Q6. 关于 dbt 增量更新的说法,正确的有哪些?
- A.通过时间戳筛选新增数据← 正确答案
- B.配置 unique_key 支持更新← 正确答案
- C.每次全量重建结果表
- D.自动感知源表删除记录
答案:AB。增量更新通过 is_incremental() 配合时间戳过滤只算新增数据;unique_key 让 dbt 生成 MERGE 实现“有则更新、无则插入”。它不会全量重建,也默认感知不到源表删除——想处理删除需要软删除标记或定期全量重建。
Q7. incremental 物化策略在底层执行时使用什么操作?
- A.直接插入全部的数据
- B.MERGE 增量合并更新← 正确答案
- C.TRUNCATE 后全部重建
- D.重建视图方式来刷新
答案:B。dbt 的 incremental 策略在配置 unique_key 后,底层生成 MERGE 语句(部分数仓为 INSERT OVERWRITE):相同主键更新、新主键插入。TRUNCATE+重建是 table 策略的行为,直接插入全部数据则失去了增量的意义。
Q8. dbt 快照(snapshot)实现的是哪种维度建模策略?
- A.SCD Type 1 覆盖更新
- B.SCD Type 2 保留历史← 正确答案
- C.SCD Type 3 存两版本
- D.SCD Type 4 拆分历史表
答案:B。dbt snapshot 基于 SCD Type 2:记录变更时旧记录关闭 valid_to、新记录开启新版本,完整保留所有历史状态。Type 1 是直接覆盖不保留历史,Type 3 只保留“当前+上一次”两版,Type 4 是历史与当前分表——都不是 dbt 快照的行为。
Q9. 采用 dbt 分层建模带来的优势有哪些?
- A.各层职责清晰可查← 正确答案
- B.减少重复计算逻辑← 正确答案
- C.完全消除数据冗余
- D.支持按层独立重跑← 正确答案
答案:ABD。staging→intermediate→marts 分层让每层职责单一、问题定位快;公共逻辑下沉到 intermediate 层避免每个 marts 模型重复计算;某层出错只需重跑该层及下游。但它无法“完全消除”冗余——刻意保留的中间层本质就是一种冗余,用空间和存储换可维护性。
Q10. dbt 按什么顺序执行有依赖关系的模型?
- A.按模型文件名排序
- B.按模型创建时间排序
- C.按依赖 DAG 拓扑序← 正确答案
- D.随机顺序并行执行
答案:C。dbt 通过扫描所有 ref() 调用构建有向无环图(DAG),然后做拓扑排序得到执行序列,保证被依赖的模型先运行。文件名、创建时间都与执行顺序无关;“随机并行”更不可能——那会让上游还没算完下游就开跑。