Domanda

L'ho già fatto da qualche parte ne sono sicuro!

Ho una tabella di SQL Server 2000 che devo registrare le modifiche ai campi sugli aggiornamenti e inserimenti in una seconda tabella di registrazione. Una versione semplificata della struttura che sto usando è di seguito:

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

LogTable
OLDID varchar(10)
NEWID varchar(10)

Per qualsiasi altro campo qualcosa del genere funzionerebbe alla grande:

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

... Ma ovviamente l'unione fallirebbe se l'ID venisse modificato.

Non riesco a modificare le tabelle in modo, l'unico potere che ho in questo database è di creare un trigger.

In alternativa c'è qualcuno che mi può insegnare a viaggiare nel tempo e tornerò indietro nel passato e chiedermi allora come ho fatto? Saluti :)


Modifica

Penso di aver bisogno di chiarire alcune cose qui. Questo non è in realtà il mio database , è un sistema preesistente di cui non ho quasi alcun controllo, a parte scrivere questo trigger.

La mia domanda è come posso recuperare la vecchia chiave primaria se detta chiave primaria è stata cambiata. Non ho bisogno di sentirmi dire che non dovrei cambiare la chiave primaria o di rincorrere chiavi esterne ecc. Non è un mio problema :)

È stato utile?

Soluzione

È possibile presumere che le tabelle INSERTED e DELETED presentate in un trigger siano garantite nello stesso ordine?

Altri suggerimenti

DECLARE @OldKey int, @NewKey int;

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

Funziona solo se hai una sola riga. Altrimenti non hai " anchor " per collegare vecchie e nuove righe. Quindi controlla nel tuo trigger per > 1 in INSERITO.

Non penso sia possibile. Immagina se hai 4 righe nella tabella:

1  Val1
2  Val2
3  Val3
4  Val4

Ora pubblica il seguente aggiornamento:

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

Ora, come hai intenzione di distinguere tra quello che è successo alle righe 1 & amp; 2 e cosa è successo alle righe 3 & amp; 4. E, cosa più importante, puoi descrivere cosa c'è di diverso tra loro? Tutte le cose che ti dicono quali colonne sono state aggiornate non ti aiuteranno.

Se in questo caso è possibile che sul tavolo sia presente una chiave aggiuntiva (ad es. la descrizione è UNICA) e le regole di aggiornamento lo consentono, è possibile scrivere il trigger per impedire aggiornamenti simultanei su entrambe le chiavi e quindi utilizzare qualsiasi la chiave non è stata aggiornata per correlare le due tabelle.

Se è necessario gestire inserimenti / aggiornamenti su più righe e non esiste una chiave alternativa che non garantisca la modifica, l'unico modo che posso vedere è utilizzare un trigger INSTEAD OF. Ad esempio, nel trigger è possibile suddividere il comando di inserimento / aggiornamento originale in un comando per riga, catturando ogni vecchio ID prima di inserire / aggiornare.

Nei trigger di SQL Server è possibile accedere a due tabelle: eliminate e inserite. Entrambi sono già stati menzionati. Ecco come funzionano a seconda dell'azione che il trigger sta attivando:

INSERISCI OPERAZIONI

  • eliminato - non utilizzato
  • inserito: contiene le nuove righe che vengono aggiunte alla tabella

ELIMINA OPERAZIONI

  • eliminato: contiene le righe che vengono rimosse dalla tabella
  • inserito - non utilizzato

OPERAZIONI DI AGGIORNAMENTO

  • eliminato: contiene le righe come esisterebbero prima dell'operazione di AGGIORNAMENTO
  • inserito: contiene le righe come esisterebbero dopo l'operazione di AGGIORNAMENTO

Funzionano in ogni modo come le tabelle. Pertanto, è del tutto possibile utilizzare un'operazione basata su righe come una delle seguenti (L'operazione esiste solo nella tabella di controllo, così come 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

---- ---- nuova aggiungi una colonna di identità alla tabella che l'applicazione non può modificare, quindi puoi utilizzare quella nuova colonna per unire le tabelle inserite alle tabelle eliminate all'interno del trigger:

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

---- ---- vecchio Non aggiornare / modificare mai i valori chiave. Come puoi fare questo e riparare tutte le tue chiavi esterne?

Non consiglierei mai di usare un trigger che non è in grado di gestire un set di righe.

Se devi cambiare la chiave, inserisci una nuova riga con la nuova chiave e i valori corretti, usa SCOPE_IDENTITY () se è quello che stai facendo. Elimina la vecchia riga. Registra per la vecchia riga che è stata modificata nella chiave della nuova riga, che ora dovresti avere. Spero non ci sia chiave esterna sulla chiave modificata nel tuo registro ...

È possibile creare una nuova colonna identità nella tabella MainTable (denominata ad esempio correlationid) e correlare le tabelle inserite ed eliminate utilizzando questa colonna. Questa nuova colonna dovrebbe essere trasparente per il codice esistente.

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

Prestare attenzione, è possibile inserire record duplicati nella tabella di registro.

Ovviamente nessuno dovrebbe cambiare la chiave primaria sul tavolo - ma questo è esattamente ciò che i grilletti dovrebbero essere (in parte), è impedire alle persone di fare cose che non dovrebbero fare. È un compito banale in Oracle o MySQL scrivere un trigger che intercetta le modifiche alle chiavi primarie e le interrompe, ma per nulla facile in SQL Server.

Ciò che ovviamente ti piacerebbe poter fare sarebbe semplicemente fare qualcosa del genere:

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

Ecco perché le persone escono googling per l'equivalente di SQL Server di ROWID. Bene, SQL Server non ce l'ha; quindi devi trovare un altro approccio.

Una versione veloce, ma purtroppo non a prova di bomba, è quella di scrivere un trigger anziché aggiornamento che cerca di vedere se una delle righe inserite ha una chiave primaria non trovata nella tabella aggiornata o viceversa. Ciò catturerebbe la maggior parte, ma non tutti, degli errori:

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

Ma questo non rileva ancora un aggiornamento come ...

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

Ora, ho provato a supporre che le tabelle inserite ed eliminate siano ordinate in modo tale che scorrendo simultaneamente le tabelle inserite ed eliminate vi dia correttamente le righe corrispondenti. E questo APPARE per funzionare. In effetti trasformi il trigger nell'equivalente dei trigger per ogni riga disponibili in Oracle e obbligatorio in MySQL ... ma immagino che le prestazioni saranno pessime su aggiornamenti massicci poiché questo non è un comportamento nativo di SQL Server. Inoltre dipende dal presupposto che in realtà non riesco a trovare documentato da nessuna parte e quindi sono riluttante a dipendere da. Ma il codice strutturato in questo modo APPEARS funziona correttamente sulla mia installazione di SQL Server 2008 R2. Lo script alla fine di questo post evidenzia sia il comportamento della soluzione veloce ma non a prova di bomba sia il comportamento della seconda soluzione pseudo-Oracle.

Se qualcuno potesse indicarmi un posto in cui la mia supposizione è documentata e garantita da Microsoft, sarei un ragazzo molto grato ...

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top