Tất nhiên không ai phải thay đổi khóa chính trên bàn - nhưng đó chính xác là những gì gây ra được cho là (một phần), là để giữ cho mọi người không làm những việc mà họ không nên làm. Đó là một nhiệm vụ tầm thường trong Oracle hoặc MySQL để viết một kích hoạt chặn các thay đổi cho các khóa chính và ngăn chặn chúng, nhưng không phải ở tất cả các dễ dàng trong SQL Server.
gì bạn tất nhiên sẽ yêu để có thể làm là nên chỉ cần làm một cái gì đó như thế này:
if exists
(
select *
from inserted changed
join deleted old
where changed.rowID = old.rowID
and changed.id != old.id
)
... [roll it all back]
Đó là lý do tại sao mọi người đi ra ngoài googling cho SQL Server tương đương với ROWID. Vâng, SQL Server không có nó; vì vậy bạn phải tìm ra cách tiếp cận khác.
Phiên bản nhanh, nhưng đáng buồn là không thể chống lại, là viết thay vì kích hoạt cập nhật để xem liệu có bất kỳ hàng nào được chèn có khóa chính không được tìm thấy trong bảng được cập nhật hay ngược lại. Điều này sẽ bắt MOST, nhưng không phải tất cả, các lỗi:
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
Nhưng điều này vẫn không bắt được một bản cập nhật như ...
update myTable
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
Bây giờ, tôi đã cố gắng làm cho giả định rằng các bảng được chèn vào và xóa được sắp xếp theo cách như vậy mà cursoring thông qua các bảng chèn vào và xóa cùng một lúc sẽ cung cấp cho bạn các hàng phù hợp. Và APPEARS này hoạt động. Trong thực tế, bạn bật kích hoạt vào tương đương với các trigger cho mỗi hàng có sẵn trong Oracle và bắt buộc trong MySQL ... nhưng tôi sẽ tưởng tượng hiệu suất sẽ xấu trên các bản cập nhật lớn vì đây không phải là hành vi gốc đối với SQL Server. Ngoài ra nó phụ thuộc vào một giả định rằng tôi không thể thực sự tìm thấy tài liệu ở bất cứ đâu và do đó không muốn phụ thuộc vào. Nhưng mã có cấu trúc theo cách đó APPEARS để hoạt động đúng trên cài đặt SQL Server 2008 R2 của tôi. Kịch bản ở cuối bài viết này nêu bật cả hành vi của giải pháp nhanh nhưng không chống bom và hành vi của giải pháp giả Oracle thứ hai.
Nếu ai có thể chỉ cho tôi đến một nơi mà giả định của tôi được ghi chép lại và được bảo đảm bởi Microsoft Tôi muốn là một anh chàng rất biết ơn ...
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
Bạn không nên thay đổi khóa chính của bạn. –
Trong một thế giới hoàn hảo, bạn hoàn toàn chính xác. Tuy nhiên tôi đang làm việc bên ngoài cơ sở dữ liệu và ứng dụng sử dụng bảng này để tôi không thể thực thi quy tắc đó – keith