Come controllare la versione di un record in un database


177

Diciamo che ho un record nel database e che sia l'amministratore che gli utenti normali possono fare aggiornamenti.

Qualcuno può suggerire un buon approccio / architettura su come controllare la versione di ogni modifica in questa tabella in modo che sia possibile ripristinare un record a una revisione precedente.

Risposte:


164

Supponiamo che tu abbia una FOOtabella che gli amministratori e gli utenti possono aggiornare. Il più delle volte è possibile scrivere query sulla tabella FOO. Giorni felici.

Quindi, vorrei creare una FOO_HISTORYtabella. Questo ha tutte le colonne della FOOtabella. La chiave primaria è la stessa di FOO più una colonna RevisionNumber. C'è una chiave esterna da FOO_HISTORYa FOO. È inoltre possibile aggiungere colonne relative alla revisione come UserId e RevisionDate. Popolare i RevisionNumber in modo sempre crescente su tutte le *_HISTORYtabelle (ovvero da una sequenza Oracle o equivalente). Non fare affidamento sul fatto che ci sia una sola modifica in un secondo (ovvero non inserire RevisionDatela chiave primaria).

Ora, ogni volta che esegui l'aggiornamento FOO, poco prima di eseguire l'aggiornamento inserisci i vecchi valori in FOO_HISTORY. Lo fai ad un livello fondamentale nella tua progettazione in modo che i programmatori non possano accidentalmente perdere questo passaggio.

Se vuoi eliminare una riga da FOOhai alcune scelte. Metti in cascata ed elimina tutta la cronologia oppure esegui un'eliminazione logica contrassegnandola FOOcome eliminata.

Questa soluzione è utile quando sei in gran parte interessato ai valori attuali e solo occasionalmente alla storia. Se hai sempre bisogno della cronologia, puoi inserire le date di inizio e fine effettive e conservare tutti i record in FOOsé. Ogni query deve quindi controllare quelle date.


1
È possibile eseguire l'aggiornamento della tabella di controllo con i trigger del database se il livello di accesso ai dati non lo supporta direttamente. Inoltre, non è difficile creare un generatore di codice per creare i trigger che utilizzano l'introspezione dal dizionario dei dati di sistema.
Preoccupato di

44
Consiglio vivamente di inserire effettivamente i nuovi dati, non i precedenti, quindi la tabella della cronologia contiene tutti i dati. Sebbene memorizzi i dati ridondanti, elimina i casi speciali necessari per gestire la ricerca in entrambe le tabelle quando sono richiesti dati storici.
Nerdfest,

6
Personalmente consiglierei di non cancellare nulla (rimandare a una specifica attività di pulizia) e di avere una colonna "tipo di azione" per specificare se si tratta di inserire / aggiornare / eliminare. Per un'eliminazione copi la riga normalmente, ma inserisci "elimina" nella colonna del tipo di azione.
Neil Barnwell,

3
@Hydrargyrum Una tabella contenente i valori correnti funzionerà meglio di una vista della tabella storica. Potresti anche voler definire le chiavi esterne che fanno riferimento ai valori correnti.
WW.

2
There is a foreign key from FOO_HISTORY to FOO': cattiva idea, vorrei cancellare i record da pippo senza cambiare la cronologia. la tabella cronologica deve essere solo di inserimento nell'uso normale.
Jasen,

46

Penso che tu stia cercando il controllo della versione del contenuto dei record del database (come fa StackOverflow quando qualcuno modifica una domanda / risposta). Un buon punto di partenza potrebbe essere quello di esaminare alcuni modelli di database che utilizzano il monitoraggio delle revisioni .

Il miglior esempio che mi viene in mente è MediaWiki, il motore di Wikipedia. Confronta qui il diagramma del database , in particolare la tabella di revisione .

A seconda delle tecnologie che stai utilizzando, dovrai trovare alcuni buoni algoritmi di diff / merge.

Controlla questa domanda se è per .NET.


30

Nel mondo della BI, è possibile ottenere ciò aggiungendo una data di inizio e una di fine alla tabella che si desidera versione. Quando si inserisce il primo record nella tabella, startDate viene popolato, ma endDate è null. Quando si inserisce il secondo record, si aggiorna anche la data di fine del primo record con la data di inizio del secondo record.

