Frage

Betrachten Sie diese trigger:

ALTER TRIGGER myTrigger 
   ON someTable 
   AFTER INSERT
AS BEGIN
  DELETE FROM someTable
         WHERE ISNUMERIC(someField) = 1
END

Ich habe eine Tabelle, someTable, und ich bin versucht zu verhindern, dass Menschen einsetzen bad records.Für die Zwecke dieser Frage einen fehlerhaften Eintrag hat ein Feld "someField", die numerisch.

Natürlich, der richtige Weg, dies zu tun ist NICHT mit einem trigger, aber ich habe keine Kontrolle über die Quellcode...nur die SQL-Datenbank.Also ich kann nicht wirklich verhindern, dass die insertion des schlechten Zeile, aber ich kann löschen Sie es sofort, das ist gut genug für meine Bedürfnisse.

Der Auslöser funktioniert, mit einem problem...wenn es ausgelöst wird, es scheint nie zu löschen, der gerade eingefügt fehlerhaften Eintrag...es löscht alle ALTEN schlechten Aufnahmen, aber es nicht löschen, der gerade eingefügt schlechte Bilanz.So gibt es oft eine schlechte Bilanz im Umlauf, dass es nicht gelöscht wird, bis jemand anderes kommt und tut das andere EINFÜGEN.

Ist dies ein problem, das in meinem Verständnis der Auslöser?Sind die neu eingefügten Zeilen noch nicht begangen, während der trigger ausgeführt wird?

War es hilfreich?

Lösung

Trigger können nicht die geänderten Daten (Inserted oder Deleted) ändern sonst könnte man eine unendliche Rekursion erhalten, da die Änderungen den Auslöser wieder aufgerufen. Eine Option für den Auslöser wäre, die Transaktion rückgängig zu machen.

Edit: Der Grund dafür ist, dass der Standard für SQL ist, dass eingefügt und gelöschten Zeilen können nicht durch den Auslöser modifiziert werden. Der eigentliche Grund für ist, dass die Änderungen unendliche Rekursion verursachen könnten. Im allgemeinen Fall könnte diese Auswertung mehrere Trigger in einer für beide Seiten rekursive Kaskade beinhalten. ein System intelligent Mit entscheiden, ob solchen Aktualisierungen zu ermöglichen, ist rechnerisch unlösbar, im Wesentlichen einer Veränderung auf dem Halteproblem.

Die akzeptierte Lösung dieses Problems ist nicht, den Abzug zu ermöglichen, die sich ändernden Daten zu ändern, obwohl es Rollback der Transaktion kann.

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo after insert as
    begin
        delete inserted
         where isnumeric (SomeField) = 1
    end
go


Msg 286, Level 16, State 1, Procedure FooInsert, Line 5
The logical tables INSERTED and DELETED cannot be updated.

So etwas wird die Transaktion rückgängig zu machen.

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo for insert as
    if exists (
       select 1
         from inserted 
        where isnumeric (SomeField) = 1) begin
              rollback transaction
    end
go

insert Foo values (1, '1')

Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.

Andere Tipps

Sie können die Logik umgekehrt. Statt eine ungültige Zeile zu löschen, nachdem sie eingeführt wurde, schreiben Sie eine INSTEAD OF Trigger einfügen nur , wenn Sie die Zeile gültig überprüfen.

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  DECLARE @isnum TINYINT;

  SELECT @isnum = ISNUMERIC(somefield) FROM inserted;

  IF (@isnum = 1)
    INSERT INTO sometable SELECT * FROM inserted;
  ELSE
    RAISERROR('somefield must be numeric', 16, 1)
      WITH SETERROR;
END

Wenn Ihre Anwendung keine Fehler zu behandeln (wie Joel sagt der Fall in seiner App ist), dann RAISERROR nicht. So stellen Sie den Auslöser leise nicht zu tun, einen Einsatz, der nicht gültig ist.

