Question

Je l'ai déjà fait quelque part, j'en suis sûr!

J'ai une table SQL Server 2000 dont j'ai besoin pour consigner les modifications apportées aux champs dans les mises à jour et les insérer dans une deuxième table de journalisation. Voici une version simplifiée de la structure que j'utilise:

MainTable
ID varchar(10) PRIMARY KEY
DESCRIPTION varchar(50)

LogTable
OLDID varchar(10)
NEWID varchar(10)

Pour tout autre domaine, quelque chose comme ceci fonctionnerait très bien:

Select i.DESCRIPTION As New, d.DESCRIPTION As Old 
From Inserted i
LEFT JOIN Deleted d On i.ID=d.ID

... Mais évidemment, la jointure échouerait si l'ID était modifié.

Je ne peux pas modifier les tables de la même manière, le seul pouvoir que j’ai dans cette base de données est de créer un déclencheur.

Sinon, y a-t-il quelqu'un qui peut m'apprendre le voyage dans le temps et je vais revenir dans le passé et me demander à l'époque comment j'ai fait cela? Cheers:)

Modifier:

Je pense que je dois clarifier quelques points ici. Ce n'est pas vraiment ma base de données , c'est un système préexistant sur lequel je n'ai presque aucun contrôle, mis à part l'écriture de ce déclencheur.

Ma question est de savoir comment puis-je récupérer l'ancienne clé primaire si cette clé primaire a été modifiée. Je n'ai pas besoin de me faire dire que je ne devrais pas changer la clé primaire, ni rechercher des clés étrangères, etc. Ce n'est pas mon problème:)

Était-ce utile?

La solution

Est-il possible de supposer que les tables INSERTED et DELETED qui vous sont présentées dans un déclencheur ont la garantie d'être dans le même ordre?

Autres conseils

DECLARE @OldKey int, @NewKey int;

SELECT @Oldkey = [ID] FROM DELETED;
SELECT @NewKey = [ID] FROM INSERTED;

Ceci ne fonctionne que si vous avez une seule ligne. Sinon, vous ne disposez pas de "ancre". pour relier les anciennes et les nouvelles lignes. Vérifiez donc dans votre déclencheur pour > 1 dans INSERTED.

Je ne pense pas que ce soit possible. Imaginez si vous avez 4 lignes dans la table:

1  Val1
2  Val2
3  Val3
4  Val4

Publiez maintenant la mise à jour suivante:

UPDATE MainTable SET
ID = CASE ID WHEN 1 THEN 2 WHEN 2 THEN 1 ELSE ID END
Description = CASE ID WHEN 3 THEN 'Val4' WHEN 4 THEN 'Val3' ELSE Description END

Maintenant, comment allez-vous faire la distinction entre ce qui est arrivé aux lignes 1 & amp; 2 et ce qui est arrivé aux rangées 3 & amp; 4. Et plus important encore, pouvez-vous décrire ce qui est différent entre eux? Tout ce qui vous dit quelles colonnes ont été mises à jour ne vous aidera pas.

S'il est possible dans ce cas qu'il y ait une clé supplémentaire sur la table (par exemple, Description est unique) et que vos règles de mise à jour le permettent, vous pouvez écrire le déclencheur pour empêcher les mises à jour simultanées des deux clés, puis vous pouvez utiliser la valeur la clé n'a pas été mise à jour pour corréler les deux tables.

Si vous devez gérer des insertions / mises à jour sur plusieurs lignes et qu'aucune autre clé n'est garantie pour ne pas changer, la seule façon de procéder consiste à utiliser un déclencheur INSTEAD OF. Par exemple, dans le déclencheur, vous pouvez diviser la commande originale insert / update en une commande par ligne, en récupérant chaque ancien ID avant d'insérer / update.

Dans les déclencheurs de SQL Server, vous avez accès à deux tables: supprimées et insérées. Les deux ont déjà été mentionnés. Voici comment ils fonctionnent en fonction de l’action déclenchée par la gâchette:

INSERT OPERATION

  • supprimé - non utilisé
  • inséré - contient les nouvelles lignes ajoutées à la table

SUPPRIMER L'OPÉRATION

  • supprimé - contient les lignes supprimées de la table
  • inséré - non utilisé

OPÉRATION DE MISE À JOUR

  • supprimé - contient les lignes telles qu'elles existaient avant l'opération UPDATE
  • inséré - contient les lignes telles qu'elles existeraient après l'opération UPDATE

Celles-ci fonctionnent à tous égards comme des tables. Par conséquent, il est tout à fait possible d’utiliser une opération basée sur les lignes du type suivant (l’opération n’existe que dans la table d’audit, de même que DateChanged):

INSERT INTO MyAuditTable
(ID, FirstColumn, SecondColumn, ThirdColumn, Operation, DateChanged)
VALUES
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-Before', GETDATE()
FROM deleted
UNION ALL
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-After', GETDATE()
FROM inserted

---- nouveau ---- Ajoutez une colonne d'identité à la table que l'application ne peut pas modifier. Vous pouvez ensuite utiliser cette nouvelle colonne pour joindre les tables insérées aux tables supprimées dans le déclencheur:

ALTER TABLE YourTableName ADD
    PrivateID int NOT NULL IDENTITY (1, 1)
GO

---- vieux ---- Ne jamais mettre à jour / modifier les valeurs clés. Comment pouvez-vous faire cela et réparer toutes vos clés étrangères?

Je ne recommanderais jamais d'utiliser un déclencheur qui ne peut pas gérer un ensemble de lignes.

Si vous devez modifier la clé, insérez une nouvelle ligne avec la nouvelle clé et les valeurs appropriées, utilisez SCOPE_IDENTITY () si tel est votre rôle. Supprimer l'ancienne ligne. Connectez-vous à l'ancienne ligne pour indiquer qu'elle a été remplacée par la clé de la nouvelle ligne. J'espère qu'il n'y a pas de clé étrangère sur la clé modifiée dans votre journal ...

Vous pouvez créer une nouvelle colonne d'identité sur la table MainTable (nommée par exemple, correlationid) et mettre en corrélation les tables insérées et supprimées à l'aide de cette colonne. Cette nouvelle colonne doit être transparente pour le code existant.

INSERT INTO LOG(OLDID, NEWID)
SELECT deleted.id AS OLDID, inserted.id AS NEWID
FROM inserted 
INNER JOIN deleted 
    ON inserted.correlationid = deleted.correlationid

Faites attention, vous pouvez insérer des enregistrements en double dans la table du journal.

Bien sûr, personne ne devrait changer la clé primaire sur la table - mais c’est précisément ce que les déclencheurs sont censés être (en partie), c’est d’empêcher les gens de faire des choses qu’ils ne devraient pas faire. C’est une tâche triviale sous Oracle ou MySQL d’écrire un déclencheur qui intercepte les modifications apportées aux clés primaires et les arrête, mais pas du tout facile dans SQL Server.

Ce que vous aimeriez bien sûr pouvoir faire serait de simplement faire quelque chose comme ceci:

if exists
  (
  select *
    from inserted changed
           join deleted old
   where changed.rowID = old.rowID
     and changed.id != old.id
  )
... [roll it all back]

C’est pourquoi les gens recherchent l’équivalent de ROWID dans SQL Server. Eh bien, SQL Server ne l’a pas; vous devez donc proposer une autre approche.

Une version rapide, mais malheureusement non sécurisée, consiste à écrire un déclencheur à la place de update qui cherche à savoir si l'une des lignes insérées possède une clé primaire introuvable dans la table mise à jour ou inversement. Cela attraperait la plupart, mais pas toutes, des erreurs:

if exists
  (
  select *
    from inserted lost
           left join updated match
             on match.id = lost.id
   where match.id is null
  union
  select *
    from deleted new
           left join inserted match
             on match.id = new.id
    where match.id is null
  )
  -- roll it all back