Quando si desidera visualizzare il record corrente, selezionare quello in cui endDate è null.

A volte questa viene chiamata dimensione 2 a modifica lenta . Vedi anche TupleVersioning


Il mio tavolo non diventerà abbastanza grande usando questo approccio?
Niels Bosma,

1
Sì, ma puoi gestirlo indicizzando e / o partizionando la tabella. Inoltre, ci sarà solo una manciata di tavoli di grandi dimensioni. La maggior parte sarà molto più piccola.
Preoccupato di

Se non sbaglio, l'unico inconveniente qui è che limita le modifiche a una volta al secondo, giusto?
pimbrouwers,

@pimbrouwers sì, dipende in ultima analisi dalla precisione dei campi e dalla funzione che li popola.
Dave Neeley,

9

Aggiornamento a SQL 2008.

Prova a utilizzare il rilevamento delle modifiche SQL, in SQL 2008. Invece di hack di timestamp e colonne tombali, puoi utilizzare questa nuova funzionalità per tenere traccia delle modifiche ai dati nel tuo database.

Monitoraggio modifiche MSDN SQL 2008


7

Volevo solo aggiungere che una buona soluzione a questo problema è usare un database temporale . Molti fornitori di database offrono questa funzionalità sia pronta all'uso che tramite un'estensione. Ho usato con successo l' estensione della tabella temporale con PostgreSQL ma anche altri lo hanno. Ogni volta che aggiorni un record nel database, anche il database mantiene la versione precedente di quel record.


6

Due opzioni:

  1. Avere una tabella di cronologia: inserire i vecchi dati in questa tabella di cronologia ogni volta che l'originale viene aggiornato.
  2. Tabella di controllo - memorizza i valori prima e dopo - solo per le colonne modificate in una tabella di controllo insieme ad altre informazioni come chi ha aggiornato e quando.

5

È possibile eseguire il controllo su una tabella SQL tramite trigger SQL. Da un trigger è possibile accedere a 2 tabelle speciali ( inserite ed eliminate ). Queste tabelle contengono le righe esatte che sono state inserite o eliminate ogni volta che la tabella viene aggiornata. Nel trigger SQL è possibile prendere queste righe modificate e inserirle nella tabella di controllo. Questo approccio significa che l'auditing è trasparente per il programmatore; che non richiede alcuno sforzo da parte loro o alcuna conoscenza implementativa.

Il vantaggio aggiuntivo di questo approccio è che il controllo avverrà indipendentemente dal fatto che l'operazione sql sia avvenuta tramite le DLL di accesso ai dati o tramite una query SQL manuale; (poiché il controllo viene eseguito sul server stesso).


3

Non dici quale database e non lo vedo nei tag post. Se è per Oracle, posso consigliare l'approccio integrato in Designer: utilizzare le tabelle del journal . Se è per qualsiasi altro database, beh, sostanzialmente raccomando anche allo stesso modo ...

Il modo in cui funziona, nel caso in cui si desideri replicarlo in un altro DB, o forse se si desidera semplicemente capirlo, è che per una tabella esiste anche una tabella shadow, solo una normale tabella di database, con le stesse specifiche del campo , oltre ad alcuni campi extra: come quale ultima azione è stata eseguita (stringa, valori tipici "INS" per inserimento, "UPD" per aggiornamento e "DEL" per eliminazione), datetime per quando è avvenuta l'azione e ID utente per chi ha fatto esso.

Tramite i trigger, ogni azione su una riga della tabella inserisce una nuova riga nella tabella journal con i nuovi valori, quale azione è stata intrapresa, quando e da quale utente. Non eliminare mai alcuna riga (almeno non negli ultimi mesi). Sì, diventerà grande, facilmente milioni di righe, ma puoi facilmente tenere traccia del valore per qualsiasi record in qualsiasi momento dal il journaling è stato avviato o le vecchie righe del journal sono state eliminate l'ultima volta e chi ha apportato l'ultima modifica.

In Oracle tutto ciò di cui hai bisogno viene generato automaticamente come codice SQL, tutto ciò che devi fare è compilarlo / eseguirlo; e viene fornito con un'applicazione CRUD di base (in realtà solo "R") per controllarlo.


