Domanda

Considera questo trigger:

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

Ho una tabella, someTable e sto cercando di impedire alle persone di inserire record errati. Ai fini di questa domanda, un record negativo ha un campo "someField" questo è tutto numerico.

Ovviamente, il modo giusto per farlo NON è con un trigger, ma non controllo il codice sorgente ... solo il database SQL. Quindi non posso davvero impedire l'inserimento della riga errata, ma posso eliminarla immediatamente, il che è abbastanza buono per le mie esigenze.

Il trigger funziona, con un problema ... quando si attiva, sembra che non elimini mai il record errato appena inserito ... elimina qualsiasi record errato OLD, ma non elimina il record errato appena inserito . Quindi c'è spesso un brutto disco che gira intorno che non viene cancellato finché non arriva qualcun altro e fa un altro INSERT.

È un problema nella mia comprensione dei trigger? Le righe appena inserite non sono ancora impegnate mentre il trigger è in esecuzione?

È stato utile?

Soluzione

I trigger non possono modificare i dati modificati ( Inseriti o Deleted ) altrimenti si potrebbe ottenere una ricorsione infinita poiché le modifiche hanno richiamato nuovamente il trigger. Un'opzione sarebbe che il trigger ripristinasse la transazione.

Modifica: Il motivo è che lo standard per SQL è che le righe inserite e cancellate non possono essere modificate dal trigger. Il motivo alla base di ciò è che le modifiche potrebbero causare una ricorsione infinita. Nel caso generale, questa valutazione potrebbe comportare più trigger in una cascata reciprocamente ricorsiva. Avere un sistema in grado di decidere in modo intelligente se consentire tali aggiornamenti è irrisolvibile dal punto di vista computazionale, essenzialmente una variazione del problema di interruzione.

La soluzione accettata a questo non è quella di consentire al trigger di modificare i dati che cambiano, sebbene possa ripristinare la transazione.

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.

Qualcosa del genere eseguirà il rollback della transazione.

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.

Altri suggerimenti

È possibile invertire la logica. Invece di eliminare una riga non valida dopo che è stata inserita, scrivere un trigger INSTEAD OF per inserire solo se si verifica che la riga è valida.

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

Se la tua applicazione non vuole gestire errori (come dice Joel nella sua app), allora non RAISERROR . Basta fare in modo che il grilletto non esegua un inserimento non valido.

L'ho eseguito su SQL Server Express 2005 e funziona. Si noti che INSTEAD OF innesca non causa ricorsione se si inserisce nella stessa tabella per cui è definito il trigger.

Ecco la mia versione modificata del codice di Bill:

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

In questo modo l'inserimento ha sempre esito positivo e tutti i record fasulli vengono gettati nei tuoi sometableRejects dove puoi gestirli in un secondo momento. È importante che la tua tabella dei rifiuti utilizzi i campi nvarchar per tutto - non ints, tinyints, ecc. Perché se vengono rifiutati, è perché i dati non sono quelli che ti aspettavi di essere.

Questo risolve anche il problema dell'inserimento di più record, che causerà il fallimento del trigger di Bill. Se si inseriscono dieci record contemporaneamente (come se si fa un select-insert-into) e solo uno di questi è falso, il trigger di Bill li avrebbe contrassegnati come tutti cattivi. Questo gestisce qualsiasi numero di record buoni e cattivi.

Ho usato questo trucco su un progetto di data warehousing in cui l'applicazione di inserimento non aveva idea se la logica di business fosse buona, e invece abbiamo fatto la logica di business nei trigger. Davvero brutto per le prestazioni, ma se non riesci a far fallire l'inserto, funziona.

Penso che tu possa usare il vincolo CHECK - è esattamente quello per cui è stato inventato.

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

La mia risposta precedente (anche qui potrebbe essere un po 'eccessiva):

Penso che il modo giusto sia usare il trigger INSTEAD OF per impedire l'inserimento di dati errati (piuttosto che eliminarli dopo il factum)

AGGIORNAMENTO: DELETE da un trigger funziona sia su MSSql 7 che su MSSql 2008.

Non sono un guru relazionale, né un esperto di standard SQL. Tuttavia - contrariamente alla risposta accettata - MSSQL gestisce bene sia r valutazione del trigger ecursivo e nidificato . Non conosco altri RDBMS.

Le opzioni pertinenti sono "trigger ricorsivi" e "trigger nidificati" . I trigger nidificati sono limitati a 32 livelli e il valore predefinito è 1. I trigger ricorsivi sono disattivati ??per impostazione predefinita e non si parla di un limite - ma francamente non li ho mai attivati, quindi non so cosa succede con l'inevitabile overflow dello stack. Sospetto che MSSQL avrebbe semplicemente ucciso il tuo spid (o c'è un limite ricorsivo).

Ovviamente, ciò dimostra che la risposta accettata ha la ragione sbagliata, non che sia errata. Tuttavia, prima dei trigger INSTEAD OF, ricordo di aver scritto i trigger ON INSERT che AGGIORNANO allegramente le righe appena inserite. Tutto ha funzionato bene e come previsto.

Anche un rapido test di ELIMINAZIONE della riga appena inserita funziona:

 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

Qui sta succedendo qualcos'altro.

Dalla CREATE TRIGGER :

  

