Le déclencheur «AFTER INSERT» de SQL Server ne voit pas la ligne qui vient d'être insérée

StackOverflow https://stackoverflow.com/questions/405288

Question

Considérez ce déclencheur:

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

J'ai une table, une table, et j'essaie d'empêcher les gens d'insérer de mauvais enregistrements. Pour les besoins de cette question, un mauvais enregistrement a un champ "someField". c'est tout numérique.

Bien sûr, la bonne façon de faire cela n’EST PAS d’utiliser un déclencheur, mais je ne contrôle pas le code source, mais uniquement la base de données SQL. Je ne peux donc pas vraiment empêcher l’insertion de la mauvaise ligne, mais je peux la supprimer tout de suite, ce qui est suffisant pour mes besoins.

Le déclencheur fonctionne, avec un problème ... lorsqu’il se déclenche, il ne semble jamais supprimer le mauvais enregistrement vient d’être inséré ... il supprime tous les anciens enregistrements défectueux, mais ne supprime pas le mauvais enregistrement qui vient d’être inséré . Donc, il y a souvent un mauvais disque qui flotte et qui n'est pas supprimé jusqu'à ce que quelqu'un d'autre vienne et fasse un autre INSERT.

Est-ce un problème dans ma compréhension des déclencheurs? Les nouvelles lignes insérées ne sont-elles pas encore validées pendant l'exécution du déclencheur?

Était-ce utile?

La solution

Les déclencheurs ne peuvent pas modifier les données modifiées ( Inséré ou Supprimé ), sinon vous pourriez obtenir une récursion infinie lorsque les modifications invoqueraient à nouveau le déclencheur. Une option serait que le déclencheur annule la transaction.

Modifier: La raison en est que la norme SQL est que les lignes insérées et supprimées ne peuvent pas être modifiées par le déclencheur. La raison sous-jacente est que les modifications pourraient provoquer une récursion infinie. Dans le cas général, cette évaluation peut impliquer plusieurs déclencheurs dans une cascade mutuellement récursive. Avoir un système qui décide intelligemment d’autoriser de telles mises à jour est une tâche ardue sur le plan des calculs, il s’agit essentiellement d’une variante du problème bloquant.

La solution acceptée à ce problème consiste à ne pas autoriser le déclencheur à modifier les données en cours de modification, bien qu'il puisse annuler la transaction.

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.

Quelque chose comme ça va annuler la transaction.

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.

Autres conseils

Vous pouvez inverser la logique. Au lieu de supprimer une ligne non valide après son insertion, écrivez un déclencheur INSTEAD OF pour insérer uniquement si vous vérifiez que la ligne est valide.

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 votre application ne veut pas gérer les erreurs (comme le dit Joel, c'est le cas dans son application), alors ne RAISERROR . Il suffit de faire en sorte que la gâchette ne pas faire un insert qui n'est pas valide.

Je l'ai exécuté sur SQL Server Express 2005 et cela fonctionne. Notez que les déclencheurs INSTEAD OF ne causent pas de récurrence si vous insérez dans la même table pour laquelle le déclencheur est défini.

Voici ma version modifiée du code 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

Cela permet à l'insertion de toujours réussir et tous les faux enregistrements sont jetés dans votre sometableRejects où vous pourrez les manipuler plus tard. Il est important de faire en sorte que votre table de rejets utilise les champs nvarchar pour tout, pas pour les ints, les tinyints, etc., car s'ils sont rejetés, c'est parce que les données ne sont pas ce que vous espériez.

Ceci résout également le problème d'insertion de plusieurs enregistrements, ce qui entraînera l'échec du déclencheur de Bill. Si vous insérez dix enregistrements simultanément (comme si vous faisiez un select-insert-into) et qu'un seul d'entre eux est faux, le déclencheur de Bill les aurait tous marqués comme étant mauvais. Cela gère n'importe quel nombre de bons et de mauvais enregistrements.

J'ai utilisé cette astuce sur un projet d'entreposage de données où l'application d'insertion ne savait pas si la logique métier était bonne, et nous avons plutôt utilisé la logique métier dans les déclencheurs. Vraiment méchant pour la performance, mais si vous ne pouvez pas laisser l'insertion échouer, cela fonctionne.

Je pense que vous pouvez utiliser la contrainte CHECK - c'est exactement ce pour quoi elle a été inventée.

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

Ma réponse précédente (aussi peut-être juste un peu exagéré):

Je pense qu'il est judicieux d'utiliser le déclencheur INSTEAD OF pour empêcher l'insertion de données erronées (plutôt que de les supprimer après le mémoire)

UPDATE: DELETE à partir d'un déclencheur fonctionne à la fois avec MSSql 7 et MSSql 2008.

Je ne suis ni un gourou relationnel, ni un expert en normes SQL. Cependant, contrairement à la réponse acceptée, MSSQL traite très bien les deux évaluation de déclencheurs ecursifs et imbriqués . Je ne connais pas les autres SGBDR.

Les options appropriées sont "déclencheurs récursifs" et "déclencheurs imbriqués" . Les déclencheurs imbriqués sont limités à 32 niveaux et par défaut à 1. Les déclencheurs récursifs sont désactivés par défaut, et on ne parle pas de limite - mais franchement, je ne les ai jamais activés, donc je ne sais pas ce qui se passe avec l'inévitable. débordement de pile. Je soupçonne que MSSQL tuerait simplement votre spid (ou qu'il existe une limite récursive).

