2009-03-04 46 views
5

Tôi đã thực hiện việc này trước khi ở đâu đó mà tôi chắc chắn về điều đó!Xác định khóa chính cũ trong Trình kích hoạt SQL

Tôi có bảng SQL Server 2000 mà tôi cần ghi nhật ký các thay đổi cho các trường trên bản cập nhật và chèn vào bảng Ghi nhật ký thứ hai. Một phiên bản đơn giản của cấu trúc Tôi đang sử dụng là dưới đây:

MainTable 
ID varchar(10) PRIMARY KEY 
DESCRIPTION varchar(50) 

LogTable 
OLDID varchar(10) 
NEWID varchar(10) 

Đối với bất kỳ một cái gì đó lĩnh vực khác như thế này sẽ làm việc lớn:

Select i.DESCRIPTION As New, d.DESCRIPTION As Old 
From Inserted i 
LEFT JOIN Deleted d On i.ID=d.ID 

... Nhưng rõ ràng là tham gia sẽ thất bại nếu ID là đã thay đổi.

Tôi không thể sửa đổi Bảng theo cách, quyền năng duy nhất tôi có trong cơ sở dữ liệu này là tạo trình kích hoạt.

Ngoài ra có ai đó có thể dạy tôi thời gian đi du lịch và tôi sẽ trở lại quá khứ và tự hỏi bản thân mình sau đó làm thế nào tôi đã làm điều này? Chúc mừng :)


Edit:

Tôi nghĩ rằng tôi cần phải làm rõ một vài điều ở đây. Đây là không thực sự là cơ sở dữ liệu của tôi, nó là một hệ thống đã tồn tại mà tôi hầu như không kiểm soát được, ngoài việc viết trình kích hoạt này.

Câu hỏi của tôi là làm cách nào tôi có thể truy xuất khóa chính cũ nếu khóa chính đã được thay đổi. Tôi không cần phải được thông báo rằng tôi không nên thay đổi khóa chính hoặc về việc đuổi theo khóa ngoại trừ vv. Đó không phải là vấn đề của tôi :)

+0

Bạn không nên thay đổi khóa chính của bạn. –

+1

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

Trả lời

2

Có thể giả định rằng chèn vào và bảng DELETED trình bày cho bạn trong một kích hoạt được đảm bảo để được theo thứ tự?

+0

Hmm trong thử nghiệm giới hạn của tôi có đúng thứ tự, nhưng sau đó có câu hỏi về việc sử dụng con trỏ (hiệu suất), và nó có đáng tin cậy không, nếu tôi thấy nó trong tài liệu ở đâu đó tôi sẽ vui hơn :) – keith

+0

với điều này cuối cùng và nó làm việc cho tôi trong vấn đề khủng khiếp hạn chế của tôi! – keith

3
DECLARE @OldKey int, @NewKey int; 

SELECT @Oldkey = [ID] FROM DELETED; 
SELECT @NewKey = [ID] FROM INSERTED; 

Điều này chỉ hoạt động nếu bạn có một hàng. Nếu không, bạn không có "neo" để liên kết các hàng cũ và mới. Vì vậy, hãy kiểm tra trình kích hoạt của bạn cho> 1 trong INSERTED.

0

---- mới ---- thêm một cột sắc để bàn rằng ứng dụng không thể thay đổi, sau đó bạn có thể sử dụng cột mới tham gia chèn các bảng bị xóa trong vòng cò:

ALTER TABLE YourTableName ADD 
    PrivateID int NOT NULL IDENTITY (1, 1) 
GO 

---- cũ ---- Đừng bao giờ cập nhật/thay đổi giá trị khóa. Làm thế nào bạn có thể làm điều này và sửa chữa tất cả các phím nước ngoài của bạn?

Tôi sẽ không khuyên bạn nên sử dụng trình kích hoạt không thể xử lý một tập hợp các hàng.

Nếu bạn phải thay đổi khóa, hãy chèn hàng mới có khóa và giá trị mới thích hợp, sử dụng SCOPE_IDENTITY() nếu đó là những gì bạn đang làm. Xóa hàng cũ. Đăng nhập cho hàng cũ mà nó đã được thay đổi thành khóa của hàng mới, mà bây giờ bạn đã có. Tôi hy vọng không có khóa ngoại nào trên khóa đã thay đổi trong nhật ký của bạn ...

+0

Bạn biết rằng @@ identity là một điều rất xấu để sử dụng phải không? – HLGEM

+0

@HLGEM, tôi nghĩ SCOPE_IDENTITY() là mới năm 2005, và câu hỏi là vào năm 2000, nhưng tôi đã kiểm tra và SCOPE_IDENTITY() đã được thêm vào năm 2000, vì vậy tôi đã thay đổi nó để phản ánh rằng –