eliminato e inseriti sono tabelle logiche (concettuali). Loro sono   strutturalmente simile al tavolo   quale è definito il trigger, ovvero   la tabella su cui si trova l'azione dell'utente   tentato e mantenere i vecchi valori o   nuovi valori delle righe che possono essere   modificato dall'azione dell'utente. Per   esempio, per recuperare tutti i valori in   tabella eliminata, utilizzare: SELEZIONA * DA eliminata

In modo che almeno ti dia un modo di vedere i nuovi dati.

Non riesco a vedere nulla nei documenti che specifichi che non vedrai i dati inseriti durante l'interrogazione della tabella normale ...

Ho trovato questo riferimento:

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."

Non l'ho testato, ma logicamente sembra che dovrebbe dp quello che stai cercando ... piuttosto che eliminare i dati inseriti, impedire l'inserimento completo, quindi non è necessario annullare l'operazione. Dovrebbe funzionare meglio e, quindi, alla fine dovrebbe gestire un carico maggiore con maggiore facilità.

Modifica: Naturalmente, esiste il potenziale che se l'inserimento avvenisse all'interno di una transazione altrimenti valida, la transazione intera potrebbe essere annullata, quindi sarà necessario prendere in considerazione quello scenario e determinare se l'inserimento di una riga di dati non validi costituirebbe una transazione completamente non valida ...

È possibile che INSERT sia valido, ma in seguito viene eseguito un AGGIORNAMENTO separato che non è valido ma non attiva il trigger?

Le tecniche descritte sopra descrivono abbastanza bene le tue opzioni. Ma cosa vedono gli utenti? Non riesco a immaginare come un conflitto di base come questo tra te e chiunque sia responsabile del software non possa finire in confusione e antagonismo con gli utenti.

Farei tutto il possibile per trovare un'altra via d'uscita dall'impasse - perché altre persone potrebbero facilmente vedere qualsiasi cambiamento apportato come un aumento del problema.

EDIT:

Segnerò il mio primo " undelete " e ammettere di pubblicare quanto sopra quando questa domanda è apparsa per la prima volta. Ovviamente mi sono arrabbiato quando ho visto che era di JOEL SPOLSKY. Ma sembra che sia atterrato da qualche parte vicino. Non ho bisogno di voti, ma lo inserirò nel registro.

IME, i trigger sono così raramente la risposta giusta per qualcosa di diverso dai vincoli di integrità a grana fine al di fuori del regno delle regole aziendali.

MS-SQL ha un'impostazione per impedire l'attivazione del trigger ricorsivo. Questo viene confermato tramite la procedura memorizzata sp_configure, dove è possibile attivare o disattivare i trigger ricorsivi o nidificati.

In questo caso, sarebbe possibile, se si disattivano i trigger ricorsivi, collegare il record dalla tabella inserita tramite la chiave primaria e apportare modifiche al record.

Nel caso specifico della domanda, non è proprio un problema, perché il risultato è eliminare il record, che non rifiuta questo particolare trigger, ma in generale potrebbe essere un approccio valido. Abbiamo implementato la concorrenza ottimistica in questo modo.

Il codice per il tuo trigger che potrebbe essere utilizzato in questo modo sarebbe:

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

Il tuo " trigger " sta facendo qualcosa che un "innesco" non si suppone che stia facendo. Puoi semplicemente far funzionare il tuo Sql Server Agent

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1

ogni 1 secondo circa. Mentre ci sei, che ne dici di scrivere un bel piccolo SP per impedire alla gente di programmazione di inserire errori nella tua tabella. Una cosa positiva degli SP è che i parametri sono sicuri.

Mi sono imbattuto in questa domanda alla ricerca di dettagli sulla sequenza di eventi durante un'istruzione insert & amp; grilletto. Ho finito per codificare alcuni brevi test per confermare il comportamento di SQL 2016 (EXPRESS) e ho pensato che sarebbe stato opportuno condividerli perché potrebbe aiutare gli altri a cercare informazioni simili.

Sulla base del mio test, è possibile selezionare i dati da " inserito " tabella e utilizzarlo per aggiornare i dati inseriti stessi. E, per me interessante, i dati inseriti non sono visibili ad altre query fino a quando il trigger non completa il momento in cui il risultato finale è visibile (almeno meglio che potrei testare). Non ho testato questo per trigger ricorsivi, ecc. (Mi aspetto che il trigger nidificato abbia piena visibilità dei dati inseriti nella tabella, ma è solo una supposizione).

Ad esempio - supponendo che abbiamo la tabella " table " con un campo intero "campo" e campo chiave primaria " pk " e il seguente codice nel nostro trigger di inserimento:

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

Inseriamo una riga con il valore 1 per " campo " ;, quindi la riga finirà con il valore 2. Inoltre, se apro un'altra finestra in SSMS e provo:    seleziona * dalla tabella dove pk = @pk

dove @pk è la chiave primaria che ho inserito originariamente, la query sarà vuota fino alla scadenza dei 15 secondi e quindi mostrerà il valore aggiornato (campo = 2).

Ero interessato a quali dati sono visibili ad altre query mentre il trigger è in esecuzione (apparentemente nessun nuovo dato). Ho provato anche con una cancellazione aggiunta:

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'

Ancora una volta, l'inserimento ha richiesto 15 secondi per l'esecuzione. Una query in esecuzione in una sessione diversa non ha mostrato nuovi dati - durante o dopo l'esecuzione del trigger insert + (anche se mi aspetterei che qualsiasi identità aumenterebbe anche se nessun dato sembra essere inserito).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top