2009-03-04 10 views
5

Ich habe dies schon einmal getan, irgendwo bin ich mir sicher!Ermitteln alten Primärschlüssel in einem SQL-Trigger

Ich habe eine SQL Server 2000-Tabelle, die ich Änderungen an Feldern auf Aktualisierungen protokollieren muss und in einer zweiten Protokolltabelle eingefügt wird. Eine vereinfachte Version der Struktur Ich verwende unter:

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

LogTable 
OLDID varchar(10) 
NEWID varchar(10) 

für andere Bereiche so etwas wie dies würde große Arbeit:

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

... Aber natürlich würde die Verbindung fehl, wenn ID war geändert.

Ich kann die Tabellen nicht in der Weise ändern, die einzige Macht, die ich in dieser Datenbank habe, ist, einen Auslöser zu verursachen.

Alternativ gibt es jemanden, der mir die Zeitreise beibringen kann und ich werde zurück in die Vergangenheit gehen und mich dann fragen, wie ich das gemacht habe? Cheers :)


Edit:

Ich glaube, ich brauche hier ein paar Dinge zu klären. Das ist nicht wirklich meine Datenbank, es ist ein bereits existierendes System, das ich fast keine Kontrolle über habe, außer diesem Auslöser zu schreiben.

Meine Frage ist, wie kann ich den alten Primärschlüssel abrufen, wenn dieser Primärschlüssel geändert wurde. Ich brauche nicht gesagt, dass ich nicht den Primärschlüssel oder über Fremdschlüssel usw. Jagd nach oben ändern sollte Das ist nicht mein Problem :)

+0

Sie sollten Ihren Primärschlüssel nicht sein würde zu ändern. –

+1

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

Antwort

2

Ist es möglich, anzunehmen, dass die inserted und deleted-Tabellen Ihnen in einem Trigger vorgestellt werden garantiert in der gleichen Reihenfolge sein?

+0

Hmm in meinen begrenzten Tests ja, sie waren in der richtigen Reihenfolge, aber dann gibt es die Frage der Verwendung eines Cursors (Leistung), und ist es zuverlässig, wenn ich es in der Dokumentation irgendwo sah ich würde viel glücklicher :) – keith

+0

Ich ging damit am Ende und es hat für mich in meinem furchtbar begrenzten Problem funktioniert! – keith

3
DECLARE @OldKey int, @NewKey int; 

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

Dies funktioniert nur, wenn Sie eine einzelne Zeile haben. Ansonsten haben Sie keinen "Anker", um alte und neue Zeilen zu verknüpfen. Überprüfen Sie Ihren Trigger für> 1 in INSERTED.

0

---- ---- neue eine Identitätsspalte der Tabelle hinzufügen, dass die Anwendung nicht ändern können, können Sie dann die neue Spalte verwenden, um die eingefügt die gelöschten Tabellen innerhalb des Triggers zu verbinden:

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

---- alt ---- Aktualisieren/Ändern Sie niemals Schlüsselwerte. Wie können Sie das tun und all Ihre Fremdschlüssel reparieren?

Ich würde nicht empfehlen, jemals einen Trigger zu verwenden, der eine Reihe von Zeilen nicht verarbeiten kann.

Wenn Sie den Schlüssel ändern müssen, fügen Sie eine neue Zeile mit dem richtigen neuen Schlüssel und den richtigen Werten ein, verwenden Sie SCOPE_IDENTITY(), wenn Sie das tun. Löschen Sie die alte Zeile. Protokollieren Sie für die alte Zeile, die geändert wurde, den neuen Schlüssel, den Sie jetzt haben sollten. Ich hoffe, es gibt keinen Fremdschlüssel auf dem geänderten Schlüssel in Ihrem Protokoll ...

+0

Sie wissen, dass @@ Identität ist eine sehr schlechte Sache, richtig zu verwenden? – HLGEM

+0

@HLGEM, ich dachte SCOPE_IDENTITY() war neu in 2005, und die Frage war für 2000, aber ich überprüfte und SCOPE_IDENTITY() wurde im Jahr 2000 hinzugefügt, also änderte ich es, um das widerzuspiegeln –

