【问题标题】:How can I extract the column name when there is a change in the data in SQL Server 2014?当 SQL Server 2014 中的数据发生变化时,如何提取列名?
【发布时间】:2017-06-24 00:12:20
【问题描述】:

我有一个表格,每晚都会插入一个数据快照。在任何时间点,列中的数据都可能发生变化(AccountNo 保持不变,RunKey 增加 1,RunDate 增加 1 天;所有其他列都可以临时更改)。以下是数据外观示例:

|帐号号 |运行键 |运行日期 |地址 |工资 |促销日期|
-------------------------------------------------- --------------------------
| 12345 | 2 | 2017 年 6 月 20 日 |大街 123 号 | 60,000 | 2017 年 1 月 15 日 |
| 12345 | 3 | 2017 年 6 月 21 日 |大街 123 号 | 60,000 | 2017 年 1 月 15 日 |
| 12345 | 4 | 2017 年 6 月 22 日 |大街 123 号 | 65,000 | 2017 年 6 月 21 日 |

使用 LAG 函数和 CASE 表达式,我能够确定何时发生变化(标志为 1 表示已发生变化):

|帐号号 |运行键 |运行日期 |地址 |地址滞后 |地址标志|工资 | SalaryLAG |SalaryFlag|促销日期|促销日期滞后|促销标志|
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------
| 12345 | 2 | 2017 年 6 月 20 日 |大街 123 号 |大街 123 号 | 0 | 60,000 | 60,000 | 0 | 2017 年 1 月 15 日 | 2017 年 1 月 15 日 | 0 |
| 12345 | 3 | 2017 年 6 月 21 日 |大街 123 号 |大街 123 号 | 0 | 60,000 | 60,000 | 0 | 2017 年 1 月 15 日 | 2017 年 1 月 15 日 | 0 |
| 12345 | 4 | 2017 年 6 月 22 日 |大街 123 号 |大街 123 号 | 0 | 65,000 | 60,000 | 1 | 2017 年 1 月 15 日 | 2017 年 6 月 21 日 | 1 |

我只需要将更改的记录插入到新表中,新表将如下所示:

|运行键 |帐号 |改变Col |上一页运行日期 |运行日期 |上一页价值 |新价值 | -------------------------------------------------- ----------------------------------------- | 4 | 12345 |工资 | 2017 年 6 月 21 日 | 2017 年 6 月 22 日 | 60,000 | 65,000 | | 4 | 12345 |促销日期 | 2017 年 6 月 21 日 | 2017 年 6 月 22 日 | 2017 年 1 月 15 日 | 2017 年 6 月 21 日 |

每列更改都会有一条新记录。因此,如果多个列发生更改,则每个更改都将记录在一个新行中。这是我需要帮助的地方,我不知道如何将仅更改的列动态插入到新表中。

【问题讨论】:

  • 您确定这就是您想要进行行版本控制或审核更改的方式吗?您可以使用except 看到这非常简单
  • 我对想法持开放态度。这只是我的第一个想法,与我合作的人没有其他建议。我更改了数据以简化它,但我正在处理费用和日期,问题是我们的经理可能会问为什么发生了变化,我们有太多的记录我们无法分辨,这就是为什么我们要创建这个表来跟踪任何变化,这样我们就可以回到任何时间点,看看哪里发生了变化,变化是什么。
  • 嗯,您正在将数据转换为一种新的审计格式。这对你来说可能没问题,但问题是你会用这些数据做什么?你想怎么查询它。你会在上面做什么聚合/等?以这种格式放置它很难链接回任何数据集,从而可能导致一些问题。所以第一个问题是你打算用它做什么。这将有助于确定这是否是一种合乎逻辑的方法恕我直言
  • 我在上面更新了我的推荐,但这个表只是为了跟踪任何变化,所以如果我们被问到为什么值发生了变化,我们可以查询这个表来查看什么/什么时候发生了变化。
  • 在这种情况下,您可以在除 RunKey 和 Rundate 之外的所有列上使用 distinct 直接查询表,然后将表自连接到该行的 min(RunDate) 和 min(RunKey)数据。这类似于“仅插入更改”,这是一个更好的主意。也就是说,不是用未更改的行来归档该表,而是仅在发生更改时插入。这将减小表格大小并消除您正在创建的额外表格。这符合业务需求吗?

标签: sql sql-server tsql


【解决方案1】:

因此,通常这是通过触发器完成的。每当对表进行插入或更新时,将对您的审计表进行后续插入。所以,我真的会调查一下。但是,如果您不想走那条路,或者这是一个无法添加触发器的第 3 方系统,您可以通过几种方式插入更改。一个快速的方法是使用except。基本上,当它们不完全匹配时,它将源表中的记录插入到审计表中。这是一个例子。

