4. dbt内置通用测试详解

dbt 的通用测试(generic tests)是数据质量工程中最常用的一层防线。它们以 YAML 声明的方式挂在模型或列上,由 dbt 在 dbt test 时自动编译成 SQL 查询并执行。理解这四类内置测试的语法、配置、执行机制与适用场景,是构建可靠数据管道的基础。本节将逐一拆解 not_null、unique、accepted_values、relationships,并说明它们在实际项目中的组合方式。

通用测试的声明位置与执行机制

通用测试写在模型的 YAML 文件(通常是 schema.yml 或 _models.yml)中,通过 tests 键挂载。dbt 会为每个测试生成一条独立的 SQL 查询,查询返回的每一行都代表一次失败。若查询返回 0 行,测试通过;返回任意行,测试失败并列出违规记录。这种“返回失败行”的机制是理解所有通用测试的钥匙——测试的本质是“找出不满足约束的数据”。

执行时,dbt 会并行运行这些测试查询,并在日志中输出 PASS/FAIL 状态。失败时可通过 --store-failures 将失败行写入数据库表,便于排查。下面这张图展示了四类测试在数据流中的位置与各自的检查逻辑。

dbt 四类内置通用测试:检查逻辑与数据流 源模型 / 上游表 stg_orders not_null 检查列值是否为 NULL WHERE col IS NULL unique 检查列值是否唯一 GROUP BY col HAVING COUNT(*) > 1 accepted_values 检查值是否在允许集合内 WHERE col NOT IN (...) relationships 检查外键是否存在于目标表 LEFT JOIN ... WHERE target IS NULL 测试结果判定 返回 0 行 → PASS 数据满足约束,测试通过 返回 >0 行 → FAIL 列出违规行,可存储失败记录 失败行示例(not_null) order_id | customer_id 1001 | NULL 失败行示例(relationships) order_id | customer_id 1002 | 9999(目标表不存在)

not_null:最基础的完整性约束

not_null 用于检查指定列是否存在 NULL 值。语法非常直接:

models:
  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - not_null

执行时 dbt 生成类似 SELECT * FROM stg_orders WHERE order_id IS NULL 的查询。若返回任何行,说明存在空值,测试失败。适用场景包括:主键列、外键列、金额列、时间戳列等业务上不允许缺失的字段。需要注意的是,not_null 只检查 NULL,不检查空字符串或 0,这些需要额外测试或自定义测试覆盖。

unique:主键与去重保障

unique 检查列值是否唯一。语法与 not_null 一致:

      - name: order_id
        tests:
          - unique
          - not_null

dbt 生成的查询为 SELECT order_id, COUNT(*) FROM stg_orders GROUP BY order_id HAVING COUNT(*) > 1。返回重复值即失败。实际项目中,unique 和 not_null 几乎总是成对出现在主键列上,二者组合才能完整表达“主键非空且唯一”的约束。对于复合主键,dbt 内置测试无法直接表达,需要借助 dbt_utils.unique_combination_of_columns 或自定义测试。

accepted_values:枚举与业务规则校验

accepted_values 检查列值是否落在允许的集合内。它需要传入 values 参数:

      - name: status
        tests:
          - accepted_values:
              values: ['pending', 'shipped', 'delivered', 'cancelled']

生成的查询为 SELECT * FROM stg_orders WHERE status NOT IN ('pending','shipped','delivered','cancelled')。任何不在集合中的值都会导致失败。适用场景包括:订单状态、支付方式、渠道类型、性别等有限枚举字段。配置时需注意大小写敏感和前后空格问题,建议在源数据清洗阶段统一格式。若允许 NULL 值,需额外说明——NOT IN 对 NULL 的处理会导致 NULL 行不被返回,因此 accepted_values 默认不检查 NULL,NULL 是否合法应由 not_null 单独控制。

relationships:跨表引用完整性

relationships 用于检查当前表的某列值是否全部存在于另一张表的指定列中,即外键约束。它需要 to 和 field 两个参数:

      - name: customer_id
        tests:
          - relationships:
              to: ref('dim_customers')
              field: customer_id

dbt 生成的查询为:

SELECT o.customer_id
FROM stg_orders o
LEFT JOIN dim_customers c
  ON o.customer_id = c.customer_id
WHERE c.customer_id IS NULL

返回的行即为“在订单表中存在、但在客户维度表中找不到对应记录”的孤儿外键。适用场景包括:事实表到维度表的外键、子表到父表的引用、任何需要保证引用完整性的关联字段。配置时需确保 to 使用 ref() 或 source(),以便 dbt 正确解析依赖关系并安排执行顺序。

四类测试的对比与组合策略

测试 检查目标 关键参数 典型适用列 失败含义
not_null 非空 无 主键、外键、金额、时间 存在缺失值
unique 唯一 无 主键、业务编号 存在重复值
accepted_values 枚举合法 values 状态、类型、渠道 出现未定义枚举值
relationships 引用完整 to、field 外键列 存在孤儿记录
组合建议:主键列同时挂 unique 和 not_null;外键列挂 not_null 和 relationships;枚举列挂 accepted_values,若业务不允许空值再叠加 not_null。这样四类测试就能覆盖数据质量中最常见的完整性、唯一性、合法性和引用完整性四个维度。
常见误区:accepted_values 不会捕获 NULL,因为 NOT IN 对 NULL 返回 UNKNOWN。若枚举列不允许空值,必须同时配置 not_null。另外,relationships 的目标列若存在重复值,测试仍可能通过,但引用完整性语义要求目标列本身唯一,建议在目标表上也配置 unique 测试。