Pregunta

Considere este disparador:

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

Tengo una tabla, alguna tabla, y estoy tratando de evitar que las personas inserten registros incorrectos. Para los fines de esta pregunta, un registro incorrecto tiene un campo " someField " eso es todo numérico.

Por supuesto, la forma correcta de hacerlo NO es con un activador, pero no controlo el código fuente ... solo la base de datos SQL. Así que realmente no puedo evitar la inserción de la fila incorrecta, pero puedo eliminarla de inmediato, lo cual es lo suficientemente bueno para mis necesidades.

El desencadenador funciona, con un problema ... cuando se dispara, nunca parece eliminar el registro incorrecto que se acaba de insertar ... elimina cualquier registro incorrecto OLD, pero no elimina el registro incorrecto que se acaba de insertar . Por lo tanto, a menudo hay un registro malo que no se elimina hasta que alguien más aparece y hace otro INSERT.

¿Es esto un problema en mi comprensión de los desencadenantes? ¿Las filas recién insertadas aún no se han confirmado mientras se ejecuta el activador?

¿Fue útil?

Solución

Los disparadores no pueden modificar los datos modificados ( Inserta o Deleted ), de lo contrario, podría obtener una recursión infinita ya que los cambios invocaron el disparador nuevamente. Una opción sería que el activador deshaga la transacción.

Editar: El motivo de esto es que el estándar para SQL es que el disparador no puede modificar las filas insertadas y eliminadas. La razón subyacente de esto es que las modificaciones podrían causar una recursión infinita. En el caso general, esta evaluación podría involucrar múltiples desencadenantes en una cascada recursiva mutuamente. Tener un sistema que decida inteligentemente si permitir tales actualizaciones es intratable computacionalmente, esencialmente una variación en el problema de detención.

La solución aceptada para esto no es permitir que el activador altere los datos cambiantes, aunque puede revertir la transacción.

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.

Algo como esto hará retroceder la transacción.

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.

Otros consejos

Puedes revertir la lógica. En lugar de eliminar una fila no válida después de que se haya insertado, escriba un desencadenador INSTEAD OF para insertar solamente si verifica que la fila sea válida.

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

Si su aplicación no quiere manejar los errores (como dice Joel en su aplicación), no haga RAISERROR . Simplemente haga que el activador silenciosamente no haga una inserción que no sea válida.

Ejecuté esto en SQL Server Express 2005 y funciona. Tenga en cuenta que los desencadenadores INSTEAD OF no causan recursión si inserta en la misma tabla para la que se ha definido el desencadenante.

Aquí está mi versión modificada del código de 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

Esto permite que la inserción siempre sea exitosa, y cualquier registro falso se lance en sus SometableRejects donde puede manejarlos más tarde. Es importante hacer que su tabla de rechazos use los campos nvarchar para todo, no ints, minúsculos, etc., porque si se rechazan, es porque los datos no son lo que esperaba que fueran.

Esto también resuelve el problema de inserción de registros múltiples, lo que hará que el disparo de Bill falle. Si inserta diez registros simultáneamente (como si hiciera una inserción de selección) y solo uno de ellos es falso, el activador de Bill los habría marcado como malos. Esto maneja cualquier número de registros buenos y malos.

Utilicé este truco en un proyecto de almacenamiento de datos donde la aplicación de inserción no tenía idea de si la lógica de negocios era buena, e hicimos la lógica de negocios en los activadores. Realmente desagradable para el rendimiento, pero si no puedes dejar que la inserción falle, funciona.

Creo que puedes usar la restricción CHECK - es exactamente para lo que fue inventado.

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

Mi respuesta anterior (también a la derecha puede ser un poco exagerada):

Creo que la forma correcta es utilizar el desencadenador INSTEAD OF para evitar que se inserten datos incorrectos (en lugar de eliminarlos post-factum)

ACTUALIZACIÓN: ELIMINAR de un activador funciona tanto en MSSql 7 como en MSSql 2008.

No soy un gurú relacional, ni un estándar de SQL no funciona. Sin embargo, al contrario de la respuesta aceptada, MSSQL funciona bien con r Evaluación de disparo ecursiva y anidada . No sé de otros RDBMS.

Las opciones relevantes son 'disparadores recursivos' y 'disparadores anidados' . Los activadores anidados están limitados a 32 niveles y el valor predeterminado es 1. Los activadores recursivos están desactivados de manera predeterminada, y no se habla de un límite, pero francamente, nunca los he activado, por lo que no sé qué pasa con lo inevitable desbordamiento de pila. Sospecho que MSSQL simplemente matará tu spid (o hay un límite recursivo).

Por supuesto, eso solo demuestra que la respuesta aceptada tiene el motivo incorrecto, no que sea incorrecta. Sin embargo, antes de los activadores INSTEAD OF, recuerdo haber escrito activadores ON INSERT que actualizarían alegremente las filas recién insertadas. Todo esto funcionó bien, y como se esperaba.

Una prueba rápida de BORRAR la fila recién insertada también funciona:

 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

Tienes algo más aquí.

De la CREATE TRIGGER :

  