1

Ich glaube nicht, dass es möglich ist. Stellen Sie sich vor, wenn Sie 4 Zeilen in der Tabelle haben:

1 Val1 
2 Val2 
3 Val3 
4 Val4 

Jetzt geben Sie den folgenden Update:

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 

Nun, wie wollen Sie unterscheiden, was passiert ist, um die Zeilen 1 & 2 und was passiert Reihen 3 & 4.Und noch wichtiger, kannst du beschreiben, was zwischen ihnen ist? Alle Informationen, die Ihnen sagen, welche Spalten aktualisiert wurden, werden Ihnen nicht weiterhelfen.

Wenn es in diesem Fall möglich ist, dass es einen zusätzlichen Schlüssel in der Tabelle gibt (zB Beschreibung ist UNIQUE), und Ihre Update-Regeln erlauben, könnten Sie den Auslöser schreiben, um gleichzeitige Aktualisierungen beider Schlüssel zu verhindern, und dann können Sie verwenden welcher Schlüssel nicht aktualisiert wurde, um die beiden Tabellen zu korrelieren.

+0

Twas eine gute Idee, es gibt eine Tonne von Indizes auf dem Tisch, aber leider (außer dem Primärschlüssel) gibt es keinen eindeutigen Index. – keith

1

Wenn Sie mit mehrzeiligen Einfügungen/Aktualisierungen umgehen müssen und es keinen alternativen Schlüssel gibt, der garantiert nicht geändert wird, kann ich dies nur mithilfe eines INSTEAD OF-Triggers erreichen. Zum Beispiel könnten Sie im Trigger den ursprünglichen Befehl insert/update in einen Befehl pro Zeile zerlegen und jede alte ID vor dem Einfügen/update packen.

0

Innerhalb von Triggern in SQL Server haben Sie Zugriff auf zwei Tabellen: gelöscht und eingefügt. Beide wurden bereits erwähnt. Hier ist, wie sie funktionieren, je nachdem, welche Aktion der Trigger feuert auf:

Einfügevorgang

  • gelöscht - nicht verwendet
  • eingefügt - enthält die neuen Zeilen in der Tabelle hinzugefügt werden

LÖSCHBETRIEB

  • gelöscht - enthält die Zeilen aus der Tabelle entfernt werden
  • eingesetzt - nicht verwendet

UPDATE OPERATION

  • gelöscht - enthält die Zeilen, wie sie vor der UPDATE-Operation existieren würden
  • eingefügt - enthält die Zeilen, wie sie nach der UPDATE-Operation existieren würden

Diese funktionieren in jeder Hinsicht wie Tabellen. Daher ist es durchaus möglich, eine Zeile basierend Betrieb wie etwa wie folgt (Operation existiert nur auf der Audit-Tabelle, wie auch DateChanged) zu verwenden:

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

Sie können eine neue Identitätsspalte in der Tabelle MainTable (z. B. correlationid genannt) erstellen und mit dieser Spalte eingefügte und gelöschte Tabellen korrelieren. Diese neue Spalte sollte für vorhandenen Code transparent sein.

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

Achten Sie darauf, dass Sie doppelte Datensätze in die Protokolltabelle einfügen können.

+0

Eine gute Idee, die in der Tat funktionieren würde, aber in der Frage habe ich festgestellt, dass ich die Tabelle in keiner Weise ändern konnte – keith

+0

Sie können eine neue Tabelle mit Identity-Spalte, Eins-zu-Eins-Beziehung zu Main-Tabelle und Kaskaden-Update erstellen, dann verwenden der Ansatz, der zuvor beschrieben wurde, und den eingefügten und gelöschten Tabellen. Wenn Sie keine Tabelle erstellen können, sollten Sie "statt trigger" verwenden, aber bei großen Aktualisierungen sollten Leistungsprobleme auftreten, da der Trigger einmal pro betroffene Zeile aufgerufen wird. ich hoffe, es wird nützlich sein –

0

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

Offensichtlich dies ist nur die gleiche Idee, die ich denke, Chris KL im Jahr 2009 hatte –

Verwandte Themen