Natürlich sollte niemand den Primärschlüssel auf dem Tisch ändern - aber das ist genau das, was Auslöser (teilweise) sein sollen, ist, Leute davon abzuhalten, Dinge zu tun, die sie nicht tun sollten. Es ist eine triviale Aufgabe in Oracle oder MySQL, einen Trigger zu schreiben, der Änderungen an Primärschlüsseln abfängt und sie stoppt, aber in SQL Server gar nicht so einfach.
Was Sie würde natürlich gerne in der Lage sein zu tun wäre, tun einfach etwas wie folgt aus:
if exists
(
select *
from inserted changed
join deleted old
where changed.rowID = old.rowID
and changed.id != old.id
)
... [roll it all back]
, weshalb Menschen für den SQL Server-Äquivalent von ROWID googeln gehen. Nun, SQL Server hat es nicht; Sie müssen sich also eine andere Herangehensweise einfallen lassen.
Eine schnelle, aber leider nicht bombensichere Version besteht darin, einen statt des Aktualisierungs-Triggers zu schreiben, der überprüft, ob eine der eingefügten Zeilen einen Primärschlüssel enthält, der nicht in der aktualisierten Tabelle gefunden wurde oder umgekehrt. Dies würde fangen die meisten, aber nicht alle, der Fehler:
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
Aber das verfängt noch kein Update wie ...
update myTable
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
Nun, ich habe versucht, die Annahme, dass Die eingefügten und gelöschten Tabellen sind so angeordnet, dass das gleichzeitige Durchlaufen der eingefügten und gelöschten Tabellen Ihnen die passenden Zeilen liefert. Und das scheint zu funktionieren. In der Tat werden Sie den Auslöser in das Äquivalent der für jede Zeile verfügbaren Trigger in Oracle und obligatorisch in MySQL ... aber ich würde mir vorstellen, dass die Leistung bei massiven Updates schlecht sein wird, da dies nicht natives Verhalten für SQL Server ist. Es hängt auch von einer Annahme ab, dass ich nirgends tatsächlich dokumentiert finden kann und deshalb zögere ich mich darauf zu verlassen. Aber der Code ist so strukturiert, dass er bei meiner SQL Server 2008 R2-Installation ordnungsgemäß funktioniert. Das Skript am Ende dieses Posts zeigt sowohl das Verhalten der Fast-but-not-Bombproof-Lösung als auch das Verhalten der zweiten Pseudo-Oracle-Lösung.
Wenn jemand mich irgendwo zeigen könnte, wo meine Annahme dokumentiert und garantiert von Microsoft ich sehr dankbar Kerl ...
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
Sie sollten Ihren Primärschlüssel nicht sein würde zu ändern. –
In einer perfekten Welt sind Sie absolut richtig. Aber ich arbeite außerhalb der Datenbank und Anwendung, die diese Tabelle verwendet, so kann ich diese Regel nicht erzwingen – keith