web-dev-qa-db-fra.com

Après avoir mis à jour les déclencheurs qui traitent de mises à jour de plusieurs lignes

Travaillant actuellement sur un projet d'audit de base de données basé sur des déclencheurs qui sont tirés sur la mise à jour sur des tables spécifiques. Les déclencheurs écrivent les changements dans une table; Les informations écrites sont les suivantes: Nom de la table, colonne mise à jour, horodatage, utilisateur, valeur ancienne et nouvelle valeur.

Les déclencheurs fonctionnent bien avec des mises à jour unique, mais lorsqu'il s'agit de mises à jour à plusieurs lignes, cela ne fonctionne pas.

Mon code est comme ceci:

IF (UPDATE(Priority))  
BEGIN
    SET @UpdatedColumn = 'Priority'
    INSERT INTO dbo.AuditTable
        ( [TableName] ,
          [Source] ,
          [RecordId] ,
          [User] ,
          [TimeStamp] ,
          [UpdatedColumn] ,
          [OldValue] ,
          [NewValue]
        )
    SELECT 
        N'BookingItem' , -- TableName - nvarchar(max)
        (SELECT CODE FROM TBL_LEG_SOURCE 
                     INNER JOIN INSERTED INS ON LEG_SOURCE_ID = INS.SourceId) ,
        INS.Id , -- RecordId - bigint
        (SELECT USERNAME FROM INSERTED 
                     INNER JOIN TBL_USER 
                     ON ModifiedById = USER_ID) , -- User - nvarchar(max)
        GETDATE() , -- TimeStamp - datetime
        @UpdatedColumn , -- UpdatedColumn - nvarchar(max)
        DEL.Priority , -- OldValue - nvarchar(max)
        INS.Priority  -- NewValue - nvarchar(max)
    FROM 
        INSERTED INS INNER JOIN DELETED DEL ON INS.Id = DEL.Id
    WHERE
        (
            (INS.Priority <> DEL.Priority)
            OR (INS.Priority IS NULL AND DEL.Priority IS NOT NULL)
            OR (INS.Priority IS NOT NULL AND DEL.Priority IS NULL)
        )
END

Message d'erreur:

MSG 512, niveau 16, état 1, procédure Mytrigger, ligne 818
La sous-requête a renvoyé plus d'une valeur. Ceci n'est pas autorisé lorsque la sous-requête suit =,! =, <, <=,>,> = Ou lorsque la sous-requête est utilisée comme une expression.

Toute suggestion sur la manière de corriger mon déclencheur afin de gérer des opérations à plusieurs rangées?

2
Hani El Mouallem

Les deux sous-requêtes dans la requête que vous montrent sont rejoignées à insérer sans faire un agrégat ou un sommet (1). Donc, les deux potentiellement renvoient plus d'une ligne. Au lieu de se joindre à la table insérée à nouveau, faites-vous référence directement à la colonne. Avec ce que la deuxième requête ressemblerait à ceci:

(SELECT U.USERNAME FROM TBL_USER U WHERE INS.ModifiedById = U.USER_ID)

Le changement à l'autre est similaire.

4
Sebastian Meine

Voici comment corriger les erreurs, en utilisant des jointures correctes (et si ce n'est pas "assez rapide" puis regardez votre indexation):

INSERT dbo.AuditTable
(
  [TableName],
  [Source],
  [RecordId],
  [User],
  [TimeStamp],
  [UpdatedColumn],
  [OldValue],
  [NewValue]
)
SELECT 
  N'BookingItem', -- TableName - nvarchar(max)
  ls.CODE,
  INS.Id, -- RecordId - bigint
  u.USERNAME,
  GETDATE(), -- TimeStamp - datetime
  @UpdatedColumn, -- UpdatedColumn - nvarchar(max)
  DEL.Priority, -- OldValue - nvarchar(max)
  INS.Priority  -- NewValue - nvarchar(max)
FROM 
  INSERTED AS INS 
INNER JOIN 
  DELETED AS DEL ON INS.Id = DEL.Id
INNER JOIN 
  dbo.TBL_LEG_SOURCE AS ls ON ls.LEG_SOURCE_ID = INS.SourceId
INNER JOIN
  dbo.TBL_USER AS u ON INS.ModifiedById = u.USER_ID
WHERE
(
  (INS.Priority <> DEL.Priority)
  OR (INS.Priority IS NULL AND DEL.Priority IS NOT NULL)
  OR (INS.Priority IS NOT NULL AND DEL.Priority IS NULL)
);

Cependant, je pense qu'il est assez stupide de gérer plus de 50 inserts différents de cette variété afin de capturer chaque changement de colonne. Pourquoi ne pas simplement créer une table avec des colonnes pour un nom de temps et de table (vous n'avez pas besoin de stocker le nom d'utilisateur car vous pouvez toujours regarder cela plus tard), puis chaque fois qu'il y a une mise à jour, stockez l'ancienne et la nouvelle version de la ligne? Vous pouvez même utiliser un SEQUENCE pour vous assurer que vous pouvez identifier l'ensemble des lignes modifiées ensemble (puisque l'horodatage peut ne pas être suffisamment unique pour le faire).

CREATE SEQUENCE dbo.AuditSequence
  AS INT START WITH 1 INCREMENT BY 1;

CREATE TABLE dbo.AuditData
(
  AuditSequenceID INT,
  TableName SYSNAME,
  [TimeStamp] DATETIME,
  RowState CHAR(1), -- e.g. 'B' = before, 'A' = after
  ... all your 50 columns, including ModifidById ...
);

Maintenant dans votre déclencheur:

CREATE TRIGGER dbo.MyTrigger
  ON dbo.BookingItem
  FOR UPDATE
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @as INT = NEXT VALUE FOR dbo.AuditSequence,
          @now DATETIME = CURRENT_TIMESTAMP;

  INSERT dbo.AuditData(AuditSequenceID, TableName, [TimeStamp], RowState,
    ... the rest of your 50 columns)
  SELECT @as, N'BookingItem', @now, 'B', * FROM deleted;

  INSERT dbo.AuditData(AuditSequenceID, TableName, [TimeStamp], RowState,
    ... the rest of your 50 columns)
  SELECT @as, N'BookingItem', @now, 'A', * FROM inserted;
END
GO

Maintenant, écrivez des requêtes compliquées contre cette structure d'audit beaucoup plus simple qui est inefficace et essayez de suivre exactement quelles colonnes ont changé et tout cela. Mieux vaut mieux payer ce prix lorsque vous examinez des données d'audit que de payer ce prix sur chaque opération de mise à jour.

9
Aaron Bertrand