Rilevamento delle modifiche in un database di SQL Server 2005
-
08-07-2019 - |
Domanda
Mi è stato assegnato il compito di sviluppare una soluzione che tenga traccia delle modifiche a un database.
Per gli aggiornamenti che devo acquisire:
- data di aggiornamento
- vecchio valore
- nuovo valore
- campo interessato
- persona che sta cambiando
- ID record
- il record della tabella è in
Per le eliminazioni:
- data di eliminazione
- persona che cancella
- Titolo / descrizione / ID del record eliminati. Le tabelle su cui sto monitorando le modifiche hanno tutte un titolo o un campo di descrizione. Vorrei catturarlo prima che il record fosse cancellato.
- il record della tabella era in
Per inserti:
- data di inserimento
- persona che sta cambiando
- ID record
- il record della tabella è in
Ho pensato ad alcuni modi per farlo:
- Sto utilizzando le procedure memorizzate per eventuali aggiornamenti / eliminazioni / inserti. Vorrei creare un generico & Quot; tracking & Quot; tavolo. Avrebbe abbastanza campi per acquisire tutti i dati. Vorrei quindi aggiungere un'altra riga in ogni proc memorizzato all'effetto di & Quot; Inserire il record nella tabella di tracciamento & Quot ;.
- svantaggio: tutti gli aggiornamenti / eliminazioni / inserti sono tutti confusi nella stessa tabella
- molti campi NULLed
- come posso tenere traccia degli aggiornamenti / eliminazioni / inserimenti batch? < ---- questo potrebbe non essere un problema. In realtà non faccio nulla del genere nell'applicazione.
- come posso catturare l'utente mentre aggiorna. Il database vede solo un account.
- modifica molto codice esistente da modificare.
- Infine, potrei creare un trigger che viene chiamato dopo aggiornamenti / eliminazioni / inserimenti. Molti degli stessi aspetti negativi della prima soluzione, tranne: avrei dovuto modificare tanto codice. Non sono sicuro di come tracciare gli aggiornamenti. Non sembra che ci sia un modo per usare i trigger per vedere i record aggiornati di recente.
Sto usando asp.net, C #, sql server 2005, iis6, windows 2003. Purtroppo non ho un budget così triste che non posso comprare nulla per aiutarmi in questo.
Grazie per le tue risposte!
Soluzione
Un trigger non avrebbe tutte le informazioni necessarie per una serie di motivi, ma nessun ID utente è il copertoncino.
Direi che sei sulla buona strada con uno sp comune da inserire ovunque venga apportata una modifica. Se stai standardizzando su sp per le tue interfacce, allora sei avanti al gioco - sarà difficile intrufolarsi in una modifica che non viene tracciata.
Guarda questo come l'equivalente di una pista di controllo in un'applicazione di contabilità - questo è il Journal - una singola tabella con ogni transazione registrata. Non implementerebbero riviste separate per depositi, prelievi, rettifiche, ecc. E questo è lo stesso principio.
Altri suggerimenti
Odio affrontare il problema e so che non hai budget, ma la soluzione più semplice sarà l'aggiornamento a SQL Server 2008. Ha questa funzionalità incorporato . Ho pensato che dovrebbe essere menzionato almeno per chiunque incontri questa domanda, anche se non puoi usarla da solo.
(Tra le edizioni distribuibili di SQL 2008, questa funzionalità è disponibile solo in Enterprise.)
Ti suggerirei di usare 2 colonne in ogni tabella. nomi storia della riga e IsDeleted e il tipo di dati sarà xml e bit. Non eliminare mai le righe, usa sempre il flag IsDeleted Ora vai con i trigger di aggiornamento. Ti darò un esempio per lo stesso Ho questa tabella chiamata Pagina
CREATE TABLE te_Page([Id] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](200) NOT NULL, [Description] [varchar](200) NULL,[CreatedBy] [uniqueidentifier] NULL, [CreatedDate] [datetime] NOT NULL, [UpdatedBy] [uniqueidentifier] NULL, [UpdatedDate] [datetime] NULL, [IsDeleted] [bit] NULL, [RowHistory] [xml] NULL, CONSTRAINT [PK_tm_Page] PRIMARY KEY CLUSTERED ([Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
Ora, dopo aver creato la tabella, tutto ciò che devi fare è copiare e incollare il codice qui sotto e il tuo compito è fatto per la tabella Pagina. Inizierà a registrare la cronologia della riga nella stessa riga che viene aggiornata insieme ai valori vecchi e nuovi.
ALTER Trigger [dbo].[Trg_Te_Page]
On [dbo].[te_Page]
After Update
As
--If @@rowcount = 0 Or Update(RowHistory)
--Return
Declare @xml NVARCHAR(MAX)
Declare @currentxml NVARCHAR(MAX)
Declare @node NVARCHAR(MAX)
Declare @ishistoryexists XML
Declare @FormLineAttributeValueId int
-- new Values
Declare @new_Name varchar(200)
Declare @new_Description varchar(200)
Declare @new_CreatedBy UNIQUEIDENTIFIER
Declare @new_CreatedDate DATETIME
Declare @new_UpdatedBy UNIQUEIDENTIFIER
Declare @new_UpdatedDate DATETIME
Declare @new_IsDeleted BIT
--old values
Declare @old_Name varchar(200)
Declare @old_Description varchar(200)
Declare @old_CreatedBy UNIQUEIDENTIFIER
Declare @old_CreatedDate DATETIME
Declare @old_UpdatedBy UNIQUEIDENTIFIER
Declare @old_UpdatedDate DATETIME
Declare @old_IsDeleted BIT
-- declare temp fmId
Declare @fmId int
-- declare cursor
DECLARE curFormId cursor
FOR select Id from INSERTED
-- open cursor
OPEN curFormId
-- fetch row
FETCH NEXT FROM curFormId INTO @fmId
WHILE @@FETCH_STATUS = 0
BEGIN
Select
@FormLineAttributeValueId = Id,
@old_Name = Name,
@old_Description = [Description],
@old_CreatedBy = CreatedBy,
@old_CreatedDate =CreatedDate,
@old_UpdatedBy =UpdatedBy,
@old_UpdatedDate =UpdatedDate,
@old_IsDeleted = IsDeleted,
@currentxml = cast(RowHistory as NVARCHAR(MAX))
From DELETED where Id=@fmId
Select
@new_Name = Name,
@new_Description = [Description],
@new_CreatedBy = CreatedBy,
@new_CreatedDate =CreatedDate,
@new_UpdatedBy =UpdatedBy,
@new_UpdatedDate =UpdatedDate,
@new_IsDeleted = IsDeleted
From INSERTED where Id=@fmId
set @old_Name = Replace(@old_Name,'&','&')
set @old_Name = Replace(@old_Name,'>','>')
set @old_Name = Replace(@old_Name,'<','<')
set @old_Name = Replace(@old_Name,'"','"')
set @old_Name = Replace(@old_Name,'''',''')
set @new_Name = Replace(@new_Name,'&','&')
set @new_Name = Replace(@new_Name,'>','>')
set @new_Name = Replace(@new_Name,'<','<')
set @new_Name = Replace(@new_Name,'"','"')
set @new_Name = Replace(@new_Name,'''',''')
set @old_Description = Replace(@old_Description,'&','&')
set @old_Description = Replace(@old_Description,'>','>')
set @old_Description = Replace(@old_Description,'<','<')
set @old_Description = Replace(@old_Description,'"','"')
set @old_Description = Replace(@old_Description,'''',''')
set @new_Description = Replace(@new_Description,'&','&')
set @new_Description = Replace(@new_Description,'>','>')
set @new_Description = Replace(@new_Description,'<','<')
set @new_Description = Replace(@new_Description,'"','"')
set @new_Description = Replace(@new_Description,'''',''')
set @xml = ''
BEGIN
-- for Name
If ltrim(rtrim(IsNull(@new_Name,''))) != ltrim(rtrim(IsNull(@old_Name,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Name" OldValue="'+ @old_Name + '" NewValue="' + @new_Name + '"/>'
-- for Description
If ltrim(rtrim(IsNull(@new_Description,''))) != ltrim(rtrim(IsNull(@old_Description,'')))
set @xml = @xml + '<ColumnInfo ColumnName="Description" OldValue="'+ @old_Description + '" NewValue="' + @new_Description + '"/>'
-- CreatedDate
If IsNull(@new_CreatedDate,'') != IsNull(@old_CreatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="CreatedDate" OldValue="'+ cast(isnull(@old_CreatedDate,'') as varchar(100)) + '" NewValue="' + cast(isnull(@new_CreatedDate,'') as varchar(100)) + '"/>'
-- CreatedBy
If cast(IsNull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar (36)) != cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="CreatedBy" OldValue="'+ cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(isnull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- UpdatedDate
If IsNull(@new_UpdatedDate,'') != IsNull(@old_UpdatedDate,'')
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedDate" OldValue="'+ cast(IsNull(@old_UpdatedDate,'') as varchar(100)) + '" NewValue="' + cast(IsNull(@new_UpdatedDate,'') as varchar(100)) + '"/>'
-- UpdatedBy
If cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) != cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))
set @xml = @xml + '<ColumnInfo ColumnName="UpdatedBy" OldValue="'+ cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
'"/>'
-- IsDeleted
If cast(IsNull(@new_IsDeleted,'') as varchar(10)) != cast(IsNull(@old_IsDeleted,'') as varchar(10))
set @xml = @xml + '<ColumnInfo ColumnName="IsDeleted" OldValue="'+ cast(IsNull(@old_IsDeleted,'') as varchar(10)) + '" NewValue="' + cast(IsNull(@new_IsDeleted,'') as varchar(10)) + '" />'
END
Set @xml = '<RowInfo TableName="te_Page" UpdatedBy="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(50)) + '" UpdatedDate="' + Convert(Varchar(20),GetDate()) + '">' + @xml + '</RowInfo>'
Select @ishistoryexists = RowHistory From DELETED
--print @ishistoryexists
If @ishistoryexists is null
Begin
Set @xml = '<History>' + @xml + '</History>'
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
Else
Begin
set @xml = REPLACE(@currentxml, '<History>', '<History>' + @xml)
Update te_Page
Set
RowHistory = @xml
Where
Id = @FormLineAttributeValueId
End
FETCH NEXT FROM curFormId INTO @fmId
END
CLOSE curFormId
DEALLOCATE curFormId
Ora, ogni volta che eseguirai qualsiasi aggiornamento, i tuoi dati verranno archiviati nella colonna storia delle righe
Un modo in cui l'ho visto gestito (anche se non lo consiglierei, onestamente) è gestirlo tramite procedure memorizzate, passando userid / username / qualunque come parametro. Le procedure memorizzate chiamerebbero una procedura di registrazione, che ha scritto i dettagli rilevanti in una tabella di registro centrale.
Ecco dove è diventato un po 'strano, però ...
Per INSERT / UPDATE, le righe pertinenti sono state memorizzate nella tabella come dati XML dopo che INSERT / UPDATE sono stati completati correttamente. Per DELETE, la riga è stata archiviata prima dell'esecuzione di DELETE (sebbene, realisticamente, avrebbero potuto ottenerla dall'output dell'istruzione DELETE - almeno con SQL Server 2005).
Se ricordo bene, la tabella aveva solo un paio di colonne: UserID, DateTime della registrazione, Tipo di transazione (I / U / D), dati XML contenenti le righe pertinenti, nome della tabella e valore della chiave primaria (usato principalmente per una rapida ricerca dei record desiderati).
Molti modi per scuoiare un gatto, però ...
Il mio consiglio è di mantenerlo semplice. Espandilo in un secondo momento se / quando è necessario.
Se hai la possibilità di farlo, blocca gli utenti per poter eseguire solo istruzioni eseguibili sui tavoli tramite procedure memorizzate e quindi gestire la registrazione (come preferisci) da lì.
abbiamo creato il nostro e avevamo solo bisogno che l'utente e il PC passassero in ogni procedura di aggiunta / aggiornamento memorizzata. quindi si tratta solo di ottenere il record originale e di popolare le variabili, confrontarle con le variabili passate e registrare i dati nella nostra tabella. per le eliminazioni abbiamo solo una copia delle tabelle di origine + un campo timestamp in modo che il record non sia mai realmente cancellato e possa essere ripristinato in qualsiasi momento necessario (ovviamente i controlli di routine di eliminazione per le relazioni FK e simili).
assomiglia / aggiunge la tabella di registro appuntamento, table_name, column_name, record_id, old_value, NEW_VALUE, ID utente, del computer
non inseriamo mai valori null quindi li convertiamo in stringhe vuote, le nuove voci sono contrassegnate con '{new entry}' nella colonna old_value. record_id è composto da altrettante colonne chiave per identificare in modo univoco quel singolo record (field1 + '.' + field2 + ...)
Prima di tutto, in tutte le tabelle dovresti avere almeno queste colonne aggiunte alle colonne di dati DateCreated, UserCreated, DateModified, UserModified. Forse potresti voler aggiungere un & Quot; Status & Quot; oppure " LastAction " colonna in modo da non eliminare mai una riga, ma semplicemente impostarla su uno stato eliminato / inserito / aggiornato. Successivamente è possibile creare un & Quot; Tabella cronologica & Quot; che è una copia esatta della prima tabella. Quindi su eventuali aggiornamenti o eliminazioni, il trigger deve copiare le voci della tabella Eliminate nella tabella Cronologia modificando contemporaneamente i campi DateModified, UserModified e Status.
Ho avuto una configurazione in SQL Server in cui avremmo usato le viste per accedere ai nostri dati, che avrebbe gestito inserimenti, aggiornamenti ed eliminazioni con i trigger INSTEAD OF.
Ad esempio: un INSTEAD OF DELETE sulla vista contrassegnerebbe i record nella tabella sottostante come eliminati e la vista veniva filtrata per non mostrare i record eliminati.
In tutti i trigger, abbiamo aggiornato una data di modifica e un nome utente. Il problema è che registra il nome utente del database, che non è lo stesso del nome utente dell'applicazione finale.
La vista deve essere associata allo schema affinché funzioni.
Informazioni sulla registrazione degli utenti che cambiano DB: puoi creare tutti gli utenti SQL di cui hai bisogno per il tuo DB e se usi sessioni e accesso limitato / registrato al tuo programma / script puoi utilizzare tali informazioni per avviare impostazioni di connessione DB diverse (ad esempio nome utente), prima di qualsiasi operazione con DB.
Almeno questo dovrebbe essere fattibile per gli script PHP-saggi, ma potrei essere sbagliato per asp.net.