You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让增量dbt模型同步源数据的删除记录至临时表?

问题场景

现有dbt增量同步逻辑用于生成临时表emp__dbt_tmp,出现以下问题:

  • 3月20日,源表raw.emp的一条记录成功同步到临时表和目标表;
  • 3月22日,该记录在源表中被标记为删除(cdc_operation='D'、is_deleted=true),但这条删除变更未同步到临时表,导致目标表中对应记录始终存在。

需求:如何让删除标记的记录同步到临时表,并最终在目标表中完成删除操作?是否需要用post-hook或者其他增量策略?

解决思路

不需要依赖post-hook,核心是调整增量过滤规则和merge语句逻辑,让删除变更能流入临时表,并触发目标表的删除动作,具体操作如下:

1. 放开增量同步的过滤限制

当前增量逻辑里加了is_deleted = false,直接把删除标记的记录拦在了外面。要删掉这个条件,只保留时间过滤(用目标表的最新更新时间做基准,而非硬写死时间),这样所有新增/更新的变更(包括删除)都能进入临时表。

2. 给merge语句加删除分支

默认的merge只有更新和插入逻辑,需要新增WHEN MATCHED AND 源表是删除标记 THEN DELETE的分支,当临时表传来删除记录时,直接删掉目标表的对应行。

3. 保留最新版本记录的逻辑

原有的ROW_NUMBER()按mandt+loc分区、按时间倒序取最新记录的逻辑可以保留,确保每个主键只取最新的变更(包括最新的删除标记)。

修改后的完整代码

dbt模型核心代码

{{ config(
    materialized='incremental',
    unique_key=['mandt', 'loc'],
    on_schema_change='append_new_columns',
    partition_by={
        "field": "gcp_updated_ts",
        "data_type": "timestamp",
        "granularity": "day"  
    }
) }}

WITH source_data AS (
    SELECT 
        COALESCE(SAFE_CAST(mandt AS STRING), '') AS mandt, 
        COALESCE(SAFE_CAST(loc AS STRING), '') AS loc,
        SAFE_CAST(RECORDSTAMP AS TIMESTAMP) AS gcp_updated_ts, 
        SAFE_CAST(OPERATION_FLAG AS STRING) AS cdc_operation, 
        SAFE_CAST(IS_DELETED AS BOOLEAN) AS is_deleted,
        -- 按主键分区,取最新的变更记录
        ROW_NUMBER() OVER (PARTITION BY mandt, loc ORDER BY recordstamp DESC) AS row_num
    FROM {{ source('raw', 'emp') }}
   
    {% if is_incremental() %}
        -- 用目标表的最新时间做过滤,而非硬写死时间
        WHERE recordstamp > (SELECT MAX(gcp_updated_ts) FROM {{ this }})
        -- 移除is_deleted = false,允许删除标记的记录进入
    {% endif %}
)

SELECT * EXCEPT(row_num) FROM source_data WHERE row_num = 1

自定义merge语句(通过post-hook实现)

如果dbt默认生成的merge不支持删除分支,可以通过post-hook自定义完整的merge逻辑:

{{ config(
    materialized='incremental',
    unique_key=['mandt', 'loc'],
    on_schema_change='append_new_columns',
    partition_by={
        "field": "gcp_updated_ts",
        "data_type": "timestamp",
        "granularity": "day"  
    },
    post_hook=[
        """
        MERGE INTO `vk`.`transient`.`emp` AS DBT_INTERNAL_DEST
        USING (SELECT * FROM `vk`.`transient`.`emp__dbt_tmp`) AS DBT_INTERNAL_SOURCE
        ON (
            DBT_INTERNAL_SOURCE.mandt = DBT_INTERNAL_DEST.mandt
            AND DBT_INTERNAL_SOURCE.loc = DBT_INTERNAL_DEST.loc
        )
        -- 匹配到删除标记记录时,删除目标表行
        WHEN MATCHED AND DBT_INTERNAL_SOURCE.is_deleted = TRUE THEN DELETE
        -- 匹配到正常更新记录时,更新目标表行
        WHEN MATCHED THEN UPDATE SET
            `mandt` = DBT_INTERNAL_SOURCE.`mandt`,
            `loc` = DBT_INTERNAL_SOURCE.`loc`,
            `posn` = DBT_INTERNAL_SOURCE.`posn`,
            `gcp_updated_ts` = DBT_INTERNAL_SOURCE.`gcp_updated_ts`,
            `is_deleted` = DBT_INTERNAL_SOURCE.`is_deleted`,
            `cdc_operation` = DBT_INTERNAL_SOURCE.`cdc_operation`
        -- 未匹配到记录时,插入新行
        WHEN NOT MATCHED THEN INSERT
            (`mandt`, `loc`, `posn`,`gcp_updated_ts`,`is_deleted`,`cdc_operation`)
        VALUES
            (`mandt`, `loc`, `posn`,`gcp_updated_ts`,`is_deleted`,`cdc_operation`)
        """
    ]
) }}

-- 下面是原有的source_data逻辑,和上面的模型代码一致
WITH source_data AS (
    SELECT 
        COALESCE(SAFE_CAST(mandt AS STRING), '') AS mandt, 
        COALESCE(SAFE_CAST(loc AS STRING), '') AS loc,
        SAFE_CAST(RECORDSTAMP AS TIMESTAMP) AS gcp_updated_ts, 
        SAFE_CAST(OPERATION_FLAG AS STRING) AS cdc_operation, 
        SAFE_CAST(IS_DELETED AS BOOLEAN) AS is_deleted,
        ROW_NUMBER() OVER (PARTITION BY mandt, loc ORDER BY recordstamp DESC) AS row_num
    FROM {{ source('raw', 'emp') }}
   
    {% if is_incremental() %}
        WHERE recordstamp > (SELECT MAX(gcp_updated_ts) FROM {{ this }})
    {% endif %}
)

SELECT * EXCEPT(row_num) FROM source_data WHERE row_num = 1

内容的提问来源于stack exchange,提问作者Vaishak Nambiar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:10:57