Ich lief dies auf SQL Server Express 2005 und es funktioniert. Beachten Sie, dass INSTEAD OF Trigger nicht Ursache Rekursion, wenn Sie in derselben Tabelle einfügen, für die der Trigger definiert ist.

Hier ist meine modifizierte Version von Bills Code:

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  INSERT INTO sometable SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 1 FROM inserted;
  INSERT INTO sometableRejects SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 0 FROM inserted;
END

Auf diese Weise kann der Einsatz immer erfolgreich, und alle falschen Datensätze in Ihren sometableRejects bekommen geworfen, wo man sie später behandeln kann. Es ist wichtig, Ihre Spuck Tafel- nvarchar Felder für alles zu machen - nicht Ints, tinyints, etc. -. Denn wenn sie immer abgelehnt, es ist, weil die Daten nicht, was Sie erwartet, sein

Dies löst auch das Multiple-Rekordeinsatz Problem, das Auslöser verursacht Bills zum Scheitern verurteilt. Wenn Sie gleichzeitig zehn Datensätze einfügen (wie wenn Sie eine Select-Insert-in tun) und nur einer von ihnen ist falsch, Bills Trigger hätte sie alle als schlecht gekennzeichnet. Diese Griffe eine beliebige Anzahl von guten und schlechten Aufzeichnungen.

habe ich diesen Trick auf einem Data-Warehousing-Projekt, bei dem die Einführungs Anwendung hatte keine Ahnung, ob die Business-Logik jeder gut war, und wir haben die Business-Logik in Triggern statt. Wirklich böse für Leistung, aber wenn Sie nicht der Einsatz scheitern lassen kann, es funktioniert.

Ich glaube, Sie CHECK-Einschränkung verwenden - es ist genau das, was es erfunden wurde

.
ALTER TABLE someTable 
ADD CONSTRAINT someField_check CHECK (ISNUMERIC(someField) = 1) ;

Meine vorherige Antwort (auch direkt vielleicht ein bisschen zu viel des Guten):

Ich denke, der richtige Weg INSTEAD OF-Trigger zu verwenden, ist die falschen Daten nicht eingefügt zu verhindern (anstatt es post-factum Löschen)

UPDATE:. DELETE von einem Trigger funktioniert sowohl MSSql 7 und MSSql 2008

Ich bin kein relationaler Guru, noch eine SQL-Standards wonk. Allerdings - im Gegensatz zu der akzeptierten Antwort - MSSQL Angebote ganz gut mit sowohl r ecursive und verschachtelte Trigger Auswertung . Ich weiß nicht, über andere RDBMS-Systeme.

Die entsprechenden Optionen sind ‚rekursive Trigger‘ und ‚verschachtelte Trigger‘ . Verschachtelte Trigger werden auf 32 Ebenen beschränkt, und standardmäßig auf 1. Rekursive Trigger sind standardmäßig deaktiviert, und es gibt keine Rede von einer Grenze - aber ehrlich gesagt, ich habe sie nie eingeschaltet, so dass ich weiß nicht, was mit dem unvermeidlichen geschieht Paketüberfluss. Ich vermute, MSSQL würde nur Ihre spid töten (oder gibt es eine rekursive Grenze).

Natürlich, das zeigt nur, dass die akzeptierte Antwort hat den falschen Grund , nicht, dass es falsch ist. Jedoch Prior von Triggern STATTDESSEN I Rückruf auf INSERT Schreiben auslöst, dass die merrily gerade eingefügten Zeilen AKTUALISIEREN würde. Das alles hat gut funktioniert, und wie erwartet.