Bien sûr, cela montre simplement que la réponse acceptée a la raison erronée, mais pas qu’elle est incorrecte. Cependant, avant les déclencheurs INSTEAD OF, je me souviens d’avoir écrit des déclencheurs ON INSERT qui mettraient joyeusement à jour les lignes qui viennent d’être insérées. Tout cela a bien fonctionné et comme prévu.

Un test rapide de suppression de la ligne qui vient d'être insérée fonctionne également:

 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

Vous avez autre chose à faire ici.

Dans la documentation sur CREATE TRIGGER :

  Les

supprimés et les insérés sont des tableaux logiques (conceptuels). Elles sont   structurellement similaire à la table sur   dont le déclencheur est défini, c'est-à-dire   la table sur laquelle l'action de l'utilisateur est   tenté, et maintenez les anciennes valeurs ou   nouvelles valeurs des lignes qui peuvent être   modifié par l'action de l'utilisateur. Pour   exemple, pour récupérer toutes les valeurs du   table supprimée, utilisez: SELECT * FROM supprimé

Cela vous donne au moins un moyen de voir les nouvelles données.

Je ne vois rien dans la documentation qui indique que vous ne verrez pas les données insérées lors de l'interrogation de la table normale ...

J'ai trouvé cette référence:

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

Je ne l'ai pas testé, mais logiquement, il semble qu'il faille dp ce que vous cherchez ... plutôt que de supprimer les données insérées, empêchez l'insertion complètement, vous évitant ainsi d'avoir à annuler l'insertion. Il devrait être plus performant et devrait donc pouvoir supporter plus facilement une charge plus élevée.

Éditer: Bien sûr, il y a le potentiel que si l'insertion se produisait à l'intérieur d'une transaction par ailleurs valide, la transaction wole pouvait être annulée, de sorte que vous deviez prendre en compte ce scénario et déterminer si l’insertion d’une ligne de données non valide constitue une transaction totalement invalide ...

Est-il possible que l'INSERT soit valide, mais qu'un UPDATE séparé soit effectué par la suite, il est invalide mais ne déclenche pas le déclencheur?

Les techniques décrites ci-dessus décrivent assez bien vos options. Mais que voient les utilisateurs? Je ne peux pas imaginer comment un conflit fondamental comme celui-ci entre vous et le responsable du logiciel ne peut aboutir à une confusion et à un antagonisme avec les utilisateurs.

