按目标子句合并不更新所需的列
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;
潜在替代方案:
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
语法)。它还减少了操作总数,您现在只需执行更新和插入,而不是合并(插入和更新)然后再插入。
我需要编写一个 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;
潜在替代方案:
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
语法)。它还减少了操作总数,您现在只需执行更新和插入,而不是合并(插入和更新)然后再插入。