更新行时,如何同时保留旧值并更新链接表?
When updating a row, how to also keep the old values and update the linking tables?
我有两张桌子; ItemTable
和 ProductTable
。 ProductTable
的 ItemID
列链接到 ItemTable
的 ID
列。两个表的 ID
列都是主键和标识列。
像这样:
ItemTable:
ID Col ColOther Latest Time
100 'old' 'oldother' 1 <Autogenerated timestamp>
ProductTable:
ID ItemID Value ValueOther Latest Time
12 100 'foo' 'bar' 1 <Autogenerated timestamp>
每当我想 手动 UPDATE
ItemTable
中的一行时,通常只需一个查询即可完成:
query = \
"""
UPDATE ItemTable
SET Col = ?, ColOther = ?
WHERE ID = 100;
"""
cursor.execute(query, 'new', 'newother')
我不想像上面那样只做 UPDATE
,我想为 ItemTable
做这些事情:
- 自动
UPDATE
旧行有Latest = 0
INSERT
具有更新值的行,以及 Latest = 1
(假设这一行得到 ID
250)
然后 ProductTable
:
- 自动
UPDATE
ProductTable 中的旧链接行具有 Latest = 0
- 和自动
INSERT
与新的ItemID
和Latest = 1
同一行
这个自动 INSERT
和 UPDATE
最好只用一个纯 SQL 查询(可能使用 OUTPUT
,但我不熟悉),或者它可以用一些 Python 代码来实现。我该怎么做呢?
想要的最终结果:
ItemTable:
ID Col ColOther Latest Time
100 'old' 'oldother' 0 <Autogenerated timestamp>
250 'new' 'newother' 1 <Autogenerated timestamp>
ProductTable:
ID ItemID Value ValueOther Latest Time
12 100 'foo' 'bar' 0 <Autogenerated timestamp>
110 250 'foo' 'bar' 1 <Autogenerated timestamp>
为此使用 OUTPUT:
我不会为此使用触发器,触发器中的太多 activity 会导致在发生其他更改时发生不希望的事情。相反,我正在使用存储过程
改题后我重写了答案:
-- create test tables and test data
CREATE table item
(ID int identity,
Col varchar(99),
ColOther varchar(99),
Latest bit default 1,
Time datetime default getdate())
INSERT item(col, colother)
values('old','oldother')
CREATE table product
(ID int identity,
ItemID int,
[Value] varchar(20),
ValueOther varchar(20),
Latest bit default 1,
Time datetime default getdate())
INSERT product(itemid, [value], valueother)
values(1, 'foo', 'bar')
go
-- create procedure
CREATE procedure p_insert
(
@id int,
@col varchar(99),
@colOther varchar(99)
)
as
BEGIN tran t
DECLARE @out table(IDold int, IDnew int)
INSERT item(col, colother, Latest)
OUTPUT @id, inserted.id INTO @out
SELECT @col, @colother, 1
FROM item
WHERE
id = @id
and latest = 1
UPDATE i
SET Latest=0
FROM item i
JOIN @out o
ON o.IDold = i.id
and i.Latest=1
DECLARE @p table
(itemid int,
[value] varchar(20),
valueother varchar(20))
UPDATE p
SET Latest=0
OUTPUT o.IDnew, deleted.[value],deleted.[valueother]
INTO @p
FROM product p
JOIN @out o
ON p.ItemID = o.IDold
WHERE p.Latest=1
INSERT product(itemid, [value], valueother, Latest)
SELECT itemid, value, valueother, 1
FROM @p
commit tran t
要对此进行测试:
exec p_insert 1, 'a','b'
请注意,这仅在您尝试更新 Latest=1 的现有行时才有效。
下面的触发器将执行您想要的操作:
CREATE TRIGGER ItemTable_OnUpdate
ON ItemTable
INSTEAD OF UPDATE
AS
BEGIN
DECLARE @newId int, @oldId int
INSERT INTO ItemTable(Col, ColOther, Latest)
SELECT Col, ColOther, 1 FROM INSERTED
--get old and new ids
SELECT @newId=@@IDENTITY, @oldId=ID FROM INSERTED
UPDATE ItemTable SET Latest=0 WHERE ID=@oldId
--updating ProductTable
INSERT INTO ProductTable (ItemID, [Value], ValueOther, Latest)
SELECT @newId, [Value], ValueOther, 1 FROM ProductTable WHERE ItemID=@oldId
UPDATE ProductTable SET Latest=0 WHERE ItemID=@oldId
END;
创建触发器后,任何更新都会在 ItemTable
中插入新记录,更新上一条记录。使用 Latest = 0
,并根据您的要求更新子项 table。
下面是我更新后的输出:
UPDATE ItemTable SET col='new', ColOther='newother' WHERE ID=12;
项目表:
ID Col ColOther Latest Time
12 old oldother 0 0x00000000000007F0
13 new newother 1 0x00000000000007EF
产品表:
ID ItemID Value ValueOther Latest Time
18 12 foo bar 0 0x00000000000007F2
19 13 foo bar 1 0x00000000000007F1
对于多行更新,我们可以使用下面的触发器,因为上面的触发器被设计为请求者指定的更新(一行)的参考。我相信下面将处理多行更新。请尝试一下。
create TRIGGER ItemTable_OnUpdate
ON ItemTable
INSTEAD OF UPDATE
AS
BEGIN
Declare @s table( IdNew int,IdOld int)
MERGE INTO ItemTable AS dest
USING INSERTED AS ins ON 1=0 -- always false
WHEN NOT MATCHED BY TARGET -- happens for every row, because 1 is never 0
THEN
INSERT (Col, ColOther, Latest)
VALUES (Col, ColOther, Latest)
OUTPUT inserted.ID, ins.ID INTO @s (IdNew, IdOld);
UPDATE ItemTable SET Latest=0 WHERE ID in (select IdOld from @s)
--updating ProductTable
INSERT INTO ProductTable (ItemID, [Value], ValueOther, Latest)
SELECT s.IdNew, pt.[Value], pt.ValueOther, 1 FROM @s s
inner join ProductTable pt on pt.ItemID=s.IdOld
UPDATE ProductTable SET Latest=0 WHERE ItemID in (select IdOld from @s)
END;
如果您使用的是 SQL Server 的现代版本(即 >= SQL Server 2016 SP1)
您是否考虑过为此使用 Temporal Table?
您可以在 Microsofts BOL
阅读更多信息
这是跟踪 tables 更改的一种非常聪明的方法。它表现良好,但改变 table 结构有点工作。
我有两张桌子; ItemTable
和 ProductTable
。 ProductTable
的 ItemID
列链接到 ItemTable
的 ID
列。两个表的 ID
列都是主键和标识列。
像这样:
ItemTable:
ID Col ColOther Latest Time
100 'old' 'oldother' 1 <Autogenerated timestamp>
ProductTable:
ID ItemID Value ValueOther Latest Time
12 100 'foo' 'bar' 1 <Autogenerated timestamp>
每当我想 手动 UPDATE
ItemTable
中的一行时,通常只需一个查询即可完成:
query = \
"""
UPDATE ItemTable
SET Col = ?, ColOther = ?
WHERE ID = 100;
"""
cursor.execute(query, 'new', 'newother')
我不想像上面那样只做 UPDATE
,我想为 ItemTable
做这些事情:
- 自动
UPDATE
旧行有Latest = 0
INSERT
具有更新值的行,以及Latest = 1
(假设这一行得到ID
250)
然后 ProductTable
:
- 自动
UPDATE
ProductTable 中的旧链接行具有Latest = 0
- 和自动
INSERT
与新的ItemID
和Latest = 1
同一行
这个自动 INSERT
和 UPDATE
最好只用一个纯 SQL 查询(可能使用 OUTPUT
,但我不熟悉),或者它可以用一些 Python 代码来实现。我该怎么做呢?
想要的最终结果:
ItemTable:
ID Col ColOther Latest Time
100 'old' 'oldother' 0 <Autogenerated timestamp>
250 'new' 'newother' 1 <Autogenerated timestamp>
ProductTable:
ID ItemID Value ValueOther Latest Time
12 100 'foo' 'bar' 0 <Autogenerated timestamp>
110 250 'foo' 'bar' 1 <Autogenerated timestamp>
为此使用 OUTPUT:
我不会为此使用触发器,触发器中的太多 activity 会导致在发生其他更改时发生不希望的事情。相反,我正在使用存储过程
改题后我重写了答案:
-- create test tables and test data
CREATE table item
(ID int identity,
Col varchar(99),
ColOther varchar(99),
Latest bit default 1,
Time datetime default getdate())
INSERT item(col, colother)
values('old','oldother')
CREATE table product
(ID int identity,
ItemID int,
[Value] varchar(20),
ValueOther varchar(20),
Latest bit default 1,
Time datetime default getdate())
INSERT product(itemid, [value], valueother)
values(1, 'foo', 'bar')
go
-- create procedure
CREATE procedure p_insert
(
@id int,
@col varchar(99),
@colOther varchar(99)
)
as
BEGIN tran t
DECLARE @out table(IDold int, IDnew int)
INSERT item(col, colother, Latest)
OUTPUT @id, inserted.id INTO @out
SELECT @col, @colother, 1
FROM item
WHERE
id = @id
and latest = 1
UPDATE i
SET Latest=0
FROM item i
JOIN @out o
ON o.IDold = i.id
and i.Latest=1
DECLARE @p table
(itemid int,
[value] varchar(20),
valueother varchar(20))
UPDATE p
SET Latest=0
OUTPUT o.IDnew, deleted.[value],deleted.[valueother]
INTO @p
FROM product p
JOIN @out o
ON p.ItemID = o.IDold
WHERE p.Latest=1
INSERT product(itemid, [value], valueother, Latest)
SELECT itemid, value, valueother, 1
FROM @p
commit tran t
要对此进行测试:
exec p_insert 1, 'a','b'
请注意,这仅在您尝试更新 Latest=1 的现有行时才有效。
下面的触发器将执行您想要的操作:
CREATE TRIGGER ItemTable_OnUpdate
ON ItemTable
INSTEAD OF UPDATE
AS
BEGIN
DECLARE @newId int, @oldId int
INSERT INTO ItemTable(Col, ColOther, Latest)
SELECT Col, ColOther, 1 FROM INSERTED
--get old and new ids
SELECT @newId=@@IDENTITY, @oldId=ID FROM INSERTED
UPDATE ItemTable SET Latest=0 WHERE ID=@oldId
--updating ProductTable
INSERT INTO ProductTable (ItemID, [Value], ValueOther, Latest)
SELECT @newId, [Value], ValueOther, 1 FROM ProductTable WHERE ItemID=@oldId
UPDATE ProductTable SET Latest=0 WHERE ItemID=@oldId
END;
创建触发器后,任何更新都会在 ItemTable
中插入新记录,更新上一条记录。使用 Latest = 0
,并根据您的要求更新子项 table。
下面是我更新后的输出:
UPDATE ItemTable SET col='new', ColOther='newother' WHERE ID=12;
项目表:
ID Col ColOther Latest Time
12 old oldother 0 0x00000000000007F0
13 new newother 1 0x00000000000007EF
产品表:
ID ItemID Value ValueOther Latest Time
18 12 foo bar 0 0x00000000000007F2
19 13 foo bar 1 0x00000000000007F1
对于多行更新,我们可以使用下面的触发器,因为上面的触发器被设计为请求者指定的更新(一行)的参考。我相信下面将处理多行更新。请尝试一下。
create TRIGGER ItemTable_OnUpdate
ON ItemTable
INSTEAD OF UPDATE
AS
BEGIN
Declare @s table( IdNew int,IdOld int)
MERGE INTO ItemTable AS dest
USING INSERTED AS ins ON 1=0 -- always false
WHEN NOT MATCHED BY TARGET -- happens for every row, because 1 is never 0
THEN
INSERT (Col, ColOther, Latest)
VALUES (Col, ColOther, Latest)
OUTPUT inserted.ID, ins.ID INTO @s (IdNew, IdOld);
UPDATE ItemTable SET Latest=0 WHERE ID in (select IdOld from @s)
--updating ProductTable
INSERT INTO ProductTable (ItemID, [Value], ValueOther, Latest)
SELECT s.IdNew, pt.[Value], pt.ValueOther, 1 FROM @s s
inner join ProductTable pt on pt.ItemID=s.IdOld
UPDATE ProductTable SET Latest=0 WHERE ItemID in (select IdOld from @s)
END;
如果您使用的是 SQL Server 的现代版本(即 >= SQL Server 2016 SP1)
您是否考虑过为此使用 Temporal Table? 您可以在 Microsofts BOL
阅读更多信息这是跟踪 tables 更改的一种非常聪明的方法。它表现良好,但改变 table 结构有点工作。