Ein schneller Test von deleteing die gerade eingefügten Zeile auch funktioniert:

 CREATE TABLE Test ( Id int IDENTITY(1,1), Column1 varchar(10) )
 GO

 CREATE TRIGGER trTest ON Test 
 FOR INSERT 
 AS
    SET NOCOUNT ON
    DELETE FROM Test WHERE Column1 = 'ABCDEF'
 GO

 INSERT INTO Test (Column1) VALUES ('ABCDEF')
 --SCOPE_IDENTITY() should be the same, but doesn't exist in SQL 7
 PRINT @@IDENTITY --Will print 1. Run it again, and it'll print 2, 3, etc.
 GO

 SELECT * FROM Test --No rows
 GO

Sie haben etwas anderes geht hier vor.

Von der CREATE TRIGGER Dokumentation:

  

gelöscht und eingefügt sind logische (konzeptionelle) Tabellen. Sie sind   strukturell ähnlich die Tabelle auf   die der Trigger definiert wird, das heißt,   der Tisch, auf dem die Benutzeraktion   versucht, und die alten Werte halten oder   neue Werte der Zeilen, die sein können,   durch die Aktion des Benutzers geändert. Zum   Beispiel, um alle Werte in dem abrufen   Deleted-Tabelle, Verwendung: SELECT * FROM deleted

So, dass zumindest gibt Ihnen die Möglichkeit, die neuen Daten zu sehen.

Ich kann nichts in der Dokumentation sehen, die, dass Sie nicht die eingefügten Daten sehen gibt an, wann obwohl die normale Tabelle abfragen ...

Ich fand diese Referenz:

create trigger myTrigger
on SomeTable
for insert 
as 
if (select count(*) 
    from SomeTable, inserted 
    where IsNumeric(SomeField) = 1) <> 0
/* Cancel the insert and print a message.*/
  begin
    rollback transaction 
    print "You can't do that!"  
  end  
/* Otherwise, allow it. */
else
  print "Added successfully."

Ich habe es nicht getestet, aber logisch sieht es aus wie sollte es dp, was Du nach ... anstatt den eingefügten Daten zu löschen, das Einsetzen vollständig verhindern, wodurch die Sie auffordert, nicht zu haben, um den Einsatz rückgängig zu machen. Es sollte eine bessere Leistung und sollte daher letztlich eine höhere Belastung mit mehr Leichtigkeit.

Edit: Natürlich gibt es ist die Möglichkeit, dass, wenn der Einsatz innerhalb einer ansonsten gültigen Transaktion passiert, dass die wole Transaktion rückgängig gemacht werden könnte, so dass Sie dieses Szenario berücksichtigen müßten und bestimmen wenn das Einfügen einer ungültigen Datenzeile würde eine völlig ungültige Transaktion darstellen ...

Ist es möglich, die INSERT gültig ist, sondern dass eine separate UPDATE wird danach durchgeführt, die ungültig ist, aber würde den Auslöser nicht ausgelöst?

Die Techniken, die oben skizzierte beschreiben Sie Ihre Optionen ziemlich gut. Aber was sehen die Nutzer? Ich kann mir nicht vorstellen, wie ein Grundkonflikt wie dies zwischen Ihnen und wer für die Software verantwortlich ist, kann nicht in Verwirrung und Antagonismus mit den Nutzern am Ende.

Ich mache alles würde ich könnte einen anderen Weg aus der Sackgasse zu finden - weil andere Menschen leicht eine Veränderung sehen könnten Sie als eskalierenden das Problem machen

.

EDIT:

Ich werde meinen ersten „Undelete“ Partitur und zugeben, die oben zu veröffentlichen, wenn diese Frage zum ersten Mal erschien. Ich gekniffen natürlich, als ich sah, dass es von Joel Spolsky war. Aber es sieht aus wie es irgendwo in der Nähe gelandet. Nicht die Stimmen, aber ich werde es auf der Platte setzen.

IME, Auslöser sind so selten die richtige Antwort für etwas anderes als feinkörniges Integritätsbedingungen außerhalb des Bereichs der Geschäftsregeln.

MS-SQL hat eine Einstellung, um zu verhindern, dass rekursive auslösen.Dies ist confirgured über die Option sp_configure gespeicherten proceedure, wo Sie sich wenden können, rekursive oder geschachtelte Trigger auf on oder off.