2

Sto anche facendo la stessa cosa. Sto creando un database per i piani di lezione. Questi piani richiedono flessibilità di controllo delle versioni a cambiamento atomico. In altre parole, ogni modifica, per quanto piccola, ai piani di lezione deve essere consentita, ma anche la vecchia versione deve essere mantenuta intatta. In questo modo, i creatori di lezioni possono modificare i piani di lezione mentre gli studenti li utilizzano.

Il modo in cui funzionerebbe è che una volta che uno studente ha fatto una lezione, i suoi risultati sono allegati alla versione che hanno completato. Se viene apportata una modifica, i loro risultati punteranno sempre alla loro versione.

In questo modo, se i criteri di una lezione vengono eliminati o spostati, i loro risultati non cambieranno.

Il modo in cui lo sto facendo attualmente è gestendo tutti i dati in una tabella. Normalmente avrei solo un campo ID, ma con questo sistema sto usando un ID e un sub_id. Il sub_id rimane sempre con la riga, attraverso aggiornamenti ed eliminazioni. L'id viene incrementato automaticamente. Il software del piano di lezione si collegherà al più recente sub_id. I risultati degli studenti si collegheranno all'ID. Ho anche incluso un timestamp per il monitoraggio in caso di modifiche, ma non è necessario gestire il controllo delle versioni.

Una cosa che potrei cambiare, una volta provato, è che potrei usare l'idea null endDate precedentemente menzionata. Nel mio sistema, per trovare la versione più recente, dovrei trovare il massimo (id). L'altro sistema cerca endDate = null. Non sono sicuro se i benefici in uscita abbiano un altro campo data.

I miei due centesimi.


2

Mentre @WW. answer è una buona risposta un altro modo è quello di creare una colonna di versione e mantenere tutte le versioni nella stessa tabella.

Per un approccio a una tabella è necessario:

  • Utilizzare una bandiera per indicare l'ultima ala Word Press
  • O fai una brutta versione più grande della versione outer join.

Un esempio SQL del outer joinmetodo che utilizza i numeri di revisione è:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

La cattiva notizia è che quanto sopra richiede outer joine un join esterno può essere lento. La buona notizia è che la creazione di nuove voci è teoricamente più economica perché è possibile farlo in una sola operazione di scrittura senza transazioni (supponendo che il database sia atomico).

Un esempio per fare una nuova revisione '/stuff'potrebbe essere:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Inseriamo usando i vecchi dati. Ciò è particolarmente utile se si dice che si desidera aggiornare solo una colonna ed evitare il blocco e / o le transazioni ottimistiche.

L'approccio flag e l'approccio tabella cronologica richiedono l' inserimento / l'aggiornamento di due righe.

L'altro vantaggio con l' outer joinapproccio del numero di revisione è che si può sempre rifattorizzare l'approccio a più tabelle in un secondo momento con i trigger poiché il trigger deve essenzialmente fare qualcosa di simile a quanto sopra.


2

Alok ha suggerito Audit tablesopra, vorrei spiegarlo nel mio post.

Ho adottato questo progetto a tabella singola senza schema nel mio progetto.

Schema:

  • id - INTEGER AUTO INCREMENT
  • nome utente - STRING
  • tablename - STRING
  • oldvalue - TEXT / JSON
  • newvalue - TEXT / JSON
  • Createdon - DATETIME

Questa tabella può contenere record storici per ogni tabella in un'unica posizione, con la cronologia completa degli oggetti in un record. Questa tabella può essere popolata utilizzando trigger / hook in cui i dati cambiano, memorizzando un'istantanea del valore vecchio e nuovo della riga di destinazione.

Pro con questo design:

  • Meno numero di tabelle da gestire per la gestione della cronologia.
  • Memorizza l'istantanea completa di ogni riga vecchio e nuovo stato.
  • Facile da cercare su ogni tavolo.
  • Può creare partizioni per tabella.
  • Può definire criteri di conservazione dei dati per tabella.

Contro con questo design:

  • Le dimensioni dei dati possono essere elevate, se il sistema presenta frequenti modifiche.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.