web-dev-qa-db-fra.com

comment mettre à jour la colonne sur la même ligne avec déclencheur sans récursivité?

SQL Server 2012.

J'ai besoin de mettre à jour la colonne [LastUpdated] avec la date et l'heure actuelles chaque fois qu'un enregistrement change dans ma table. Actuellement, j'ai:

CREATE TRIGGER Trig_LastUpdated ON Contact AFTER UPDATE
AS
SET NOCOUNT ON

UPDATE ct
SET LastUpdated = GETDATE()
FROM Contact ct
INNER JOIN Inserted i
    ON ct.IDContact = i.IDContact

Mais c'est récursif et je ne veux pas cela, cela provoque des blocages et autres bizarreries. Je ne peux pas désactiver globalement les déclencheurs récursifs. Je vois que les déclencheurs INSTEAD OF ne sont pas récursifs, mais si je le fais, dois-je vérifier toutes les autres colonnes de l'inséré pour voir s'il a été mis à jour ou SQL Server s'en occupera-t-il pour moi? Quelle est la meilleure façon de procéder?

5
Zaphodb2002

Comme @ ypercubeᵀᴹ l'a souligné dans les commentaires, Comment ajouter une colonne "dernière mise à jour" dans une table SQL Server 2008 R2? a la réponse. Vérifier si la colonne a été mise à jour saute simplement la logique et fonctionne comme je m'y attendais. Lorsque l'utilisateur met à jour la colonne manuellement, il prend sa mise à jour et ne provoque pas de récursivité lorsqu'il ne le fait pas. Pour une raison quelconque, je pensais que cela arrêterait complètement la mise à jour, mais ce n'est évidemment pas le cas.

1
Zaphodb2002

Étant donné que vous ne pouvez pas désactiver les déclencheurs récursifs, les meilleures options suivantes sont les suivantes:

  1. Demandez au déclencheur de détecter le nombre de niveaux de profondeur qu'il utilise TRIGGER_NESTLEVEL . Utilisez-le au début du déclencheur pour simplement quitter s'il ne s'agit pas de la première exécution du déclencheur dans la pile. Quelque chose dans le sens de:

    IF (TRIGGER_NESTLEVEL() > 1)
    BEGIN
      -- Uncomment the following PRINT line for debugging
      -- PRINT 'Exiting from recursive call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      RETURN;
    END;
    

    Cela nécessitera un peu de test pour voir comment il est affecté par l'insertion initiale effectuée par un autre déclencheur (au cas où cela deviendrait un problème, mais ce ne serait pas le cas). Si cela ne fonctionne pas comme prévu lorsqu'il est appelé par un autre déclencheur, essayez de définir certains des paramètres de cette fonction. Veuillez consulter la documentation (liée ci-dessus) pour plus de détails.

  2. Définissez un indicateur dans les "informations de contexte" basées sur la session à l'aide de SET CONTEXT_INFO . Les informations de contexte sont une valeur VARBINARY(128) qui existe au niveau de la session et conserve sa valeur jusqu'à ce qu'elle soit remplacée ou jusqu'à la fin de la session. La valeur peut être récupérée en utilisant la fonction CONTEXT_INFO ou en sélectionnant la colonne context_info À partir de l'un des DMV suivants: sys.dm_exec_requests et sys.dm_exec_sessions .

    Vous pouvez placer ce qui suit au début du déclencheur:

    IF (CONTEXT_INFO() = 0x01)
    BEGIN
      -- Uncomment the following PRINT line for debugging
      --PRINT 'Exiting from recursive call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      RETURN;
    END;
    ELSE
    BEGIN
      -- Uncomment the following PRINT line for debugging
      --PRINT 'Initial call to: ' + ISNULL(OBJECT_NAME(@@PROCID), '');
      SET CONTEXT_INFO 0x01;
    END;
    

    Cette option ne fonctionne pas si bien si vous utilisez déjà les informations contextuelles pour une autre raison. Mais, toute personne utilisant SQL Server 2016 peut utiliser SESSION_CONTEXT , qui est un nouvel ensemble de paires clé-valeur basées sur la session.

L'une ou l'autre de ces méthodes est plus fiable que l'utilisation de IF NOT UPDATE(LastUpdated) car la fonction UPDATE(column_name) ne peut vous dire que si la colonne était dans la clause SET ou non. Il ne peut pas vous dire si la valeur a changé ou si elle est passée à la valeur "courante" GETDATE() que vous attendez/voulez. Cela signifie que toutes les instructions suivantes contournent l'effet souhaité du déclencheur (c'est-à-dire en s'assurant que la colonne LastUpdated a la date et l'heure réelles de la modification):

UPDATE ct
SET    ct.LastUpdated = ct.LastUpdated
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = '1900-04-01`
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = 1
FROM   Contact ct
WHERE  ...

UPDATE ct
SET    ct.LastUpdated = GETDATE() + 90
FROM   Contact ct
WHERE  ...

La méthode la plus sûre consiste probablement à utiliser TRIGGER_NESTLEVEL() (option 1) et à transmettre les paramètres pour vérifier uniquement ce déclencheur particulier, de sorte que le fait d'être appelé en raison d'un INSERT d'un autre déclencheur ne l'affecte pas négativement. :

TRIGGER_NESTLEVEL( @@PROCID , 'AFTER' , 'DML' )
3
Solomon Rutzky