1

Tôi không nghĩ điều đó là có thể. Hãy tưởng tượng nếu bạn có 4 dòng trong bảng:

1 Val1 
2 Val2 
3 Val3 
4 Val4 

Bây giờ phát hành bản cập nhật sau:

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 

Bây giờ, làm thế nào thì bạn sẽ phân biệt giữa những gì đã xảy ra với hàng 1 & 2 và những gì đã xảy ra với hàng 3 & 4.Và quan trọng hơn, bạn có thể mô tả những gì khác nhau giữa chúng? Tất cả nội dung sẽ cho bạn biết cột nào đã được cập nhật sẽ không giúp bạn.

Nếu có thể trong trường hợp này có thêm phím trên bảng (ví dụ: Mô tả là UNIQUE) và quy tắc cập nhật của bạn cho phép, bạn có thể viết trình kích hoạt để ngăn cập nhật đồng thời cả hai khóa và sau đó bạn có thể sử dụng bất kỳ khóa nào chưa được cập nhật để tương quan hai bảng.

+0

trên bàn, nhưng không may (ngoài khóa chính) không có chỉ mục duy nhất. – keith

1

Nếu bạn phải xử lý chèn/cập nhật nhiều hàng và không có khóa thay thế nào được đảm bảo không thay đổi, cách duy nhất tôi có thể thấy để thực hiện việc này là sử dụng trình kích hoạt INSTEAD OF. Ví dụ, trong trình kích hoạt, bạn có thể phá vỡ lệnh chèn/cập nhật ban đầu thành một lệnh trên mỗi hàng, lấy từng id cũ trước khi bạn chèn/cập nhật.

0

Trong các trình kích hoạt trong SQL Server, bạn có quyền truy cập vào hai bảng: đã xóa và chèn. Cả hai đã được đề cập. Đây là cách chúng hoạt động phụ thuộc vào hành động kích hoạt được bắn vào lúc:

INSERT HOẠT ĐỘNG

  • xóa - không được sử dụng
  • chèn - chứa các hàng mới được thêm vào bảng

HOẠT ĐỘNG DELETE

  • xóa - chứa các hàng được loại bỏ khỏi bảng
  • chèn - không được sử dụng

CẬP NHẬT HOẠT ĐỘNG

  • xóa - chứa các hàng như họ sẽ tồn tại trước khi phẫu thuật CẬP NHẬT
  • được chèn - chứa các hàng như chúng tồn tại sau thao tác UPDATE

Các chức năng này theo mọi cách như bảng. Vì vậy, nó là hoàn toàn có thể sử dụng liên tiếp hoạt động dựa như một cái gì đó như sau (Operation chỉ tồn tại trên bảng kiểm toán, cũng như DateChanged):

INSERT INTO MyAuditTable 
(ID, FirstColumn, SecondColumn, ThirdColumn, Operation, DateChanged) 
VALUES 
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-Before', GETDATE() 
FROM deleted 
UNION ALL 
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-After', GETDATE() 
FROM inserted 
0

Bạn có thể tạo cột nhận dạng mới trên bảng MainTable (được đặt tên cho ví dụ tương quan) và các bảng được chèn và xóa tương ứng bằng cột này. Cột mới này phải minh bạch cho mã hiện có.

INSERT INTO LOG(OLDID, NEWID) 
SELECT deleted.id AS OLDID, inserted.id AS NEWID 
FROM inserted 
INNER JOIN deleted 
    ON inserted.correlationid = deleted.correlationid 

Chú ý, bạn có thể chèn bản ghi trùng lặp trong bảng nhật ký.

+0

Một ý tưởng hay sẽ thực sự hiệu quả, nhưng trong câu hỏi tôi đã nói rằng tôi không thể thay đổi bảng theo bất kỳ cách nào – keith

+0

Bạn có thể tạo một bảng mới với cột nhận dạng, mối quan hệ 1-1 và duy trì, sau đó sử dụng cách tiếp cận được mô tả trước đây, tham gia các bảng đã chèn và xóa. Nếu bạn không thể tạo bảng, bạn nên sử dụng "thay vì kích hoạt" nhưng vấn đề hiệu suất sẽ xảy ra trong bản cập nhật lớn vì trình kích hoạt sẽ được gọi một lần cho mỗi hàng bị ảnh hưởng. tôi hy vọng nó sẽ hữu ích –

0

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 
+0

Rõ ràng đây chỉ là thịt ra cùng một ý tưởng mà tôi nghĩ rằng Chris KL đã trở lại trong năm 2009 –

Các vấn đề liên quan