2013-06-12 13 views
7

Sto cercando di progettare un database che tenga traccia di ogni insieme di modifiche in modo che possa fare riferimento a loro in futuro. Così, per esempio:Progettazione database con cronologia delle modifiche

Database A 

+==========+========+==========+ 
| ID  | Name | Property | 

    1  Kyle  30 

Se cambio campo 'di proprietà' della riga a 50, si deve aggiornare la riga a:

1 Kyle 50 

ma dovrebbe risparmiare il fatto che la proprietà della riga era di 30 a un certo punto nel tempo. Poi, se la riga viene di nuovo aggiornato per essere 70:

1 Kyle 70 

Entrambi i fatti che la proprietà della riga è stata il 50 e 70 deve essere conservato, in modo tale che con un po 'di query ho potuto recuperare:

1 Kyle 30 
1 Kyle 50 

Dovrebbe riconoscere che queste erano le "stesse voci" solo in momenti diversi.

Edit: Questa storia dovrà essere presentato all'utente a un certo punto nel tempo così idealmente, ci dovrebbe essere una comprensione di quali righe appartengono allo stesso "gruppo di revisione"

Qual è il modo migliore per approccio alla progettazione di questo database?

+0

L'applicazione deve comprendere la cronologia (ovvero presentare questa cronologia all'utente finale) o è a scopo di controllo? – Matthew

+0

È necessario che questo sia memorizzato nel DB? Di solito questo viene fatto dall'applicazione in modo che possa essere in controllo di versione e possa essere applicato tra più sviluppatori. –

+0

Sì, l'applicazione dovrebbe presentare questa cronologia all'utente. –

risposta

12

Un modo è quello di avere un MyTableNameHistory per ogni tabella nel database, e rendere lo schema identico allo schema di tabella di MyTableName, tranne che la chiave primaria della tabella di storia ha una colonna aggiuntiva denominata effectiveUtc come DateTime. Ad esempio, se si dispone di una tabella denominata Employee,

Create Table Employee 
{ 
    employeeId integer Primary Key Not Null, 
    firstName varChar(20) null, 
    lastName varChar(30) Not null, 
    HireDate smallDateTime null, 
    DepartmentId integer null 
} 

Poi il tavolo la storia sarebbe

Create Table EmployeeHistory 
{ 
    employeeId integer Not Null, 
    effectiveUtc DateTime Not Null, 
    firstName varChar(20) null, 
    lastName varChar(30) Not null, 
    HireDate smallDateTime null, 
    DepartmentId integer null, 
    Primary Key (employeeId , effectiveUtc) 
} 

Poi, si può mettere un trigger sulla tabella Employee, in modo che ogni volta che si inserisce, aggiornamento oppure eliminare qualsiasi cosa nella tabella Employee, un nuovo record viene inserito nella tabella EmployeeHistory con gli stessi valori esatti per tutti i campi regolari e il datetime UTC corrente nella colonna effectiveUtc.

Quindi per trovare i valori in qualsiasi punto nel passato, basta selezionare il record dalla tabella della cronologia il cui valore efficaceUtc è il valore più alto prima del valore di data/ora asOf di cui si desidera il valore.

Select * from EmployeeHistory h 
Where EmployeeId = @EmployeeId 
    And effectiveUtc = 
    (Select Max(effectiveUtc) 
    From EmployeeHistory 
    Where EmployeeId = h.EmployeeId 
     And effcetiveUtc < @AsOfUtcDate) 
+1

Ma ... la query è inefficiente e lo schema non consente di ottenere facilmente il cluster di revisione di OP (come nel caso di query facilmente scrivibili), cioè di sapere quando un dipartimento è apparso, quando è stato cancellato e quando è stato ricreato. .. con tutti i dipendenti al suo interno in tempo 't'. –

+0

La query può essere semplificata usando la clausola 'DICTINCT ON()' di Postgres. Qualcosa come 'Seleziona distinto su (EmployeeId) * da EmployeeHistory dove effectiveUtc <= @AsOfUtcDate ordina da EmployeeId, effectiveUtc desc' –

+0

@Igor Typo:' DISTINCT ON() '(perché la copia-incauta sconsiderata è una mia cattiva abitudine) – luckydonald

1

Il modo migliore dipende da cosa stai facendo. Si vuole guardare più profondamente in dimensioni che cambiano lentamente:

https://en.wikipedia.org/wiki/Slowly_changing_dimension

In Postgres 9.2 Da non perdere il tipo tsrange, anche. Consente di unire start_date e end_date in una singola colonna e di indicizzare il materiale con un indice GIST (o GIN) a fianco di un vincolo di esclusione per evitare intervalli di date sovrapposti.


Edit:

ci dovrebbe essere una comprensione di quali righe appartengono allo stesso "gruppo di revisione"

In questo caso si desidera intervalli di date in un modo o un altro nella tua tabella, piuttosto che numeri di revisione o live flag, altrimenti finirai per duplicare tutti i dati correlati in tutto il luogo.

In una nota a parte, considerare di discriminare le tabelle di controllo dai dati in tempo reale, anziché memorizzare tutto nella stessa tabella. È più difficile da implementare e gestire, ma rende molto più efficienti le query sui dati in tempo reale.


Vedi questo post correlati, troppo: Temporal database design, with a twist (live vs draft rows)

1

Uno dei modi per registrare tutti i cambiamenti è quello di creare i cosiddetti audit triggers. Tali trigger possono registrare qualsiasi modifica alla tabella su cui si trovano in una tabella di log separata (che può essere interrogata per vedere la cronologia delle modifiche).

Dettagli sull'implementazione here.

0

Per aggiungere sul Charles' answer, vorrei utilizzare un Entity-Attribute-Value model invece di creare una tabella diversa storia per ogni altra tabella nel database.

In sostanza, si creerebbe unoHistory tabella in questo modo:

Create Table History 
{ 
    tableId varChar(64) Not Null, 
    recordId varChar(64) Not Null, 
    changedAttribute varChar(64) Not Null, 
    newValue varChar(64) Not Null, 
    effectiveUtc DateTime Not Null, 
    Primary Key (tableId , recordId , changedAttribute, effectiveUtc) 
} 

Poi si creerebbe un record History ogni volta che si creare o modificare i dati in una delle tabelle.

a seguire il vostro esempio, quando si aggiunge 'Kyle' al vostro tavolo Employee, si creerebbe due record (uno per ogni attributo non-id), e quindi è necessario creare un nuovo record ogni volta che un cambiamento di proprietà:

modifiche
History 
+==========+==========+==================+==========+==============+ 
| tableId | recordId | changedAttribute | newValue | effectiveUtc | 
| Employee | 1  | Name    | Kyle  | N   | 
| Employee | 1  | Property   | 30  | N   | 
| Employee | 1  | Property   | 50  | N+1   | 
| Employee | 1  | Property   | 70  | N+2   | 

in alternativa, come a_horse_with_no_name suggerito, se non si desidera memorizzare un nuovo History record per ogni cambio di campo, è possibile memorizzare raggruppate (ad esempio modificando Name a 'Kyle' e Property-30 nella stessa aggiornamento) come singolo record. In questo caso, è necessario esprimere la raccolta di modifiche in JSON o in un altro formato blob. Questo unirebbe i campi changedAttribute e newValue in uno (changedValues). Per esempio:

History 
+==========+==========+================================+==============+ 
| tableId | recordId | changedValues     | effectiveUtc | 
| Employee | 1  | { Name: 'Kyle', Property: 30 } | N   | 

Questo è forse più difficile che la creazione di un tavolo di storia per ogni altra tabella nel database, ma ha molteplici vantaggi:

  • l'aggiunta di nuovi campi per le tabelle del database ha vinto' t richiedono aggiungendo gli stessi campi per un altro tavolo
  • meno tavoli utilizzato
  • E 'più facile per correlare gli aggiornamenti di tabelle diverse nel corso del tempo
+2

È probabilmente più efficiente memorizzare tutti i valori di riga in una singola colonna JSON o hstore anziché una riga per ogni colonna modificata. per esempio. seguendo lo schema utilizzato in vari trigger di controllo vedi: http://okbob.blogspot.de/2015/01/most-simply-implementation-of-history.html o http://8kb.co.uk/blog/2015/ 01/19/copying-pavel-stehules-simple-history-table-ma-with-the-jsonb-type/o http://cjauvin.blogspot.de/2013/05/impossibly-lean-audit-system-for .html –

+0

@a_horse_with_no_name Sì. Anche questo funzionerebbe sicuramente. Aggiungerò una nota che spiega anche questa opzione. Grazie! –

Problemi correlati