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:)

War es hilfreich?

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top