【问题标题】:Tracking changes for field using CDC or T-SQL with CDC使用 CDC 或 T-SQL 和 CDC 跟踪字段更改
【发布时间】:2018-01-18 23:44:54
【问题描述】:

更新操作的以下 CDC 结果集。只有area 字段已更新。

在之前的截图中,只有部分包含在表格中。领域要多得多。他们不时更新一些不更新。在以下查询中,我尝试在可用视图中按字段显示更改的统计信息。

with History AS (
SELECT
cz.GUID as Id, 
cz.category,
isnull(cz.area, 0) as area, 
isnull(cz.oilwidthmin,0) as oilwidthmin, 
isnull(cz.oilwidthmax,0) as oilwidthmax, 
isnull(cz.efectivwidthmin,0) as efectivwidthmin,
isnull(cz.efectivwidthmax,0) as efectivwidthmax,
isnull(cz.koafporistmin,0) as koafporistmin, 
isnull(cz.koafporistmax,0) as koafporistmax,
CASE cz.__$operation 
WHEN 1 THEN 'DELETE'
WHEN 2 THEN 'INSERT'
WHEN 3 THEN 'Before UPDATE'
WHEN 4 THEN 'After UPDATE'
END operation,
map.tran_begin_time as beginT, 
map.tran_end_time as endT
FROM cdc.fn_cdc_get_all_changes_dbo_EXT_GeolObject_KategZalezh(sys.fn_cdc_get_min_lsn('dbo_EXT_GeolObject_KategZalezh'), sys.fn_cdc_get_max_lsn(), 'all') AS cz 
INNER JOIN  [cdc].[lsn_time_mapping] map
    ON cz.[__$start_lsn] = map.start_lsn
)
SELECT  field, val, operation, beginT, endT FROM History
unpivot ( [val] for field in
(
--category,
area, 
oilwidthmin, 
oilwidthmax, 
efectivwidthmin, 
efectivwidthmax, 
koafporistmin, 
koafporistmax))t    where id = '2D166098-7CBD-4622-9EB0-000070506FE6'   

查询结果如下:

但之前的结果包含额外的数据。 预期结果必须如下:

我知道 CDC 按行跟踪更改。或者也许我错了?如果不是,我如何为 SQL 中的val 字段做一些比较器。我对 t-sql 没有深入的了解,我想到的所有东西都以某种方式被游标使用。有任何想法吗? 也许以某种方式使用 CT(更改跟踪)?也许以某种方式使用group by

几乎正确的答案。以下查询返回预期结果:

WITH History AS (
    SELECT
        *,
        CASE cz.__$operation 
            WHEN 1 THEN 'DELETE'
            WHEN 2 THEN 'INSERT'
            WHEN 3 THEN 'Before UPDATE'
            WHEN 4 THEN 'After UPDATE'
            END operation,
        map.tran_begin_time as beginT, 
        map.tran_end_time as endT
    FROM cdc.fn_cdc_get_all_changes_dbo_EXT_GeolObject_KategZalezh(sys.fn_cdc_get_min_lsn('dbo_EXT_GeolObject_KategZalezh'), sys.fn_cdc_get_max_lsn(), 'all') AS cz 
        INNER JOIN  [cdc].[lsn_time_mapping] map
            ON cz.[__$start_lsn] = map.start_lsn
    where cz.GUID = '2D166098-7CBD-4622-9EB0-000070506FE6'
),
UnpivotedValues AS(
    SELECT  guid, field, val, operation, beginT, endT 
    FROM History
        UNPIVOT ( [val] FOR field IN
        (
            area, 
            oilwidthmin, 
            oilwidthmax, 
            efectivwidthmin, 
            efectivwidthmax, 
            koafporistmin, 
            koafporistmax
        ))t
),
UnpivotedWithLastValue AS (
    SELECT 
        *,
        --Use LAG() to get the last value for the same field
        LAG(val, 1) OVER (PARTITION BY field ORDER BY BeginT) LastVal
    FROM UnpivotedValues
)
--Filter out record where the value equals the last value for the same field
SELECT * FROM UnpivotedWithLastValue WHERE val <> LastVal OR LastVal IS NULL ORDER BY guid

此查询的结果如下所示:

但是当WHERE cz.GUID = 不存在或者在WHERE 谓词中使用多个GUID 进行查询时,我得到以下结果:

此结果适用于两个 GUID。第一行中 LastVal 的值必须为 16691。就像第 4 行中的 val

