Sql server 确定SQL触发器中的旧主键
我以前在什么地方做过,我肯定 我有一个SQL Server 2000表,需要在更新时记录对字段的更改,并将其插入第二个日志记录表中。我使用的结构的简化版本如下:Sql server 确定SQL触发器中的旧主键,sql-server,sql-server-2000,triggers,Sql Server,Sql Server 2000,Triggers,我以前在什么地方做过,我肯定 我有一个SQL Server 2000表,需要在更新时记录对字段的更改,并将其插入第二个日志记录表中。我使用的结构的简化版本如下: MainTable ID varchar(10) PRIMARY KEY DESCRIPTION varchar(50) LogTable OLDID varchar(10) NEWID varchar(10) 对于任何其他领域,类似这样的工作都会非常有效: Select i.DESCRIPTION As New, d.DESCRI
MainTable
ID varchar(10) PRIMARY KEY
DESCRIPTION varchar(50)
LogTable
OLDID varchar(10)
NEWID varchar(10)
对于任何其他领域,类似这样的工作都会非常有效:
Select i.DESCRIPTION As New, d.DESCRIPTION As Old
From Inserted i
LEFT JOIN Deleted d On i.ID=d.ID
…但是很明显,如果ID被更改,连接将失败
我不能以这种方式修改表,我在这个数据库中唯一的能力就是创建触发器
或者,有没有人可以教我时间旅行,我会回到过去,问自己当时是怎么做到的?干杯:)
编辑: 我想我需要澄清一些事情。这实际上不是我的数据库,它是一个预先存在的系统,除了编写这个触发器之外,我几乎无法控制它 我的问题是,如果所述主键已更改,如何检索旧主键。我不需要被告知我不应该更改主键或关于追逐外键等。这不是我的问题:) 这仅在只有一行的情况下有效。否则,就没有链接新行和旧行的“锚”。因此,检查触发器中插入的>1。--new---- 将应用程序无法更改的标识列添加到表中,然后可以使用该新列将插入的表连接到触发器中已删除的表:
ALTER TABLE YourTableName ADD
PrivateID int NOT NULL IDENTITY (1, 1)
GO
----旧的----
永远不要更新/更改键值。如何做到这一点并修复所有外键
我不建议使用不能处理一组行的触发器
如果必须更改键,请插入具有正确新键和值的新行,如果您正在执行此操作,请使用SCOPE_IDENTITY()。删除旧的行。记录已更改为新行的键的旧行,您现在应该拥有该键。我希望您的日志中更改的密钥上没有外键…我认为这是不可能的。想象一下,如果表中有4行:
1 Val1
2 Val2
3 Val3
4 Val4
现在发布以下更新:
UPDATE MainTable SET
ID = CASE ID WHEN 1 THEN 2 WHEN 2 THEN 1 ELSE ID END
Description = CASE ID WHEN 3 THEN 'Val4' WHEN 4 THEN 'Val3' ELSE Description END
现在,您将如何区分第1行和第2行发生的情况以及第3行和第4行发生的情况。更重要的是,你能描述一下他们之间的区别吗?所有告诉你哪些列已经更新的信息都帮不了你
如果在这种情况下表上可能有一个额外的键(例如描述是唯一的),并且您的更新规则允许这样做,您可以编写触发器以防止同时更新两个键,然后,您可以使用尚未更新的键来关联这两个表。如果您必须处理多个行插入/更新,并且没有保证不会更改的备用键,那么我可以看到的唯一方法是使用INSTEAD OF触发器。例如,在触发器中,您可以将原始的insert/update命令拆分为每行一个命令,在插入/更新之前获取每个旧id。在SQL Server的触发器中,您可以访问两个表:deleted和inserted。这两个问题都已经提到了。以下是它们的工作方式,具体取决于触发器触发的动作: 插入操作
- 已删除-未使用
- 插入-包含要添加到表中的新行
- 已删除-包含从表中删除的行
- 插入-未使用
- 已删除-包含更新操作之前存在的行
- 已插入-包含更新操作后将存在的行
是否可以假定触发器中呈现给您的插入表和删除表的顺序是相同的?您可以在table MainTable上创建一个新的标识列(命名为例如correlationid),并使用此列关联插入表和删除表。 这个新列对于现有代码应该是透明的
INSERT INTO LOG(OLDID, NEWID)
SELECT deleted.id AS OLDID, inserted.id AS NEWID
FROM inserted
INNER JOIN deleted
ON inserted.correlationid = deleted.correlationid
请注意,您可以在日志表中插入重复的记录。当然,任何人都不应该更改表上的主键——但这正是触发器的作用(部分)是阻止人们做不应该做的事情。在Oracle或MySQL中,编写一个触发器来截取主键的更改并停止它们是一项很简单的任务,但在SQL Server中却一点也不容易 您当然希望能够做的是简单地做这样的事情:
if exists
(
select *
from inserted changed
join deleted old
where changed.rowID = old.rowID
and changed.id != old.id
)
... [roll it all back]
这就是为什么人们在谷歌上搜索相当于ROWID的SQL Server。好吧,SQL Server没有它;所以你必须想出另一种方法
一个快速但遗憾的是不可靠的版本是编写一个instead of update触发器,查看插入的行中是否有主键未在更新的表中找到,反之亦然。这将捕获大部分(但不是全部)错误:
if exists
(
select *
from inserted lost
left join updated match
on match.id = lost.id
where match.id is null
union
select *
from deleted new
left join inserted match
on match.id = new.id
where match.id is null
)
-- roll it all back
但这仍然没有捕捉到像
update myTable
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
现在,我尝试假设插入的表和删除的表是按这样的方式排序的,即同时对插入的表和删除的表进行光标扫描将得到正确匹配的行。这似乎是可行的。实际上,您将触发器转换为Oracle中可用的、MySQL中强制使用的for-each-row触发器的等价物……但我认为在大规模更新中性能会很差,因为这不是SQL Server的本机行为。另外,这取决于一个假设,即我实际上无法在任何地方找到文档,因此我不愿意依赖它。但这种结构的代码在我的SQLServer2008R2安装中似乎可以正常工作。这篇文章结尾的脚本
if exists
(
select *
from inserted lost
left join updated match
on match.id = lost.id
where match.id is null
union
select *
from deleted new
left join inserted match
on match.id = new.id
where match.id is null
)
-- roll it all back
update myTable
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
begin try
drop table kpTest;
end try
begin catch
end catch
go
create table kpTest( id int primary key, name nvarchar(10) )
go
begin try
drop trigger kpTest_ioU;
end try
begin catch
end catch
go
create trigger kpTest_ioU on kpTest
instead of update
as
begin
if exists
(
select *
from inserted lost
left join deleted match
on match.id = lost.id
where match.id is null
union
select *
from deleted new
left join inserted match
on match.id = new.id
where match.id is null
)
raisError( 'Changed primary key', 16, 1 )
else
update kpTest
set name = i.name
from kpTest
join inserted i
on i.id = kpTest.id
;
end
go
insert into kpTest( id, name ) values( 0, 'zero' );
insert into kpTest( id, name ) values( 1, 'one' );
insert into kpTest( id, name ) values( 2, 'two' );
insert into kpTest( id, name ) values( 3, 'three' );
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
-- This throws an error, appropriately
update kpTest set id = 5, name = 'FIVE' where id = 1
go
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
-- This allows the change, inappropriately
update kpTest
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
, name = UPPER( name )
go
select * from kpTest
/*
0 ZERO
1 TWO -- WRONG WRONG WRONG
2 ONE -- WRONG WRONG WRONG
3 THREE
*/
-- Put it back
update kpTest
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
, name = LOWER( name )
go
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
drop trigger kpTest_ioU
go
create trigger kpTest_ioU on kpTest
instead of update
as
begin
declare newIDs cursor for select id, name from inserted;
declare oldIDs cursor for select id from deleted;
declare @thisOldID int;
declare @thisNewID int;
declare @thisNewName nvarchar(10);
declare @errorFound int;
set @errorFound = 0;
open newIDs;
open oldIDs;
fetch newIDs into @thisNewID, @thisNewName;
fetch oldIDs into @thisOldID;
while @@FETCH_STATUS = 0 and @errorFound = 0
begin
if @thisNewID != @thisOldID
begin
set @errorFound = 1;
close newIDs;
deallocate newIDs;
close oldIDs;
deallocate oldIDs;
raisError( 'Primary key changed', 16, 1 );
end
else
begin
update kpTest
set name = @thisNewName
where id = @thisNewID
;
fetch newIDs into @thisNewID, @thisNewName;
fetch oldIDs into @thisOldID;
end
end;
if @errorFound = 0
begin
close newIDs;
deallocate newIDs;
close oldIDs;
deallocate oldIDs;
end
end
go
-- Succeeds, appropriately
update kpTest
set name = UPPER( name )
go
select * from kpTest;
/*
0 ZERO
1 ONE
2 TWO
3 THREE
*/
-- Succeeds, appropriately
update kpTest
set name = LOWER( name )
go
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
-- Fails, appropriately
update kpTest
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
go
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
-- Fails, appropriately
update kpTest
set id = id + 1
go
select * from kpTest;
/*
0 zero
1 one
2 two
3 three
*/
-- Succeeds, appropriately
update kpTest
set id = id, name = UPPER( name )
go
select * from kpTest;
/*
0 ZERO
1 ONE
2 TWO
3 THREE
*/
drop table kpTest
go