declare @source table (
                        AccountNo int
                        ,Address varchar(256)
                        ,Salary decimal(16,4)
                        ,PromotionDate datetime)
insert into @source
values
(12345,'123 Main Street',60000,'20170115')


declare @audit table (
                        AccountNo int
                        ,Address varchar(256)
                        ,Salary decimal(16,4)
                        ,PromotionDate datetime
                        ,RunDate datetime)

--load the audit table with the current version of the source table
insert into @audit
select *, getdate() from @source

--show that the tables match currently
select * from @source
select * from @audit

--insert into @audit if there are any changes (notice we haven't made any updates yet)
insert into @audit
select AccountNo, Address, Salary, PromotionDate, getdate() from @source
except 
select AccountNo, Address, Salary, PromotionDate, getdate() from @audit

--show that a record WAS NOT inserted since there was no change. There is only 1 record, the orignal version
select * from @audit

--update the promotion and salary
update @source
set
    PromotionDate = '20170331'
    ,Salary = '65000'

--insert into @audit if there are any changes
insert into @audit
select AccountNo, Address, Salary, PromotionDate, getdate() from @source
except 
select AccountNo, Address, Salary, PromotionDate, getdate() from @audit

--show that a record was inserted since there was a change
select * from @audit

然后,您所要做的就是从 @audit 表中选择并按 RunDate 排序,您可以轻松查看快速更改的内容,而不是旋转数据并为每个帐户的每次更改设置 1 行。在此示例中,尽管工资和晋升数据发生了变化,但您只会看到 1 额外的行。您可以使用您的 LEAD 和 LAG 函数,或者在 RunDate

【讨论】:

  • 有没有办法捕捉旧值?这就是这里的关键,他想知道更改的日期、旧值和新值,以及更改了哪一列。因此,他们不想插入包含更改的整行,而只想查看更改的特定列、更改日期以及旧值和新值。
  • 假设你每天运行它,更改日期是 runDate。因此,如果它发生更改,您只会得到一个新行,并且日期将是 getdate() ,它将显示它更改的日期。以另一种方式进行实际上是采取了 4 个步骤太多。这种方法向您显示更改以及更改时间,仅显示更改时间。它解决了核心问题,而不必为糟糕的设计使用解决方案恕我直言
  • @bm11 是的,触发器可以访问旧值和新值。
【解决方案2】:

以下内容可能会有所帮助,但您必须在 Cross Apply 中定义要跟踪的字段

示例dbFiddle

Declare @YourTable Table ([AccountNo] int,[RunKey] int,[RunDate] date,[Address] varchar(50),[Salary] int,[PromotionDate] date)
Insert Into @YourTable Values 
 (12345,2,'06/20/2017','123 Main Street',60000,'01/15/2017')
,(12345,3,'06/21/2017','123 Main Street',60000,'01/15/2017')
,(12345,4,'06/22/2017','123 Main Street',65000,'06/21/2017')


;with cte as (
    Select A.AccountNo
          ,A.RunKey
          ,A.RunDate
          ,B.*
         ,PreValue=Lag(Value)   over (Partition By AccountNo,Item Order by RunDate) 
         ,PreDate =Lag(RunDate) over (Partition By AccountNo,Item Order by RunDate) 
     From  @YourTable A
     Cross Apply ( values ('Address'      ,cast(A.[Address] as varchar(max)))
                         ,('Salary'       ,cast(A.[Salary]  as varchar(max)))
                         ,('PromotionDate',cast(A.[PromotionDate] as varchar(max)))
                 ) B (Item,Value)
)
Select *
 From  cte
 Where Value<>PreValue and PreValue is not null

退货

如果它有助于可视化

cte 生成

【讨论】:

  • 我用实际的样本数据对此进行了测试,效果很好。我正在使用大约 900k 行的完整数据集对其进行测试。它运行非常缓慢,因为我有 26 列来跟踪更改,并且 cte0 似乎会为每个 RunKey 的每一列创建一行。
  • @bm11 参见 EDIT-Second 选项
  • 我将您的脚本添加到一个游标中,该游标遍历帐号并插入到我的表中。适用于所有以前的记录,并将创建一个触发器。您知道如何使用触发器提取列名吗?当我运行每晚插入时,我希望它查看帐号级别的最新 RunKey 并进行比较以查看是否发生了任何更改。
【解决方案3】:

我不确定我是否清楚地理解了您的要求,但我会寻找使用 INSERT / UPDATE 触发器来解决此类审计试验任务的解决方案。也许这会让事情变得更简单。

【讨论】:

    猜你喜欢
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多