In diesem Fall wäre es möglich, wenn Sie schalten Sie rekursive Trigger zu Verknüpfung des Datensatzes aus der Tabelle eingefügt, die über den Primärschlüssel, und änderungen an dem Datensatz.

In dem speziellen Fall in Frage, es ist nicht wirklich ein problem, denn das Ergebnis ist zum löschen des Datensatzes, die nicht refire dieser bestimmten Auslöser, aber im Allgemeinen ist das ein Gültiger Ansatz.Wir implementiert optimistische Parallelität auf diese Weise.

Der code für den Auslöser, die verwendet werden könnten auf diese Weise wäre:

ALTER TRIGGER myTrigger
    ON someTable
    AFTER INSERT
AS BEGIN
DELETE FROM someTable
    INNER JOIN inserted on inserted.primarykey = someTable.primarykey
    WHERE ISNUMERIC(inserted.someField) = 1
END

Ihre „Trigger“ tut etwas, das ein „Trigger“ ist wohl nicht zu tun. Sie können einfach haben Ihr SQL Server-Agent run

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1

alle 1 Sekunde oder so. Während Sie gerade dabei sind, wie wäre es eine nette kleine SP Schreiben der Programmierung Leute aus Einfügen Fehler in Ihre Tabelle zu stoppen. Eine gute Sache über SP ist, dass die Parameter Typ sicher sind.

ich auf diese Frage gestolpert Einzelheiten über die Abfolge der Ereignisse während eines Einsatzes Aussage & Trigger suchen. Ich landete Codierung einige kurze Tests, um zu bestätigen, wie SQL 2016 (EXPRESS) verhält - und dachte, es wäre angebracht, wie es anderen helfen, für ähnliche Informationen suchen könnte zu teilen.

Basierend auf meinem Test ist es möglich, Daten aus der „eingefügt“ Tabelle auszuwählen und zu verwenden, dass die eingefügten Daten selbst zu aktualisieren. Und für mich von Interesse ist die eingefügten Daten zu anderen Abfragen nicht sichtbar, bis der Auslöser, an dem Punkt vervollständigt das Endergebnis sichtbar ist (zumindest gut ich konnte testen). Ich habe diesen Test nicht für rekursive Trigger usw. (ich würde erwarten, dass die verschachtelten Trigger volle Sichtbarkeit der eingefügten Daten in der Tabelle haben würden, aber das ist nur eine Vermutung).

Zum Beispiel - vorausgesetzt, wir haben die Tabelle „Tabelle“ mit einem ganzzahligen Feld „Feld“ und Primärschlüsselfeld „pk“ und dem folgenden Code in unserem Insert-Trigger:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
waitfor delay '00:00:15'

Wir legen Sie eine Zeile mit dem Wert 1 für „Feld“, dann wird die Zeile mit dem Wert 2. Außerdem am Ende - wenn ich ein neues Fenster in SSMS öffnen und versuchen:    select * from table where pk = @pk

wobei @pk der Primärschlüssel I ursprünglich eingeführt, die Abfrage leer sein wird, bis die 15 Sekunden ablaufen und werden dann den aktualisierten Wert zeigen (field = 2).

Ich war daran interessiert, welche Daten sichtbar zu anderen Anfragen während des Trigger ausgeführt wird (scheinbar keine neue Daten). Getestet habe mich mit einer zusätzlichen löschen als auch:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
delete from table where pk=@pk
waitfor delay '00:00:15'

Auch hier nahm der Einsatz 15sec auszuführen. Eine Abfrage in einer anderen Sitzung Ausführung zeigte keine neuen Daten - während oder nach der Ausführung des Einsatzes + Trigger (obwohl ich keine Identität erwarten würde sogar erhöhen, wenn keine Daten eingefügt zu sein scheint)

.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top