主题: 第 14 部分。Silver 层:客户、账户、卡片、支付
难度等级: 中级
预计学习时间: 2.5–3 小时(理论 — 1 小时,实践 — 1 小时,案例与复习 — 1 小时)
前置要求: 了解 Data Lakehouse 中 Medallion 架构(Bronze/Silver/Gold)
具备 dbt 基础技能:模型、ref()、sources、测试
掌握 JOIN、GROUP BY、窗口函数级别的 SQL 知识
对银行数据领域有所了解:客户、账户、卡片、支付
熟悉 grain(粒度)、PII、lineage(血缘)等概念
学习目标: 解释 Silver 层作为 staging 与 mart 之间可复用中间层的作用
为 int_customer_balances 和 int_customer_card_activity 等 intermediate 模型识别并阐明 grain(粒度)
应用 Silver 层规则:禁止 PII 泄露、清晰的 grain、不含产品决策、使用 ref() 构建血缘
按形式化的质量标准对 silver 模型进行验收检查
为 intermediate 模型填写 grain / 输入 / 使用情况 / 验证缺口对应表
概述: 在银行主题领域的 dbt 架构中,Silver 层是位于原始 staging 模型与最终 mart 报表之间、由稳定可复用实体构成的层次。它消除了源数据中的技术脏污(类型转换、键归一化、最简单的聚合),但不会做出影响业务承诺的产品决策。在教学示例中,Silver 由两个 intermediate 模型表示:int_customer_balances(将账户聚合到客户层级)和 int_customer_card_activity(聚合卡片交易及风险信号)。Silver 的核心价值在于可复用性:同一模型可被 Customer 360 报表、信贷报表和风险报表读取而无需重复实现逻辑。然而这种可复用性要求纪律:清晰的 grain、计数器窗口的文档化、PII 处理策略,以及强制使用 ref() 构建血缘。
关键概念: Silver 层(中间层):Medallion 架构中的一层,从原始数据中创建稳定可复用的实体。连接 staging(源数据清洗与类型化)和 mart(面向消费者的业务报表)。它既不是最终产品,也不再是原始数据源。
中等 模型(int_):dbt 中位于 staging 和 mart 之间的一类模型,前缀为 int_。包含中间转换与聚合。在本部分中:int_customer_balances 和 int_customer_card_activity。
Grain(粒度):表的关键属性——每一行对应哪个实体(customer_id、account_id、transaction_id)。它定义表中什么算作一条记录。grain 不清晰的模型将无法被复用。
Int customer balances:intermediate 模型,将 stg_accounts 中的数据聚合到客户层级。Grain:每个 customer_id 一行。被 mart_customer_360 使用。包含客户所有账户的聚合余额。
Int customer card activity:intermediate 模型,将 stg_card_transactions 中的卡片交易聚合,并在客户层级上计算信号,包括风险计数器(如 risk_event_count_7d)。Grain:每个 customer_id 一行。
Pii(personally identifiable information,个人身份识别信息):客户的个人数据(姓名、护照、电话、email)。Silver 层不应在缺少处理策略的情况下让直接的 PII 继续向下游流转。
Ref():dbt 中用于引用项目中其他模型的函数。它创建显式的血缘(lineage),使评审者能够在 DAG 中看到它。Silver 中所有输入模型只能通过 ref() 读取。
Lineage(数据血缘):由 dbt 基于 ref() 自动构建的模型间依赖关系图。它帮助理解哪些模型依赖于给定模型,以及该模型使用了哪些数据源。
具有明确窗口的风险计数器:聚合(例如 risk_event_count_7d),其中时间窗口(7 天)和风险标志的定义必须在模型规范中明确描述。否则模型将不可验证。
Silver 层的可复用性:同一 intermediate 模型可被多个 mart 报表使用而无需复制逻辑的特性。降低了报表之间出现分歧的风险。
验证缺口(validation gap):模型缺乏足够规范以进行数据质量验证的情况:未描述窗口、未解释 null 值、未定义 grain。空的验证缺口 = 模型已充分描述。
产品决策:定义何为「活跃客户」「逾期还款」「具备信贷资格」等的业务规则。如果 Silver 层是可复用的,它就不应做出此类决策——这是 mart 层的工作。
Qwen 评审查询:用于 LLM 助手的专用提示词,用于将 intermediate 模型与规范及 mart 的需求进行对比,识别 grain、输入和验证缺口,但不会修改文件。
重要日期: 阶段 1 — Silver 层设计:在创建 mart 报表之前划分中间实体的架构决策。它不绑定具体日历日期,但是项目中的强制里程碑。
阶段 2 — 规范对齐:每个 int_ 模型获得文档化的 grain、计数器窗口、PII 策略的时刻。在此之前模型不被视为已就绪可合并。
阶段 3 — 验收检查:按检查清单进行的正式评估:输入使用 ref()、客户模型每个 customer_id 一行、无未解释的 null、风险计数器窗口清晰。
练习题: 名称: 为两个 intermediate 模型构建对应表
问题: 利用第 14 部分的材料,填写 grain / 输入 / 使用于 / 验证缺口 表,针对 int_customer_balances 和 int_customer_card_activity 两个模型。对每个模型指出:(1)粒度——一行对应哪个实体;(2)输入——它通过 ref() 读取了哪些 staging 模型;(3)使用位置——哪些 mart 模型读取它;(4)验证缺口——规范中未描述的内容(如果全部已描述,则留空)。
解决方案: 步骤 1. 确定 int_customer_balances 的 grain:模型将账户聚合到客户层级,因此一行 = 一个 customer_id。步骤 2. 按文本的输入:stg_accounts。步骤 3. 使用位置:mart_customer_360。步骤 4. 验证缺口:材料中 grain 和输入明确给出,未使用窗口(余额聚合),PII 未作为问题区域——缺口为空。步骤 5. 确定 int_customer_card_activity 的 grain:模型将卡片交易聚合到客户层级,一行 = 一个 customer_id。步骤 6. 输入:stg_card_transactions。步骤 7. 使用位置:mart_customer_360。步骤 8. 验证缺口:模型计算 risk_event_count_7d,但规范必须说明「7 天」是什么(窗口从哪个日期起算,滚动还是固定)以及 risk_flag 来自何处(阈值、来源)。若未描述则存在缺口。最终表格:见材料中的「最小输出」一节。
难度: intermediate
名称: 在 Silver 模型中识别产品决策
问题: 您获得了 int_customer_card_activity 的 SQL 代码,其中包含一行:WHERE balance > 0 AND last_transaction_date > CURRENT_DATE - INTERVAL '90 days'。同事声称这只是「活跃度」过滤器。请判断这是否违反 Silver 层规则,以及可能带来什么后果。
解决方案: 步骤 1. 分析逻辑:balance > 0 是关于客户「拥有资金」的业务规则。last_transaction_date > CURRENT_DATE - INTERVAL '90 days' 是对客户「活跃度」的业务定义。步骤 2. 与 Silver 规则对照:只要模型是可复用的,Silver 不应做出产品决策。步骤 3. 得出结论:该条件将 Silver 模型变成了产品决策(定义「活跃客户」),使其无法被其他可能采用不同活跃度定义的 mart 报表使用(例如信贷评分可能使用 180 天)。步骤 4. 建议:从 int_ 模型中移除过滤器。将聚合(如 days_since_last_transaction、has_positive_balance)作为字段输出,关于「活跃度」的决策放在 mart 层。
难度: intermediate
名称: 通过 ref() 审计血缘
问题: 项目中找到一个模型 int_customer_card_activity_v2,它通过 source() 而不是 ref('stg_card_transactions') 直接读取 stg_card_transactions。这会如何影响评审并带来哪些风险?
解决方案: 步骤 1. 检查 Silver 的要求:「输入模型只能通过 ref() 读取」。步骤 2. 使用 source() 而非 ref() 会发生什么:dbt 不会在项目 DAG 中将 int_customer_card_activity_v2 与 stg_card_transactions 之间的依赖关系作为 model-to-model 包含进来。步骤 3. 对评审者的影响:评审者无法在血缘中看到 int_ 模型依赖于 stg_;无法追踪源数据变更时会影响到哪些 mart 报表;对 stg_card_transactions 的测试不会自动传播到依赖模型。步骤 4. 建议:改为 ref('stg_card_transactions'),并验证模型已出现在 DAG 中(命令 dbt ls --select int_customer_card_activity_v2 --output name)。
难度: beginner
名称: 风险计数器的文档化
问题: 模型 int_customer_card_activity 包含字段 risk_event_count_7d,值为 0 或正整数。请为该字段编写规范,使其通过 Silver 层的验收检查。
解决方案: 规范应包含:(1)风险事件的定义——例如「被 stg_card_transactions 中 is_risk 标志标记的交易」或「反欺诈模型评分 > 0.8 的交易」。(2)窗口的定义——「截至模型执行日期最近 7 个日历日(execution_date - 7 days)」或「交易日期前 7 天」。(3)窗口类型——固定还是滚动(rolling)。(4)风险标志的来源——stg_card_transactions.is_risk 或单独的 risk_scores 表。(5)null 处理——若 risk_score 为空则不计入事件(或计入,需明确说明)。(6)最终值的 grain——在窗口内每个 customer_id 的事件数。缺少这样的规范,模型看起来像 SQL,而不是稳健的数据资产。
难度: intermediate
名称: 重构宽表 int_ 模型
问题: 仓库中存在模型 int_customer_universe,其中包含:客户余额、卡片交易聚合、产品标志、AML 审查状态和客户同意信息。请解释为何这违反了 Silver 层规则,并提出重构计划。
解决方案: 步骤 1. 检查规则:Silver 不应混合不相关的领域,应具有清晰的 grain 和单一用途。步骤 2. 分析模型:内部混合了至少三个领域——客户财务(余额)、行为信号(卡片交易)、合规(AML、同意)。每个领域具有不同的更新动态、不同的负责人和不同的 mart 消费者。步骤 3. 虽然形式上存在一个 grain(customer_id),但缺少单一用途:模型试图同时作为所有 mart 报表的通用总线。步骤 4. 重构计划:拆分为三个 int_ 模型——int_customer_balances、int_customer_card_activity、int_customer_compliance(包含 AML 和同意信息)。如果 Customer 360 mart 报表需要全部三者,它自行通过 ref() 进行 JOIN。步骤 5. 用材料中的标准验证:「该层是否帮助表达可重复的实体并证明其质量?」拆分后——是;拆分前——否。
难度: advanced
案例研究: 名称: 案例:风险与 CRM 报表之间「活跃客户」定义的分歧
场景: 一家排名前 20 的银行(假设性示例)基于 dbt 构建了 Data Lakehouse。Silver 层包含模型 int_customer_activity,将交易和账户聚合到 customer_id 层级。两个 mart 报表依赖于它:mart_risk_scoring(用于信贷评分)和 mart_crm_active_customers(用于营销活动)。在 int_customer_activity 中内置了过滤器:customer_id IN (SELECT customer_id FROM stg_accounts WHERE balance > 0)。
挑战: 半年后,风险评分产品团队请求使用另一种活跃度定义(考虑信贷额度、不要求正余额)进行重算。与此同时 CRM 团队要求收紧过滤器(只保留 30 天内有过交易的客户)。显而易见 Silver 模型不可复用——每个 mart 报表被迫复制自己的逻辑。结果出现了分歧:mart_risk_scoring_v1 显示 120 万「活跃客户」,mart_crm_active_customers_v1 显示 80 万,而新的 mart_credit_eligibility_v2 显示 95 万。分析师们争论哪个数字正确,审计人员记录了违反单一可信来源原则的问题。
解决方案: 数据团队按第 14 部分所述原则对 Silver 层进行了重构。步骤 1:删除 int_customer_activity。步骤 2:创建两个独立的 int_ 模型——int_customer_balances(余额聚合,不含「活跃度」过滤器)和 int_customer_card_activity(具有文档化窗口 risk_event_count_7d 的交易聚合)。步骤 3:为 mart_risk_scoring 添加 has_positive_balance 作为属性,活跃度过滤器移至 mart 内部。步骤 4:为 mart_crm_active_customers 同样将 days_since_last_transaction 移为属性,「30 天」的业务过滤器置于 mart。步骤 5:为 mart_credit_eligibility 在其 mart 中使用自己的定义。步骤 6:按 Silver 检查清单进行验收——输入使用 ref()、每个 customer_id 一行、无未解释的 null、计数器窗口已文档化。
结果: 重构两个月后:(1)三个 mart 报表读取相同的 Silver 模型,「活跃度」定义的差异变得显式并可讨论;(2)变更活跃度逻辑所需的时间从 3 周(需修改三份副本)缩短为 3 天(只需修改 mart);(3)审计在单一可信来源原则方面无异议通过;(4)dbt 中的血缘变得透明——int_customer_card_activity 的任何变更对所有依赖的 mart 都可见。
经验教训: Silver 不应包含产品过滤器——否则它将丧失可复用性
mart 报表之间的分歧是 Silver 中嵌入了业务规则的征兆
「一个模型 — 一个用途 — 一个 grain」的原则比架构美学更重要
窗口和标志来源的文档化是审计通过的必要条件
相关概念: Grain
Silver 层的可复用性
Silver 中的产品决策
通过 ref() 构建血缘
Silver 与 mart 之间的责任划分
名称: 案例:通过可复用的 Silver 模型泄露 PII
场景: 一家金融科技公司(基于行业真实事件的示例)构建了 int_customer_profile 用于聚合客户数据。该模型读取 stg_customers,其中包含姓名、email、电话、护照数据、出生日期,并将所有这些聚合为每个 customer_id 一行。模型使用方便——它被接入五个 mart 报表,包括 mart_marketing_segments(用于外部推送)。
挑战: 信息安全部门在计划审计中发现 mart_marketing_segments 被导出到营销平台时携带了完整的客户姓名和护照信息,尽管分群所需的仅是 email 哈希和年龄分组。形式上没有违规——数据在客户引导时「已同意」。但 PII 处理政策要求最小化:每个 mart 报表应只获得其用途所需的 PII。将护照信息传给营销部门被认定为冗余。
解决方案: 数据团队采用了第 14 部分的规则:「不要让直接的 PII 流转得比所需更远」。步骤 1:int_customer_profile 被拆分为 int_customer_identity(姓名、护照——仅供合规 mart 使用)和 int_customer_attributes(年龄分组、客群、email 哈希——供其他所有人使用)。步骤 2:在模型中明确写入策略:「*_pii 字段仅对带有 compliance_use_case 标签的模型可用」。步骤 3:mart_marketing_segments 现在读取 int_customer_attributes,其中只有 email 哈希。步骤 4:进行验收——「PII 不会在没有策略的情况下泄漏到可复用层」。
结果: 信息安全审计无异议关闭。营销平台获得最小必要的数据集。合规 mart 通过单独受保护的分支保留对完整 PII 的访问。新 mart 消费者的上线时间缩短——不再需要在 int_customer_profile 中手动过滤字段。
经验教训: Silver 中的 PII 不是技术问题,而是策略问题:每个 mart 都必须具备正当理由
包含 PII 的宽模型是可复用 Silver 层的反模式
在 mart 数量扩展之前就应该分离 PII 和属性
dbt 的 tags 有助于自动化 PII 策略的管控
相关概念: PII
个人数据处理策略
Silver 层的可复用性
Grain
dbt 中的 tags
学习建议: 随时备好 Medallion 架构图:在每个示例上绘制 Bronze → Silver → Gold 的箭头,避免混淆各层
记住 Silver 模型的三要素:grain + 单一用途 + 文档化的窗口。缺少任何一个,模型都未就绪
阅读他人的 int_ dbt 文件时始终检查三件事:(1)输入使用 ref(),(2)grain 键的唯一性测试,(3)properties.yml 中有描述
不要用模型数量衡量 Silver 的质量。一条精心编写的 int_ 模型胜过三层漂亮命名的模型——这是第 14 部分的核心思想
将材料中的 Qwen 查询作为模板:「将 intermediate 模型与规范和 mart 的需求进行比较。展示 grain、ref() 输入、可复用字段和验证缺口」
在准备评审时,学会在 30 秒内解释 int_ 模型的每个字段:它是什么、来自何处、为何 mart 消费者需要它
为每个新的 int_ 模型在开发之前就完成 grain / 输入 / 使用位置 / 验证缺口 的表格——这能约束开发
避免「略微优化」Silver 的诱惑——添加过滤器、重命名风险、计算方便的指标。每一次这样的优化都应记录在规范中
附加资源: Dbt documentation — intermediate models: https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview(staging → intermediate → marts 一节)
Dbt documentation — ref() function: https://docs.getdbt.com/reference/dbt-jinja-functions/ref
Dbt documentation — graph operators 和 lineage: https://docs.getdbt.com/reference/dbt-jinja-functions/graph-operators
Dbt 中的 data quality — 测试与 properties.yml: https://docs.getdbt.com/best-practices/warnings
The medallion architecture (databricks): https://www.databricks.com/glossary/medallion-architecture
Dbt tags 用于访问策略: https://docs.getdbt.com/reference/resource-configs/tags
Qwen (tongyi qianwen) — 用于 dbt 代码评审的 llm: https://qwen.alibaba.com/
书籍《data mesh》(zhamak dehghani):data-as-a-product 概念与「一个 int_ 模型 — 一个用途」的原则相呼应
摘要: 银行数据(客户、账户、卡片、支付)架构中的 Silver 层是由稳定可复用实体构成的一层,它消除了源数据的技术脏污,但不会做出产品决策。在教学示例中,它由两个 intermediate 模型表示:int_customer_balances(将账户聚合到客户层级)和 int_customer_card_activity(聚合卡片交易及风险计数器)。Silver 的主要规则:清晰的 grain(客户模型中每个 customer_id 一行)、使用 ref() 构建血缘、禁止无策略的 PII 泄露、计数器窗口需文档化(例如 risk_event_count_7d 中的「7 天」指什么)、不混合不相关的领域。Silver 的质量标准很简单:该层是否帮助表达可重复的实体并证明其质量?若否,则模型是为架构对称而创建,而非为读者或数据消费者创建。用于自查的工具:grain / 输入 / 使用位置 / 验证缺口 表以及控制性问题:为什么 Silver 不应绑定到单一产品?哪些 PII 不能在没有策略的情况下传递?ref() 如何帮助评审者?