【问题标题】:When updating a row, how to also keep the old values and update the linking tables?更新行时,如何同时保留旧值并更新链接表?
【发布时间】:2018-08-28 07:26:01
【问题描述】:

我有两张桌子; ItemTableProductTableProductTableItemID 列链接到 ItemTableID 列。两个表的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>

每当我想手动UPDATEItemTable 中添加一行时,通常只需一个查询即可完成:

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与新的ItemIDLatest = 1在同一行

 

这种自动的INSERTUPDATE 最好只使用纯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 clause 会很有用。
  • 您能否按 ID 定位行?在这种情况下,您只需执行UPDATE table SET Latest=0 WHERE ID=12INSERT table SET ItemID=250, Value='foo', ValueOther='bar', Latest=1。这也假设列 ID 是自动递增的。如果您有另一种语言来补充 SQL,那么提供它也会非常有帮助。
  • 如果 ProductTable 只包含有关一种产品的信息,我可以按照您的逻辑进行操作,否则我看不到如何确定应该复制哪个 ProductTable 行。请edit你的问题充实你的例子。
  • @GordThompson 我已经对我的问题进行了相当多的编辑,现在应该更清楚了。请检查一下:)

标签: python sql sql-server tsql pyodbc


【解决方案1】:

为此使用OUTPUT

我不会为此使用触发器,因为触发器中的大量活动会导致在发生其他更改时发生不希望的事情。相反,我使用的是存储过程

问题改变后我重写了我的答案:

-- 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 的现有行时才有效。

【讨论】:

  • 我喜欢OUTPUT,但我发现它要求在 dml'ed 和 into'd 表上没有触发器限制。
  • 将它与我的实际表格一起使用时,我只得到Cannot insert the value NULL into column 'Latest', table 'dbo.ItemTable'; column does not allow nulls. INSERT fails.。我必须更改两个 INSERT 语句,同时将值 1 插入到列 Latest
  • 这是一个不错的解决方案,我非常感谢您的回答,但我将不得不使用 @AliAl-Mosawi 非常干净的解决方案。
  • @ruohola 而不是更改查询,您可以考虑修改列的默认值。我想在插入新行时,除非另有说明,否则您会希望最新为 1
  • @t-clausen.dk 是的,这也可能是一个有效的选择。但是添加几个Latest 插入并不是我不接受这个的原因。我选择另一个答案的最大原因是它更好,因为它适用于普通的 UPDATE 语句,并且更适合我的用例。
【解决方案2】:

下面的触发器会做你想做的事:

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,并根据您的要求更新子表。

以下是我更新后的输出:

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;

【讨论】:

  • 谢谢你,这是一个非常干净的解决方案!享受赏金:)
【解决方案3】:

如果您使用的是现代版本的 SQL Server(即 >= SQL Server 2016 SP1)

您是否考虑过为此使用临时表? 你可以阅读更多关于Microsofts BOL

这是一种非常聪明的方法来跟踪表的变化。它执行得很好,但改变表结构有点工作。

【讨论】:

  • 我从未听说过这些 :O 它们看起来真的非常好,但不幸的是我正在运行 SQL Server 2008 SP1,并且无法控制更改那...
  • @ruohola;使用寿命即将结束:2019 年 7 月 8 日 community.connection.com/… 这可能是升级的一个很好的理由...
  • 我确信我的公司会及时更新,但我无法控制何时更新或更新到什么版本。
  • 我知道那种(绝望的)感觉。但好的论据通常有助于说服管理者。
  • @ruohola;我知道那种感觉(就像额外的圣诞礼物)。不要忘记添加 SP 和 CU。
猜你喜欢
  • 1970-01-01
  • 2019-07-08
  • 2021-09-25
  • 2013-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多