eliminado y insertado son tablas lógicas (conceptuales). Son   estructuralmente similar a la tabla en   cual el disparador se define, es decir,   La tabla en la que se encuentra la acción del usuario.   intentado, y mantener los valores antiguos o   Nuevos valores de las filas que pueden ser.   Cambiado por la acción del usuario. por   ejemplo, para recuperar todos los valores en el   tabla eliminada, use: SELECT * FROM eliminado

De modo que al menos te ofrezca una forma de ver los nuevos datos.

No puedo ver nada en los documentos que especifique que no verás los datos insertados al consultar la tabla normal ...

Encontré esta referencia:

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

No lo he probado, pero lógicamente parece que debería dp lo que estás buscando ... en lugar de eliminar los datos insertados, evita la inserción por completo, por lo que no es necesario que tengas que deshacer la inserción. Debería funcionar mejor y, por lo tanto, debería manejar una carga más alta con mayor facilidad.

Editar: Por supuesto, existe la posibilidad de que si la inserción se realizó dentro de una transacción válida, la transacción wole podría revertirse, por lo que debería tener en cuenta ese escenario y determinar si la inserción de una fila de datos no válida constituiría una transacción completamente no válida ...

¿Es posible que INSERT sea válido, pero que después se realice una ACTUALIZACIÓN por separado que no sea válida pero que no dispare el gatillo?

Las técnicas descritas anteriormente describen sus opciones bastante bien. Pero, ¿qué están viendo los usuarios? No puedo imaginar cómo un conflicto básico como este entre usted y quien sea responsable del software no puede terminar en confusión y antagonismo con los usuarios.

Haría todo lo que pudiera para encontrar alguna otra forma de salir del impasse, porque otras personas podrían ver fácilmente cualquier cambio que hagas a medida que se intensifica el problema.

EDITAR:

Puntuaré mi primer " Recuperar " y admita publicar lo anterior cuando apareció esta pregunta por primera vez. Por supuesto, me acobardé cuando vi que era de JOEL SPOLSKY. Pero parece que aterrizó en algún lugar cerca. No necesito votos, pero lo pondré en el registro.

IME, los desencadenantes son tan raramente la respuesta correcta para cualquier otra cosa que no sean restricciones de integridad precisas fuera del ámbito de las reglas comerciales.

MS-SQL tiene una configuración para evitar el disparo de disparos recursivos. Esto se confirma a través del procedimiento almacenado sp_configure, donde puede activar o desactivar los desencadenantes recursivos o anidados.

En este caso, sería posible, si desactiva los desencadenantes recursivos para vincular el registro de la tabla insertada a través de la clave principal y hacer cambios en el registro.

En el caso específico de la pregunta, no es realmente un problema, porque el resultado es eliminar el registro, que no generará este desencadenante en particular, pero en general podría ser un enfoque válido. Implementamos una concurrencia optimista de esta manera.

El código para su activador que podría usarse de esta manera sería:

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

Tu " disparador " está haciendo algo que un " disparador " No se supone que esté haciendo. Puedes simplemente ejecutar tu Sql Server Agent

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1

cada 1 segundo más o menos. Mientras estás en eso, ¿qué tal si escribes un pequeño SP agradable para evitar que la gente de programación inserte errores en tu tabla? Una cosa buena acerca de los SP es que los parámetros son de tipo seguro.

Me encontré con esta pregunta buscando detalles sobre la secuencia de eventos durante una declaración de inserción & amp; desencadenar. Terminé codificando algunas pruebas breves para confirmar cómo se comporta SQL 2016 (EXPRESS), y pensé que sería apropiado compartir, ya que podría ayudar a otros a buscar información similar.

Según mi prueba, es posible seleccionar datos del " insertado " tabla y utilizar eso para actualizar los datos insertados en sí. Y, de interés para mí, los datos insertados no son visibles para otras consultas hasta que se completa el desencadenante, momento en el que el resultado final es visible (al menos, lo mejor que pude probar). No probé esto para los desencadenantes recursivos, etc. (Espero que el desencadenador anidado tenga una visibilidad completa de los datos insertados en la tabla, pero eso es solo una suposición).

Por ejemplo, suponiendo que tenemos la tabla " tabla " con un campo entero " campo " y el campo de clave principal " pk " y el siguiente código en nuestro activador de inserción:

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

Insertamos una fila con el valor 1 para " campo " ;, luego la fila terminará con el valor 2. Además, si abro otra ventana en SSMS e intento:    seleccione * de la tabla donde pk = @pk

donde @pk es la clave principal que inserté originalmente, la consulta estará vacía hasta que caduquen los 15 segundos y luego mostrará el valor actualizado (campo = 2).

Me interesaba saber qué datos pueden ver otras consultas mientras se ejecuta el activador (aparentemente no hay datos nuevos). También probé con una eliminación agregada:

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'

De nuevo, la inserción tardó 15 segundos en ejecutarse. Una consulta que se ejecutó en una sesión diferente no mostró datos nuevos, durante o después de la ejecución del disparador de inserción + (aunque espero que cualquier identidad se incremente incluso si no se insertan datos).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top