【问题讨论】:

    标签: tsql sql-server-2014 unpivot cdc


    【解决方案1】:

    您不能将 CDC 设置为仅跟踪已更改列的值。但是,您可以很容易地过滤掉查询中未更改的值。

    考虑以下查询,它是原始查询的简化副本:

    WITH History AS (
        SELECT
            *,
            CASE cz.__$operation 
                WHEN 1 THEN 'DELETE'
                WHEN 2 THEN 'INSERT'
                WHEN 3 THEN 'Before UPDATE'
                WHEN 4 THEN 'After UPDATE'
                END operation,
            map.tran_begin_time as beginT, 
            map.tran_end_time as endT
        FROM cdc.fn_cdc_get_all_changes_Dbo_YourTable(sys.fn_cdc_get_min_lsn('Dbo_YourTable'), sys.fn_cdc_get_max_lsn(), 'all') AS cz 
            INNER JOIN  [cdc].[lsn_time_mapping] map
                ON cz.[__$start_lsn] = map.start_lsn
    ),
    UnpivotedValues AS(
        SELECT id, field, val, operation, beginT, endT, t.tran_id
        FROM History
            UNPIVOT ( [val] FOR field IN
            (Column1, Column2, Column3))t
    ),
    UnpivotedWithLastValue AS (
        SELECT 
            *,
            --Use LAG() to get the last value for the same field
            LAG(val, 1) OVER (PARTITION BY id, field ORDER BY BeginT) LastVal
        FROM UnpivotedValues
    )
    --Filter out record where the value equals the last value for the same field
    SELECT * FROM UnpivotedWithLastValue WHERE val <> LastVal OR LastVal IS NULL
    ORDER BY Id, beginT
    

    在这个查询中,我使用了 LAG() 函数来获取每个字段的最后一个值。根据这个值,您可以过滤掉最终查询中未更改的记录,如上所示。

    【讨论】:

    • 如果在WITH History AS ( 中使用where cz.id = 'some ID',您的示例可以正常工作。在这种情况下,结果为LastVal сorrespond to real values。但如果 where 包含多个 id 或 where 不存在,则 LastVal 的值是混合的。
    • 您将 cz.id 列用于什么目的?如果没有数据样本,这很难回答。
    • 对不起。敬请期待。我补充了我的问题。
    • 啊哈——我明白了。我把我的查询简化了一点:-) 我现在在示例中添加了一个 id 列。您应该在 LAG() 语句的 PARTITION BY 子句中添加您的 cz.id。如果这不能解决问题,请告诉我。
    • 太棒了!关于您的附加问题:如果我理解正确,那么是的,可以创建一个存储过程来输出来自多个表的更改。它可能需要一些动态 SQL,并且可能会变得更复杂,具体取决于您的具体要求。
    【解决方案2】:

    在您的情况下,您可以使用 ROW_NUMBER 函数对更改进行顺序编号 - 在此之后,您可以将每个顺序更改与前一个更改(基于字段和 id)连接起来,并仅输出具有不同值的行。

    类似这样的:

    WITH 
    History AS 
    (
    SELECT
        cz.GUID as Id, 
        cz.category,
        isnull(cz.area, 0) as area, 
        isnull(cz.oilwidthmin,0) as oilwidthmin, 
        isnull(cz.oilwidthmax,0) as oilwidthmax, 
        isnull(cz.efectivwidthmin,0) as efectivwidthmin,
        isnull(cz.efectivwidthmax,0) as efectivwidthmax,
        isnull(cz.koafporistmin,0) as koafporistmin, 
        isnull(cz.koafporistmax,0) as koafporistmax,
        CASE 
            cz.__$operation 
            WHEN 1 THEN 'DELETE'
            WHEN 2 THEN 'INSERT'
            WHEN 3 THEN 'Before UPDATE'
            WHEN 4 THEN 'After UPDATE'
        END operation,
        map.tran_begin_time as beginT, 
        map.tran_end_time as endT,
        ROW_NUMBER() OVER (PARTITION BY cz.GUID ORDER BY map.tran_end_time ASC) as rn
    FROM 
        cdc.fn_cdc_get_all_changes_dbo_EXT_GeolObject_KategZalezh(sys.fn_cdc_get_min_lsn('dbo_EXT_GeolObject_KategZalezh'), sys.fn_cdc_get_max_lsn(), 'all') AS cz 
    INNER JOIN  
        [cdc].[lsn_time_mapping] map ON cz.[__$start_lsn] = map.start_lsn
    ),
    
    History2 AS
    (
        SELECT  id, field, val, operation, beginT, endT, rn FROM History
        unpivot ( [val] for field in
        (
        --category,
        area, 
        oilwidthmin, 
        oilwidthmax, 
        efectivwidthmin, 
        efectivwidthmax, 
        koafporistmin, 
        koafporistmax))t    
        where id = '2D166098-7CBD-4622-9EB0-000070506FE6'
    )
    
    -- return the values that were inserted first
    SELECT
        a.*
    FROM
        History2 a
    WHERE 
        a.rn=1
    
    UNION ALL
    
    -- ... and then return only the values that are different from the previous ones
    SELECT
        a.*
    FROM
        History2 a
    INNER JOIN
        History2 b ON a.id = b.id AND a.field=b.field AND a.rn = b.rn-1 AND a.value<>b.value
    WHERE
        a.rn>1
    

    顺便说一句;您还可以将 CDC 配置为仅跟踪某些列中的更改,而不是整个表中的更改。查看 sys.sp_cdc_enable_table 存储过程的@captured_column_list。

    【讨论】:

    • 据我了解,我必须创建两个临时表并通过过滤器a.id = b.id AND a.field=b.field AND a.rn = b.rn-1 AND a.value&lt;&gt;b.value 合并它们。我说的对吗?
    • 您不需要创建任何临时表;您只需两次参考 CTE (History2)。查看建议的查询。
    • 您的示例返回几个错误:Incorrect syntax near the keyword 'with'.Incorrect syntax near ',' 另外我不理解 History2 中的查询。据我所知unpivot 应用于结果表,但在历史 2 中没有任何表。
    • 没有测试查询,因为我没有您的表格样本...我现在已经纠正了错误;尝试执行更改后的查询。 History2 CTE 只是原始查询的副本,其中添加了一些列(id、rn)。
    • 谢谢。查询有效但不正确。我更新一个字段 2 次,另一个字段 1 次。查询不返回最后更新的最后结果,但显示最后一个endT....
    猜你喜欢
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 2016-04-09
    • 1970-01-01
    • 2022-01-06
    • 2020-09-24
    • 1970-01-01
    • 2017-10-21
    相关资源
    最近更新 更多