Bestimmen Sie Alt Primärschlüssel in einem SQL-Trigger
-
03-07-2019 - |
Frage
Ich habe dies getan, bevor irgendwo bin ich sicher!
Ich habe eine SQL Server 2000-Tabelle, die ich brauche Änderungen an Feldern auf Aktualisierungen und Einfügungen in eine zweite Logging-Tabelle zu protokollieren. 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 jedes anderes Feld so etwas wie dies funktionieren würde groß:
Select i.DESCRIPTION As New, d.DESCRIPTION As Old
From Inserted i
LEFT JOIN Deleted d On i.ID=d.ID
... Aber die offensichtlich anschließen würde scheitern, wenn ID geändert wurde.
Ich kann die Tabellen in Art und Weise ändern, die einzige Macht, die ich in dieser Datenbank ist es, einen Auslöser zu erstellen.
Als Alternative gibt es jemanden, lehren kann mir Zeit zu reisen und ich werde in die Vergangenheit zurück und frage mich damals, wie ich das getan? Prost:)
Edit:
Ich glaube, ich brauche hier ein paar Dinge zu klären. Dies ist nicht wirklich meine Datenbank , es ist ein bereits bestehendes System, das ich fast keine Kontrolle über andere habe, als diese Trigger zu schreiben.
Meine Frage ist, wie kann ich die alten Primärschlüssel abrufen, wenn der Primärschlüssel geändert wurde. Ich brauche nicht gesagt, dass ich nicht den Primärschlüssel ändern soll oder über Fremdschlüssel der Jagd nach oben usw. Das ist nicht mein Problem:)
Lösung
Ist es möglich, anzunehmen, dass die inserted und deleted-Tabellen Sie in einem Trigger präsentiert werden garantiert in der gleichen Reihenfolge sein?
Andere Tipps
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“ alte und neue Zeilen zu verknüpfen. So überprüfen Sie in Ihrem Auslöser für> 1 in gestecktem.
Ich glaube nicht, es ist möglich. Stellen Sie sich vor, wenn Sie 4 Zeilen in der Tabelle:
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 werden Sie unterscheiden, was passiert ist, um die Zeilen 1, 2 und was geschah mit den Zeilen 3 und 4. Und was noch wichtiger ist, können Sie beschreiben, was zwischen ihnen anders? All die Dinge, die Ihnen sagt, welche Spalten aktualisiert wurden, wird dir nicht helfen.
Wenn es in diesem Fall möglich ist, dass es eine zusätzliche Taste auf dem Tisch (zB Beschreibung ist UNIQUE), und die Update-Regeln erlauben es, können Sie den Auslöser schreiben könnten gleichzeitige Aktualisierungen beiden Tasten zu verhindern, und dann können Sie verwenden, je nachdem, welche Schlüssel wurde die beiden Tabellen korrelieren nicht aktualisiert.
Wenn Sie mehrreihigen Einsätze / Updates behandeln müssen, und es gibt keinen alternativen Schlüssel, der nicht ändern garantiert wird, die einzige Möglichkeit, die ich sehen kann, dies zu tun, ist ein INSTEAD OF-Trigger zu verwenden. Zum Beispiel, in dem Trigger Sie den ursprünglichen insert / update-Befehl in einen Befehl pro Zeile brechen könnten, jede alte ID greifen, bevor Sie / Update einfügen.
Innerhalb Trigger 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:
INSERT OPERATION
- gelöscht - nicht verwendet
- eingefügt - enthält die neuen Zeilen in der Tabelle hinzugefügt werden
LÖSCHEN BETRIEB
- gelöscht - enthält die Zeilen aus der Tabelle entfernt werden
- eingefügt - 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 Funktion in jeder Hinsicht wie Tabellen. Daher ist es durchaus möglich, eine Zeile basierend Betrieb wie etwa wie folgt (Operation existiert auf der Audit-Tabelle nur, genau wie 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
---- ---- 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 Nicht immer aktualisieren / ändern Schlüsselwerte. Wie können Sie dies tun, und alle Ihre Fremdschlüssel beheben?
Ich würde nie empfehlen einen Trigger verwenden, das nicht eine Reihe von Zeilen verarbeiten kann.
Wenn Sie den Schlüssel ändern müssen, legen Sie eine neue Zeile mit der richtigen neuen Schlüssel und Werte, die Verwendung SCOPE_IDENTITY (), wenn das ist, was Ihr tut. Löschen Sie die alte Zeile. Melden Sie sich für die alte Zeile, dass es zu der neuen Reihe der Schlüssel geändert wurde, die Sie jetzt haben sollte. Ich hoffe, es ist kein Fremdschlüssel auf dem geänderten Schlüssel im Log ...
Sie können eine neue Identitätsspalte auf dem Tisch MainTable (mit dem Namen zum Beispiel CorrelationId) erstellen und eingefügt und gelöscht Tabellen mit dieser Spalte korrelieren. Diese neue Spalte soll für bestehenden 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, könnten Sie doppelte Datensätze in der Log-Tabelle einfügen.
Natürlich sollte niemand den Primärschlüssel auf dem Tisch zu ändern - aber das ist genau das, was Auslöser soll für sein (teilweise), ist es, Menschen zu verhindern, Dinge zu tun, sie nicht tun sollen. Es ist eine triviale Aufgabe in Oracle oder MySQL einen Trigger zu schreiben, die Änderungen an Primärschlüssel ab und stoppt sie, aber gar nicht so einfach in SQL Server.
Was Sie würde natürlich gerne in der Lage sein, so etwas zu tun wäre, einfach zu tun:
if exists
(
select *
from inserted changed
join deleted old
where changed.rowID = old.rowID
and changed.id != old.id
)
... [roll it all back]
Welches ist, warum Menschen für den SQL Server-Äquivalent von ROWID googeln gehen. Nun, SQL Server hat es nicht; so müssen Sie sich mit einem anderen Ansatz kommen.
Eine schnelle, aber leider nicht bombensicher, Version ist einen anstelle von Update-Trigger zu schreiben, ob irgendeine der eingefügten Zeilen ein Primärschlüssel nicht in der aktualisierten Tabelle oder umgekehrt gefunden hat, um zu sehen aussieht. 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 ist noch kein Update fangen wie ...
update myTable
set id = case
when id = 1 then 2
when id = 2 then 1
else id
end
Jetzt habe ich versucht, die Annahme, dass die eingefügten und gelöschten Tabellen so bestellt werden, dass gleichzeitig durch die Tabellen inserted und deleted Cursorn werden Sie richtig passende Zeilen. Und das scheint zu funktionieren. In der Tat drehen Sie den Auslöser in das Äquivalent der for-each-Reihe löst in Oracle und obligatorisch in MySQL zur Verfügung ... aber ich könnte mir vorstellen, wird die Leistung auf massiven Updates schlecht sein, da dies zu SQL Server nicht nativen Verhalten. Auch hängt es von der Annahme, dass ich überall nicht wirklich dokumentiert finden kann und so bin nur ungern davon abhängen. Aber Code strukturiert auf diese Weise richtig auf meinem SQL Server 2008 R2 Installation scheint zu funktionieren. Das Skript am Ende dieses Beitrags Highlights sowohl das Verhalten des schnell-but-not-bombensichere Lösung und das Verhalten des zweiten, pseudo-Oracle-Lösung.
Wenn jemand mich irgendwo zeigen könnte, wo meine Annahme dokumentiert und garantiert von Microsoft mir sehr dankbar Kerl sein würde ...
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