Mais cela ne capture toujours pas une mise à jour comme ...

update myTable
   set id = case
              when id = 1 then 2 
              when id = 2 then 1
              else id
              end

Maintenant, j'ai essayé de faire l'hypothèse que les tables insérées et supprimées sont ordonnées de manière à ce que le fait de parcourir simultanément les tables insérées et supprimées vous donne des lignes correctement appariées. Et cela semble fonctionner. En fait, vous transformez le déclencheur en équivalent des déclencheurs for-each-row disponibles dans Oracle et obligatoires dans MySQL ... mais j'imagine que les performances seront mauvaises sur les mises à jour massives car il ne s'agit pas d'un comportement natif pour SQL Server. En outre, cela dépend de l’hypothèse que je ne trouve aucun document documenté et que j’ai donc du mal à en dépendre. Mais le code structuré de cette façon, APPEARS, fonctionne correctement sur mon installation de SQL Server 2008 R2. Le script à la fin de cet article met en évidence à la fois le comportement de la solution rapide, mais non bombe, et celui de la deuxième solution, pseudo-Oracle.

Si quelqu'un pouvait m'indiquer un endroit où mon hypothèse est documentée et garantie par Microsoft, je serais très reconnaissant ...

begin try
  drop table kpTest;
end try
begin catch
end catch
go

create table kpTest( id int primary key, name nvarchar(10) )
go

begin try
  drop trigger kpTest_ioU;
end try
begin catch
end catch
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  if exists
    (
    select *
      from inserted lost
             left join deleted match
               on match.id = lost.id
     where match.id is null
    union
    select *
      from deleted new
             left join inserted match
               on match.id = new.id
      where match.id is null
    )
      raisError( 'Changed primary key', 16, 1 )
  else
    update kpTest
       set name = i.name
      from kpTest
             join inserted i
               on i.id = kpTest.id
    ;
end
go

insert into kpTest( id, name ) values( 0, 'zero' );
insert into kpTest( id, name ) values( 1, 'one' );
insert into kpTest( id, name ) values( 2, 'two' );
insert into kpTest( id, name ) values( 3, 'three' );

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This throws an error, appropriately
update kpTest set id = 5, name = 'FIVE' where id = 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This allows the change, inappropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = UPPER( name )
go

select * from kpTest

/*
0   ZERO
1   TWO   -- WRONG WRONG WRONG
2   ONE   -- WRONG WRONG WRONG
3   THREE
*/

-- Put it back
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

drop trigger kpTest_ioU
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  declare newIDs cursor for select id, name from inserted;
  declare oldIDs cursor for select id from deleted;
  declare @thisOldID int;
  declare @thisNewID int;
  declare @thisNewName nvarchar(10);
  declare @errorFound int;
  set @errorFound = 0;
  open newIDs;
  open oldIDs;
  fetch newIDs into @thisNewID, @thisNewName;
  fetch oldIDs into @thisOldID;
  while @@FETCH_STATUS = 0 and @errorFound = 0
    begin
      if @thisNewID != @thisOldID
        begin
          set @errorFound = 1;
          close newIDs;
          deallocate newIDs;
          close oldIDs;
          deallocate oldIDs;
          raisError( 'Primary key changed', 16, 1 );
        end
      else
        begin
          update kpTest
             set name = @thisNewName
           where id = @thisNewID
          ;
          fetch newIDs into @thisNewID, @thisNewName;
          fetch oldIDs into @thisOldID;
        end
    end;
  if @errorFound = 0
    begin
      close newIDs;
      deallocate newIDs;
      close oldIDs;
      deallocate oldIDs;
    end
end
go

-- Succeeds, appropriately
update kpTest
   set name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

-- Succeeds, appropriately
update kpTest
   set name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/


-- Fails, appropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Fails, appropriately
update kpTest 
   set id = id + 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Succeeds, appropriately
update kpTest 
   set id = id, name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

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