Dbt项目最佳实践指南-1
本文介绍一些dbt项目最佳实践,包括如何结合数据仓库和软件工程理念对模型进行分层分类管理,让每层实现各自目标,提升项目的可维护性。另外还包括命名规范、文档描述以及分析目录的巧妙应用等。
模型文件分布
dbt项目中模型文件夹是开发者使用最频繁的地方,如:查找表和视图,构建、测试或运行,生成文档及血缘关系。同时模型文件夹的变化带来影响面也最大。
通常我们在模型文件夹下建四个子文件夹,与数据仓库的分层模型对应:
- source: 映射源表资源,实现源对象;
- staging: 清洗、转换以及过滤源表数据,实现暂存对象;
- refinement: 计算及业务逻辑层,实现业务对象;
- mart: 暴露给业务使用场景,实现集市对象
每个dbt项目业务会有差异,我们建议对4层模型文件夹采用两种策略:
- source/staging: 每个文件夹表示一个数据生成的平台或工具
- refinement/mart: 每个文件夹表示一个商业领域
项目中的sql和yaml文件处理方式不必相同。对于SQL文件遵循逻辑如下;
- 源对象和暂存对象之间的一对一关系,这意味着此时没有连接,除了一些罕见的特殊例外;
- 暂存对象和业务对象之间(或业务对象本身之间)的一对多关系。一个暂存文件夹显然可以被不同业务模型使用,并且一个业务模型可以被其他业务模型使用。
- 业务模型和集市模型之间可以为一对一的关系。它仅为外部用例构建,可以仅看作“最终”的业务模型(或者有时基于多个union模型,不是join方式)
对于yml文件,它的内容包含测试、文档和其他属性。主要解决的是数量和层次的问题。在mart模型中(因为没有join)很可能不执行任何测试,但是仅描述对象的内容和范围的简单描述就足够了。因此,为什么不将每个子文件夹中多的集市模型文档聚合到单个.yml文件中呢?
最总,项目结构类似下图:
my_first_dbt_project
├── analyses
├── docs
├── seeds
├── dbt_project.yml
├── macros
├── models
│ ├── mart
│ │ └── customer_analyse
│ │ ├── mart_custom_analyse.yml
│ │ └── mart_customer_scores.sql
│ │ └── mart_customer_feedback.sql
│ ├── refinement
│ │ ├── customer_analyse
│ │ │ ├── ref_customer_scores.sql
│ │ │ ├── ref_customer_scores.yml
│ │ │ ├── ref_customer_feedback.sql
│ │ │ └── ref_customer_feedback.yml
│ │ └── orders
│ │ ├── ref_monthly_orders.yml
│ │ └── ref_monthly_orders.sql
│ ├── staging
│ ├── platform_one
│ │ ├── stg_accounts.yml
│ │ ├── stg_accounts.sql
│ └── platform_two
│ ├── stg_addresses.yml
│ ├── stg_addresses.sql
├── packages.yml
├── snapshots
└── tests
对象命名规范
虽然为表中的列选择实际名称可能是一种主观选择,但在dbt项目中使用命名约定客观上为数据一致性和可访问性带来了巨大的好处。如果在此之前你没有遵循任何命名约定,那么刚开始可能会有点乏味,因为可能需要更改许多数据点以满足约定,并且在这样做时,下游模型或外部依赖关系可能会中断也要同步修改。但是请记住,dbt在这种情况下会派上用场,只需检查项目模型的血缘关系并避免大量的重复修改。
下面从一个查询实例深入探讨:
SELECT ord.order_id, -- primary key and foreign keysord.customer_id, ord.product_name, -- dimensionspro.product_category,cus.customer_country,cus.is_active_customer, -- booleansord.is_multiproduct_order,ord.delivering_time, -- time measures ord.count_products, -- aggregated fieldscus.customer_sign_up_date, -- dates ord.dispatched_at, -- timestamps ord.delivered_atFROM {{ ref('stg_orders') }} ord -- table aliases
LEFT JOIN {{ ref('stg_customers') }} cus ON ord.customer_id = cus.customer_id
LEFT JOIN {{ ref('stg_products') }} proON ord.product_name = pro.product_name
- primary key/foreign keys: 采用领域加上后缀ID组合;
- dimensions: 使用清晰名称,尽量避免简写,实际应用中使用前缀表明维度领域对象名称,如产品或客户等;
- booleans: 对于值为TRUE/FALSE的字段命名采用 _is 或 _has 作为前缀;
- time measures: 时间维度:使用后缀_time并在时间格式上保持一致(总是时分秒)
- aggregated fields: 避免缩写,并添加聚合中使用的函数名称(count, sum等)作为前缀
- timestamps/dates: timestamp 字段 使用 _at 后缀, date字段使用 _date 后缀;
- table aliases: 使用至少包含3个字符的有意义的别名来引用对象的内容,这样就可以跨不同的模型使用它
遵循这些超级简单的命名规则(结合表和列的文档描述),将大大加快构建下游模型的过程,因为理解数据涵义的时间将大幅减少,同时避免了不必要的数据类型转换。对于业务用户来说: 查看仪表板中的词汇将帮助他们理解正在浏览的内容,也让分析师清晰了解基本数据意义,不必返回咨询重复问题。
集中列定义
在处理数据过程中,模型文档化绝对不是最吸引人的任务。然而,由于缺乏文档或记录,分析人员将难以理解如何使用表或表中的某些字段意义。即使有文档,对于不同模型的同一列也可能有不同的解释,或者对于相同数据点的描述也可能略有不同。
dbt中有一个非常好的特性,通过使用doc函数,它可以帮助在模型之间对齐列描述并避免重复。在确定模型中经常重复出现的字段之后,你可以在dbt项目的docs文件夹中创建一个或多个yaml文件(也许每个主题/域一个文件)。在这些文件中,列出字段及其描述,然后在需要地方引用,实现相同字段共用描述内容。
注意:如果在项目中没有看到docs文件夹,只需创建它并在dbt_project.yml中指定路径。
下面是几个查询中作为主键的几个字段的最基本示例:
## customer_id{% docs customers__customer_id %}
The unique identifier assigned to each customer in numeric format.
{% enddocs %}## customer_code{% docs customers__customer_code %}
The unique identifier assigned to each customer in alphanumeric format.
{% enddocs %}
此时,可以在任何模型的.yml文件中简单地引用该文件。像这样:
columns:- name: customer_iddescription: '{{ doc("customers__customer_id") }}'
这可避免常用字段重复描述文档,对于较大组织跨业务沟通非常方便。此外,也能帮助项目新人快速了解业务、融入团队。
analyses目录
dbt项目中另一个经常被忽略的、但非常有用特性是analyses文件夹。因为dbt运行任务时,不会构建存储在该文件夹中的任何内容,因此该目录是较好的掩护所。我们可以这几种场景中使用该目录:
- 有些模型无需再次运行但值得存档,可以放在此目录
- 存放一些临时分析SQL查询或脚本文件
- 对于要集成到DBT项目,目前处于过度阶段的查询或模型
注意: 虽然dbt不会运行和物化该目录的内容,但仍会编译。这意味着如果存在问题会抛出错误(例如,ref/source宏函数),这有助我们及时发现、调试问题。要避免这些问题,有两种思路:
- 存储编译后的实现模型,所以不用担心查询中的ref 或 source 函数。
- 增加特定配置,禁用模型被编译:
{{ config(enabled = false) }}
总结
本文介绍了开发dbt项目的基本策略和一些有价值的特性,通过实践本指南中策略和方法,可以提升项目实施效率和质量。期待您的真诚反馈,更多内容请阅读数据分析工程专栏。