快照
简介¶
什么是快照?¶
分析师通常需要“及时回顾”其可变表中以前的数据状态。虽然一些源数据系统的构建方式使访问历史数据成为可能,但情况并非总是如此。dbt提供了一种机制,快照,它记录随着时间的推移对可变表的更改。
快照执行type-2 缓慢变化的维度在可变表上。这些缓慢变化的维度标识了表中的记录如何随时间变化。假设您有一个orders(订单)表,在处理订单时可以覆盖status(状态)字段。
| id | status | updated_at |
|---|---|---|
| 1 | pending | 2019-01-01 |
现在,假设订单从“pending(待定)”变为“shipped(已发货)”。同样的记录现在看起来是:
| id | status | updated_at |
|---|---|---|
| 1 | shipped | 2019-01-02 |
该订单现在处于“已发货”状态,但我们已经丢失了该订单最后一次处于“待定”状态的信息。这使得很难(或不可能)分析订单发货所需的时间。dbt可以“快照”这些更改,以帮助您了解一行中的值是如何随时间变化的。以下是上一个示例的快照表示例:
| id | status | updated_at | dbt_valid_from | dbt_valid_to |
|---|---|---|---|---|
| 1 | pending | 2019-01-01 | 2019-01-01 | 2019-01-02 |
| 1 | shipped | 2019-01-02 | 2019-01-02 | null |
在dbt中,快照是select语句,在.sql文件中的快照块中定义(通常在“snapshots”目录中)。您还需要配置快照来告诉dbt如何检测记录更改。
{% snapshot orders_snapshot %}
{{
config(
target_database='analytics',
target_schema='snapshots',
unique_key='id',
strategy='timestamp',
updated_at='updated_at',
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
在IDE中预览或编译快照
在dbt Cloud中无法为快照“预览数据”或“编译sql”。但可以通过完成以下步骤,在IDE中运行`dbt snapshot`命令。
当您运行dbt snapshot命令时:
- 第一次运行: dbt将创建初始快照表-这将是
select语句的结果集,其他列包括dbt_valid_from和dbt_valid_to。所有记录都将有一个dbt_valid_to=null。 - 在随后的运行中: dbt将检查哪些记录已更改或是否创建了任何新记录:
- 对于任何已更改的现有记录,都将更新
dbt_valid_to列 - 更新后的记录和任何新记录都将插入到快照表中。这些记录现在将具有
dbt_valid_to=null
- 对于任何已更改的现有记录,都将更新
快照可以在下游模型中引用,方法与引用模型相同——使用ref函数。
示例¶
在你的项目中添加一个快照:
1.在snapshots目录中创建一个.sql文件,比如snapshots/orders.sql
2.使用snapshot块定义快照的开始和结束:
3.在快照块中编写select语句(下面是编写好的快照查询的技巧)。此select语句定义了要随时间进行快照的结果。你可以在这里使用sources来源和refs参考。
{% snapshot orders_snapshot %}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
4.检查查询的结果集是否包含可靠的时间戳列,该列指示记录上次更新的时间。对于我们的示例,updated_at列可靠地指示记录更改,因此我们可以使用timestamp策略。如果您的查询结果集没有可靠的时间戳,则需要使用检查策略,详细信息如下。
5.使用配置块将配置添加到快照中(详细信息如下)。您也可以从dbt_project.yml文件配置快照。
{% snapshot orders_snapshot %}
{{
config(
target_database='analytics',
target_schema='snapshots',
unique_key='id',
strategy='timestamp',
updated_at='updated_at',
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
6.运行dbt snapshot命令,正如我们举例的那样,这将在analycy.snapshot.orders_snapshot中创建一个新表。您可以更改target_database配置、target_schema配置和快照名称(如{% snapshot .. %}中所定义),以更改dbt命名此表的方式。
$ dbt snapshot
Running with dbt=0.16.0
15:07:36 | Concurrency: 8 threads (target='dev')
15:07:36 |
15:07:36 | 1 of 1 START snapshot snapshots.orders_snapshot...... [RUN]
15:07:36 | 1 of 1 OK snapshot snapshots.orders_snapshot..........[SELECT 3 in 1.82s]
15:07:36 |
15:07:36 | Finished running 1 snapshots in 0.68s.
Completed successfully
Done. PASS=2 ERROR=0 SKIP=0 TOTAL=1
7.通过从创建的dbt表中进行选择来检查结果。第一次运行后,您应该会看到查询的结果,以及如下所述的快照元字段,如下所述。
8.再次运行snapshot命令,并检查结果。如果有任何记录发生更新,则快照会反映出来。
9.使用ref函数从下游模型中的snapshot中进行选择。
10.安排snapshot命令定期运行, 只有频繁运行快照,快照才有用。
检测记录变化¶
快照“策略”定义了dbt如何知道一行记录是否发生了更改。dbt内置了两种策略——timestamp和check。
Timestamp策略 (推荐)¶
timestamp策略使用updated_at字段来确定一行是否已更改。如果为一行配置的updated_at时间戳列比上次运行快照时有更新,那么dbt将使旧记录无效并记录新记录。如果时间戳没有改变,那么dbt将不会采取任何操作。
timestamp策略需包含以下配置:
| Config | Description | Example |
|---|---|---|
| updated_at | A column which represents when the source row was last updated | updated_at |
示例使用方法:
{% snapshot orders_snapshot_timestamp %}
{{
config(
target_schema='snapshots',
strategy='timestamp',
unique_key='id',
updated_at='updated_at',
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
Check策略¶
check策略对于没有真实的updated_at列的表很有用。此策略通过比较列的当前值和历史值来工作。如果这些列中的任何一列发生了更改,那么dbt将使旧记录无效并记录新记录。如果列值相同,那么dbt将不会采取任何操作。
check策略需包含以下配置:
| Config | Description | Example |
|---|---|---|
| check_cols | A list of columns to check for changes, or all to check all columns |
["name", "email"] |
check_cols = 'all'
check快照策略可以配置为通过提供check_cols='all'来跟踪 所有 列的更改。最好明确列出要检查的列。考虑使用"surrogate-key"将多个列压缩为一列。
示例
{% snapshot orders_snapshot_check %}
{{
config(
target_schema='snapshots',
strategy='check',
unique_key='id',
check_cols=['status', 'is_cancelled'],
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
硬删除(附加选择)¶
默认情况下,从SQL执行中删除的记录是有效的。使用配置选项invalidate_hard_deletes,dbt可以跟踪不再存在的记录。这是通过将快照表与源表里德left join,并筛选在该点仍然有效但在源表中找不到的行来完成的。dbt_valid_to将被设置为当前快照时间。
此配置与上述策略不同,而是一个附加的选择加入功能。默认情况下不会启用它,因为它会更改以前的行为。
要使此配置与timestamp策略配合使用,配置的updated_at列必须是时间戳类型。否则,查询将因数据类型混合而失败。
示例
{% snapshot orders_snapshot_hard_delete %}
{{
config(
target_schema='snapshots',
strategy='timestamp',
unique_key='id',
updated_at='updated_at',
invalidate_hard_deletes=True,
)
}}
select * from {{ source('jaffle_shop', 'orders') }}
{% endsnapshot %}
配置快照¶
快照配置¶
有许多特定于快照的配置:
| Config | Description | Required? | Example |
|---|---|---|---|
| target_database | dbt会将快照表输出的数据库 | No | analytics |
| target_schema | dbt会将快照表输出的数据库schema | Yes | snapshots |
| strategy | 快照策略,timestamp 或 check |
Yes | timestamp |
| unique_key | "primary-key"或表达式 | Yes | id |
| check_cols | 如果使用check策略,就检查相应的字段 |
仅使用于check策略 |
["status"] |
| updated_at | 如果使用timestamp策略, 对比timestamp字段 |
仅使用于timestamp策略 |
updated_at |
| invalidate_hard_deletes | 查找源数据中硬删除的记录, 如果不存在则将dbt_valid_to 更新为当前时间 |
No | True |
还支持许多其他配置(例如tags和post-hook),请查看完整列表。
快照可以从dbt_project.yml文件和config块中进行配置,有关详细信息,请查看配置文档 。
注意:BigQuery用户可以使用target_project和target_dataset分别作为target_dabase和target _schema的别名。
配置最佳实践¶
尽可能使用timestamp策略¶
此策略比check_cols策略能更好地处理列的添加和删除。
确保你的unique key是真的unique¶
dbt使用unique key来匹配行,因此确保该键实际上是唯一的非常重要!如果您正在对源数据进行快照,我建议您在源数据中添加一个唯一性测试(示例).
使用一个不同于你分析schema的target_schema¶
快照不能重新创建。因此,最好将快照放在一个单独的schema中,这样最终用户就知道它们是有特别用处的。您可能希望在快照上设置与模型不同的权限,甚至以不同的用户(或角色,取决于您的仓库)运行快照,以使删除快照变得非常困难。
快照查询最佳实践¶
快照源数据.¶
您的模型应该从这些快照中进行查询,将它们视为常规数据源。尽可能以原始形式快照源数据,并使用下游模型清理数据
在查询里使用source函数.¶
这有助于理解项目中的数据血缘。
包括尽可能多的字段.¶
事实上,如果性能允许,请使用select *,即使一个字段目前感觉不太有用,最好对它进行快照,以防它变得有用——毕竟,以后您将无法重新创建该字段。
避免在快照中使用join.¶
Join会使构建可靠的updated_at时间戳变得困难。相反,可以分别对这两个表进行快照,并将它们连接到下游模型中。
限制查询中的转换量.¶
如果您在快照查询中应用业务逻辑,并且该逻辑在未来发生变化,那么将逻辑中的变化应用于快照可能是不提倡的(或者至少非常困难)。
基本上,让您的查询尽可能简单!这些建议的一些合理例外情况包括:
- 如果表很宽,则选择特定字段.
- 进行轻度转换以使数据形成合理的形式,例如,将json包拆成字段.
快照元字段¶
快照表将作为源数据的克隆表创建,外加一些额外的元字段
| Field | Meaning | Usage |
|---|---|---|
| dbt_valid_from | 这一条记录被快照的创建时间 | 这个字段用来对某条记录的不同"版本"进行排序. |
| dbt_valid_to | 这一条记录失效的时间. | 最新的快照记录会设置成null. |
| dbt_scd_id | 为每个快照记录生成的唯一ID | 这是dbt内部使用的 |
| dbt_updated_at | 插入此条快照记录的时间戳. | 这是dbt内部使用的 |
- 根据您使用的策略,每列使用的时间戳略有不同:
对于
timestamp策略,配置的updated_at列用于填充dbt_valid_from、dbt_valid_to和dbt_updated_at字段。
在2019-01-01 11:00查询结果
| id | status | updated_at |
|---|---|---|
| 1 | pending | 2019-01-01 10:47 |
快照结果 (注意11:00还没有地方看到):
| id | status | updated_at | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|---|---|---|---|---|
| 1 | pending | 2019-01-01 10:47 | 2019-01-01 10:47 | 2019-01-01 10:47 |
在2019-01-01 11:30查询结果:
| id | status | updated_at |
|---|---|---|
| 1 | shipped | 2019-01-01 11:05 |
快照结果 (注意11:30 还没有地方看到):
| id | status | updated_at | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|---|---|---|---|---|
| 1 | pending | 2019-01-01 10:47 | 2019-01-01 10:47 | 2019-01-01 11:05 | 2019-01-01 11:05 |
| 1 | shipped | 2019-01-01 11:05 | 2019-01-01 11:05 | 2019-01-01 11:05 |
针对check策略,当前时间戳用于填充每一列。如果配置了,check策略则会像timestamp策略使用updated_at列。
在2019-01-01 11:00快照查询结果
| id | status |
|---|---|
| 1 | pending |
快照结果:
| id | status | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|---|---|---|---|
| 1 | pending | 2019-01-01 11:00 | 2019-01-01 11:00 |
2019-01-01 11:30查询结果:
| id | status |
|---|---|
| 1 | shipped |
快照结果:
| id | status | dbt_valid_from | dbt_valid_to | dbt_updated_at |
|---|---|---|---|---|
| 1 | pending | 2019-01-01 11:00 | 2019-01-01 11:30 | 2019-01-01 11:30 |
| 1 | shipped | 2019-01-01 11:30 | 2019-01-01 11:30 |