按目标子句合并不更新所需的列

Merge by target clause not updating desired columns

我需要编写一个 SQL 指令(不是过程)来更新日期列并在目标中已存在 ID 时插入该行。

目标 table:

ID  DT_DEBVAL   DT_FINVAL
1   2021-09-01  2050-12-31
2   2021-09-01  2050-12-31

新传入数据:

ID  DT_DEBVAL   DT_FINVAL
2   2021-09-08  2050-12-31
3   2021-09-08  2050-12-31
4   2021-09-08  2050-12-31

期望的输出:

ID  DT_DEBVAL   DT_FINVAL
1   2021-09-01  2050-12-31
2   2021-09-01  2021-09-07 -- current date minus 1 day 
2   2021-09-08  2050-12-31 
3   2021-09-08  2050-12-31
4   2021-09-08  2050-12-31

我做了和 一样的事情。这是我的代码版本:

insert into t1 (id, DT_DEBVAL, DT_FINVAL) 
select  t.id, t.DT_DEBVAL, t.DT_FINVAL from ( 
merge t1 As Target 
    using (select * from t2) AS Source
    on Target.id=Source.id and Target.dt_finval=Source.dt_finval
    when matched then
        update set Target.DT_FINVAL=DATEADD(day, -1, getdate())
    when not matched by target then insert (id, dt_debval, dt_finval) values (Source.id, Source.dt_debval, Source.dt_finval) OUTPUT $ACTION as Act, Inserted.* ) t 
    where t.Act  = 'Update'
select * from t1;

这是获得的输出:

ID  DT_DEBVAL   DT_FINVAL
1   2021-09-01  2050-12-31
2   2021-09-01  2021-09-07
2   2021-09-01  2021-09-07
3   2021-09-08  2050-12-31
4   2021-09-08  2050-12-31

如您所见,ID=2 的行并不完全正确。 知道为什么它会这样反应吗?

你的问题是,当你引用内存驻留 table inserted 时,你引用的是正在更新的行,并返回新更新的值,所以你只是插入同样的数据。您需要引用来源中的值,例如

OUTPUT $ACTION as Act, inserted.ID, inserted.DT_FINVAL AS DT_DEBVAL, source.DT_FINVAL

因此您的完整查询将是:

insert into t1 (ID, DT_DEBVAL, DT_FINVAL) 
select  t.ID, GETDATE(), t.DT_FINVAL from ( 
merge t1 As Target 
    using (select * from t2) AS Source
    on Target.ID=Source.ID and Target.DT_FINVAL=Source.DT_FINVAL
    when matched then
        update set Target.DT_FINVAL=DATEADD(day, -1, GETDATE())
    when not matched by target then 
       insert (ID, DT_DEBVAL, DT_FINVAL) 
       values (Source.ID, Source.DT_DEBVAL, Source.DT_FINVAL) 
      OUTPUT $ACTION as Act, inserted.id, source.DT_FINVAL) t 
where t.Act  = 'Update';
    
    
select * from t1 order by ID;

Example on SQL Fiddle

潜在替代方案:

insert into t1 (ID, DT_DEBVAL, DT_FINVAL) 
select  t.ID, t.DT_DEBVAL, t.DT_FINVAL from ( 
merge t1 As Target 
    using (select * from t2) AS Source
    on Target.ID=Source.ID and Target.DT_FINVAL=Source.DT_FINVAL
    when matched then
        update set Target.DT_FINVAL=DATEADD(day, -1, Source.DT_DEBVAL)
    when not matched by target then 
       insert (ID, DT_DEBVAL, DT_FINVAL) 
       values (Source.ID, Source.DT_DEBVAL, Source.DT_FINVAL) 
      OUTPUT $ACTION as Act, inserted.id, Source.DT_DEBVAL, source.DT_FINVAL) t 
where t.Act  = 'Update';

尽管如此,我要说的是,您的解决方案可能过于复杂,虽然我不会强烈远离合并(在大多数情况下我会避免,但我还没有找到一个suitable capturing columns in the output that are not part of the insert 的替代品),这是我绝对不会费心的,因为它没有任何优势。

你的逻辑永远是:

  • 更改任何现有“有效”记录的结束日期
  • 插入传入数据中的所有值(如果它们尚不存在)

这需要以下查询:

BEGIN TRANSACTION;

UPDATE t1 WITH (UPDLOCK, SERIALIZABLE)
SET    DT_FINVAL = DATEADD(DAY, -1, t2.DT_DEBVAL)
FROM   t1
       INNER JOIN t2
           ON t2.ID = t1.ID
           AND t2.DT_FINVAL = t1.DT_FINVAL;

INSERT t1 (ID, DT_DEBVAL, DT_FINVAL)
SELECT t2.ID, t2.DT_DEBVAL, t2.DT_FINVAL
FROM   t2
WHERE  NOT EXISTS (SELECT 1 FROM t1 WHERE t1.ID = t2.ID AND t2.DT_FINVAL = t1.DT_FINVAL);

COMMIT TRANSACTION;

我认为除了避免合并及其错误之外,这使意图更加清晰(至少对我而言,我认为自己非常熟悉 MERGE 语法)。它还减少了操作总数,您现在只需执行更新和插入,而不是合并(插入和更新)然后再插入。