Je ferais tout mon possible pour trouver un autre moyen de sortir de l'impasse - car d'autres personnes pourraient facilement voir que tout changement que vous apportez aggrave le problème.

EDIT:

Je marquerai mon premier " undelete " et admettre de poster ce qui précède lorsque cette question est apparue pour la première fois. Bien sûr, je me suis dégonflé quand j'ai vu que c'était de JOEL SPOLSKY. Mais on dirait qu'il a atterri quelque part près. Vous n'avez pas besoin de votes, mais je vais le noter.

IME, les déclencheurs sont si rarement la bonne solution pour des problèmes autres que des contraintes d’intégrité détaillées en dehors du domaine des règles commerciales.

MS-SQL a un paramètre pour empêcher le déclenchement récursif de déclencheurs. Ceci est configuré via la procédure sp_configure stockée, où vous pouvez activer ou désactiver les déclencheurs récursifs ou imbriqués.

Dans ce cas, il serait possible, si vous désactiviez les déclencheurs récursifs, de lier l'enregistrement de la table insérée via la clé primaire et d'apporter des modifications à l'enregistrement.

Dans le cas spécifique de la question, le problème ne se pose pas vraiment car le résultat est de supprimer l'enregistrement, ce qui ne déclenchera pas le déclenchement de ce déclencheur particulier, mais en général cela pourrait être une approche valide. Nous avons mis en place cette concurrence optimiste de cette façon.

Le code de votre déclencheur qui pourrait être utilisé de cette manière serait:

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

Votre " trigger " fait quelque chose qui "déclenche" n'est pas supposé faire. Vous pouvez simplement faire fonctionner votre agent serveur SQL Server

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1

toutes les 1 seconde environ. Pendant que vous y êtes, pourquoi ne pas écrire un joli petit SP pour empêcher les programmeurs d’insérer des erreurs dans votre tableau. Une des bonnes choses à propos des SP est que les paramètres sont dactylographiés.

Je suis tombé sur cette question en cherchant des détails sur la séquence d'événements au cours d'une instruction insert & amp; déclencheur. J'ai fini par coder de brefs tests pour confirmer le comportement de SQL 2016 (EXPRESS) - et j'ai pensé qu'il serait approprié de partager car cela pourrait aider d'autres personnes à rechercher des informations similaires.

Sur la base de mon test, il est possible de sélectionner des données à partir du " inséré " table et l'utiliser pour mettre à jour les données insérées. Et, ce qui est intéressant pour moi, les données insérées ne sont pas visibles par les autres requêtes jusqu'à ce que le déclencheur se termine, moment auquel le résultat final est visible (du moins, au mieux, comme j'ai pu le tester). Je n'ai pas testé cela pour les déclencheurs récursifs, etc. (je m'attendrais à ce que le déclencheur imbriqué ait une visibilité complète des données insérées dans la table, mais ce n'est qu'une supposition).

Par exemple, en supposant que nous ayons la table " table " avec un champ entier " champ " et champ clé primaire " pk " et le code suivant dans notre déclencheur d'insertion:

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

Nous insérons une ligne avec la valeur 1 pour "champ", puis la ligne se retrouvera avec la valeur 2. En outre, si j'ouvre une autre fenêtre dans SSMS et essayez:    sélectionnez * dans la table où pk = @pk

où @pk est la clé primaire que j'ai insérée à l'origine, la requête reste vide jusqu'à l'expiration du délai de 15 secondes et affiche ensuite la valeur mise à jour (champ = 2).

Je me suis intéressé aux données visibles par les autres requêtes pendant l'exécution du déclencheur (apparemment pas de nouvelles données). J'ai également testé avec une suppression ajoutée:

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'

Encore une fois, l'insertion a pris 15 secondes à exécuter. Une requête s’exécutant dans une session différente ne montrait aucune nouvelle donnée - pendant ou après l’exécution de l’insertion + déclencheur (bien que je suppose qu’une identité s’incrémenterait même si aucune donnée ne semble être insérée).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top