2010-09-22 8 views
26

Ecco uno scenario fittizio con alcuni dati popolati. Ai fini fiscali, la mia società fittizia deve conservare record di dati storici. Per questo motivo, ho incluso una colonna della versione sul tavolo.Versioning in tabelle SQL - come gestirlo?

TABLE EMPLOYEE: (with personal commentary) 

|ID | VERSION | NAME  | Position | PAY | 
+---+---------+------------+----------+-----+ 
| 1 | 1 | John Doe | Owner | 100 | Started company 
| 1 | 2 | John Doe | Owner | 80 | Pay cut to hire a coder 
| 2 | 1 | Mark May | Coder | 20 | Hire said coder 
| 2 | 2 | Mark May | Coder | 30 | Productive coder gets raise 
| 3 | 1 | Jane Field | Admn Asst| 15 | Need office staff 
| 2 | 3 | Mark May | Coder | 35 | Productive coder gets raise 
| 1 | 3 | John Doe | Owner | 120 | Sales = profit for owner! 
| 3 | 2 | Jane Field | Admn Asst| 20 | Raise for office staff 
| 4 | 1 | Cody Munn | Coder | 20 | Hire another coder 
| 4 | 2 | Cody Munn | Coder | 25 | Give that coder raise 
| 3 | 3 | Jane Munn | Admn Asst| 20 | Jane marries Cody <3 
| 2 | 4 | Mark May | Dev Lead | 40 | Promote mark to Dev Lead 
| 4 | 3 | Cody Munn | Coder | 30 | Give Cody a raise 
| 2 | 5 | Mark May | Retired | 0 | Mark retires 
| 5 | 1 | Joey Trib | Dev Lead | 40 | Bring outside help for Dev Lead 
| 6 | 1 | Hire Meplz | Coder | 10 | Hire a cheap coder 
| 3 | 4 | Jane Munn | Retired | 0 | Jane quits 
| 7 | 1 | Work Fofre | Admn Asst| 10 | Hire Janes replacement 
| 8 | 1 | Fran Hesky | Coder | 10 | Hire another coder 
| 9 | 1 | Deby Olav | Coder | 25 | Hire another coder 
| 4 | 4 | Cody Munn | VP Ops | 80 | Promote Cody 
| 9 | 2 | Deby Olav | VP Ops | 80 | Cody fails at VP Ops, promote Deby 
| 4 | 5 | Cody Munn | Retired | 0 | Cody retires in shame 
| 5 | 2 | Joey Trib | Dev Lead | 50 | Give Joey a raise 
+---+---------+------------+----------+-----+ 

Ora, se volevo fare qualcosa di simile "Ottenere una lista dei programmatori correnti" non ho potuto fare solo SELECT * FROM EMPLOYEE WHERE Position = 'Coder' perché sarebbe tornare un sacco di dati storici ... che è male.

Sto cercando buone idee per gestire questo scenario. Vedo alcune opzioni che mi saltano addosso, ma sono sicuro che qualcuno dirà "Wow, questo è un errore da principiante, splendore ... prova questo per le dimensioni:" di che cosa si tratta questo posto, giusto? :-)

Idea numero 1: Mantenere una tabella versione con la versione corrente come questo

TABLE EMPLOYEE_VERSION: 

|ID |VERSION| 
+---+-------+ 
| 1 | 3 | 
| 2 | 5 | 
| 3 | 4 | 
| 4 | 6 | 
| 5 | 2 | 
| 6 | 1 | 
| 7 | 1 | 
| 8 | 1 | 
| 9 | 2 |  
+---+-------+ 

Anche se non sono sicuro di come lo farei con una singola query, io sono sicuro che potrebbe essere fatto, e scommetto che potrei capirlo con uno sforzo piuttosto piccolo.

Ovviamente, dovrei aggiornare questa tabella ogni volta che inserisco nella tabella EMPLOYEE per incrementare la versione per l'ID specificato (o inserirla nella tabella delle versioni quando viene creato un nuovo ID).

Il sovraccarico di ciò sembra indesiderabile.

Numero di idea 2: Mantiene una tabella di archivio e una tabella principale. Prima di aggiornare la tabella principale, inserisci la riga che sto per sovrascrivere nella tabella di archivio e usa la tabella principale come farebbe normalmente, come se non fossi interessato al controllo delle versioni.

Idea numero 3: Trova una query che aggiunge qualcosa sulla falsariga di SELECT * FROM EMPLOYEE WHERE Position = 'Coder' and version=MaxVersionForId(EMPLOYEE.ID) ... Non del tutto sicuro di come lo farei. Questa mi sembra la migliore idea, ma a questo punto non sono davvero sicuro.

Idea numero 4: Fai una colonna per "corrente" e aggiungere "in cui la corrente = TRUE e ..."

mi viene in mente che sicuramente le persone hanno fatto prima, incorrere in questi stessi problemi e avere informazioni su di esso da condividere, e così vengo a ritenerlo! :) Ho già cercato di trovare esempi del problema, ma sembrano specializzati in uno scenario particolare.

Grazie!

EDIT 1:

In primo luogo, apprezzo tutte le risposte, e hai tutto detto la stessa cosa - DATE è meglio di VERSION NUMBER. Una ragione stavo andando con VERSION NUMBER è stato quello di semplificare il processo di aggiornamento nel server per impedire il seguente scenario

persona Un record carichi dipendente 3 nella sua sessione, ed ha la versione 4. record di persona B carichi dipendente 3 la sua sessione, e ha la versione 4. La persona A apporta modifiche e si impegna. Funziona perché la versione più recente nel database è 4. Ora è 5. La persona B apporta modifiche e commette. Questo fallisce perché la versione più recente è 5, mentre il suo è 4.

In che modo lo schema EFFECTIVE DATE risolve questo problema?

EDIT 2:

credo che avrei potuto farlo facendo qualcosa di simile a questo: persona Un record carichi dipendente 3 nella sua sessione, ed è data effettiva è 1-1-2010, 01:00 pm, senza esperienza. La persona B carica il record 3 del dipendente nella sua sessione e la sua data effettiva è 1-1-2010, 13:00, senza alcuna esperienza. La persona A apporta modifiche e commette. La vecchia copia va alla tabella archivio (sostanzialmente idea 2) con una data di sperimentazione del 21/09/2010 alle 13:00. La versione aggiornata della tabella principale ha una data effettiva del 9/22/2010 alle 13:00. La persona B apporta modifiche e commette. Il commit non riesce perché le date effettive (nel database e nella sessione) non corrispondono.

+2

Vorrei risolvere il problema delle versioni nella modifica con i blocchi e il comportamento della contesa, non con il sistema di controllo delle versioni (che sarà una sorta di modifica). – JNK

risposta

23

Penso che tu abbia iniziato il percorso sbagliato.

In genere, per il controllo delle versioni o la memorizzazione dei dati cronologici si esegue una delle due (o entrambe) cose.

  1. si ha una tabella separata che imita la tabella originale + una colonna di data/ora per la data è stato cambiato. Ogni volta che un record viene aggiornato, si inseriscono i contenuti esistenti nella tabella della cronologia appena prima dell'aggiornamento.

  2. Si dispone di un database di magazzino separato. In questo caso è possibile sia la versione proprio come a # 1 di cui sopra o semplicemente snapshot una volta ogni tanto (oraria, giornaliera, settimanale ..)

Mantenere il numero di versione nella stessa tabella come il vostro normale uno ha diversi problemi. In primo luogo, le dimensioni del tavolo cresceranno come un matto. Ciò eserciterà una pressione costante sulle normali richieste di produzione.

In secondo luogo, aumenterà radicalmente la complessità della query per i join ecc. Al fine di garantire che venga utilizzata l'ultima versione di ogni record.

+0

Ciao Chris, so che è passato un po 'di tempo da quando hai risposto a questo, ma ecco un pensiero: diciamo che ho una singola app che scrive nel database dato. Sarebbe opportuno gestire il controllo delle versioni nel livello DAO dell'applicazione, lasciandolo completamente fuori dalla tabella? – corsiKa

+0

@glowcoder: Non sono del tutto sicuro di cosa intendi lasciandolo fuori dal tavolo. – NotMe

+0

Wow, come ho riletto quel commento/domanda, non sono nemmeno sicuro di cosa intendessi. Ma penso che la tua prima opzione sarà ciò che finirò con. Grazie ancora! – corsiKa

8

Ecco il mio approccio suggerito, che ha lavorato molto bene per me in passato:

  • Dimenticate il numero di versione.Invece, utilizzare StartDate e EndDate colonne
  • Scrivi un trigger per garantire che non vi siano intervalli di date che si sovrappongono per lo stesso ID, e che ci sia sempre e solo un record con un NULLEndDate per lo stesso ID (questo è il vostro record correntemente efficace
  • Inserire indici su StartDate e EndDate; questo dovrebbe dare prestazioni ragionevoli

Questa facilmente permetterà di segnalare per data:

select * 
from MyTable 
where MyReportDate between StartDate and EndDate 

o ottenere informazioni corrente:

select * 
from MyTable 
where EndDate is null 
2

Idea 3 funzionerà:

SELECT * FROM EMPLOYEE AS e1 
WHERE Position = 'Coder' 
AND Version = (
    SELECT MAX(Version) FROM Employee AS e2 
    WHERE e1.ID=e2.ID) 

Si vuole davvero usare qualcosa come una data , Che è molto più facile da programmare e monitorare, e utilizzerà la stessa logica (qualcosa come un colonna EffectiveDate)

EDIT:

Chris è del tutto corretto sullo spostamento queste informazioni dalla vostra tabella di produzione per le prestazioni, soprattutto se si prevedono aggiornamenti frequenti. Un'altra opzione sarebbe quella di fare una VEDETTA VEDERE che mostra solo la versione più recente delle informazioni di ogni persona, che si costruisce da questa tabella.

+2

La soluzione è tecnicamente corretta, ma sarei preoccupato per le prestazioni di tale subquery correlata. –

+0

@Joe - non sarà un colpo veloce su 100 m di file per essere sicuro. Se ha un indice di copertura su ID + Versione dovrebbe essere ragionevolmente veloce. – JNK

+0

Problema prestazionale notato - avrà sicuramente aggiornamenti frequenti (o almeno, molte delle tabelle nel framework devono essere preparate per gli aggiornamenti frequenti). Vedi la mia modifica per – corsiKa

2

Stai sbagliando in modo sbagliato. Mantenere un database in esecuzione dolce richiede che si abbia solo la quantità minima di dati nelle tabelle di produzione di cui si ha bisogno. Inevitabilmente, la conservazione dei dati storici con il live aggiunge ridondanza che complicherà le query e rallenterà le prestazioni, in più i vostri successori guarderanno davvero in modo approssimativo prima di inviarli al DailyWTF!

Creare invece una copia della tabella, ad esempio EmployeeHistorical, ma con la colonna ID non impostata come identità (è possibile scegliere di aggiungere una nuova colonna ID aggiuntiva e una colonna data/ora dataCreated). Quindi aggiungi un trigger alla tabella Employee che viene attivato all'aggiornamento & delete e scrive una copia della riga completa nella tabella Historical. E mentre lo stai catturando, l'ID dell'utente che esegue la modifica spesso si rivela utile a fini di controllo.

Generalmente quando lo faccio su una tabella attiva provo a creare la tabella storica in un database diverso, tra le altre cose questo riduce la frammentazione (e quindi la manutenzione) sul database principale ed è più facile gestire i backup - come gli archivi possono diventare molto grandi.

I problemi relativi al conflitto di modifica devono essere gestiti con la normale transazione del database e i meccanismi di blocco.Codificare ad hoc gli hack per emularli è sempre una perdita di tempo e di errori (alcune condizioni marginali a cui non avevate pensato di apparire sempre e per scrivere correttamente i blocchi devi davvero fare i conti con sempahores, che è decisamente non banale)

8

un approccio che ho disegnato per un database recente è quello di utilizzare le revisioni come segue:

  • Mantenete il vostro soggetto in due tabelle:

    1. negozi "dipendenti" un primario ID chiave e tutti i dati che non vuoi essere vers ionato (se ce n'è).

    2. "employee_revision" memorizza tutti i dati salienti sul dipendente, con una chiave esterna per la tabella dei dipendenti e una chiave esterna, "RevisionID" su una tabella denominata "revisione".

  • Creare una nuova tabella denominata "revisione". Questo può essere utilizzato da tutte le entità nel database, non solo dai dipendenti. Contiene una colonna Identity per la chiave primaria (o AutoNumber, o qualunque cosa il tuo database chiami una cosa del genere). Contiene anche colonne EffectiveFrom ed EffectiveTo. Ho anche una colonna di testo sulla tabella - entity_type - per ragioni di leggibilità umana che contengono il nome della tabella di revisione primaria (in questo caso "impiegato"). La tabella di revisione non contiene chiavi esterne. Il valore predefinito per EffectiveFrom è 1-Jan-1900 e il valore predefinito per EffectiveTo è 31-Dec-9999. Questo mi consente di non semplificare l'interrogazione della data.

i Assicurarsi che la tabella di revisione è ben indicizzato su (EffectiveFrom, EffectiveTo, RevisionId) ed anche su (RevisionId, EffectiveFrom, EffectiveTo).

Posso quindi utilizzare join e semplici confronti <> per selezionare un record appropriato per qualsiasi data. Ciò significa anche che le relazioni tra entità sono anche completamente versionate. In effetti, trovo utile utilizzare le funzioni a valori di tabella di SQL Server per consentire l'interrogazione molto semplice di qualsiasi data.

Ecco un esempio (presupponendo che non si vogliano i nomi dei dipendenti di versione in modo che se cambiano il loro nome, la modifica è valida storicamente).

-------- 
employee 
-------- 
employee_id | employee_name 
----------- | ------------- 
12351  | John Smith 

----------------- 
employee_revision 
----------------- 
employee_id | revision_id | department_id | position_id | pay 
----------- | ----------- | ------------- | ----------- | ---------- 
12351  | 657442  | 72    | 23   | 22000.00 
12351  | 657512  | 72    | 27   | 22000.00 
12351  | 657983  | 72    | 27   | 28000.00 

-------- 
revision 
-------- 
revision_id | effective_from | effective_to | entity_type 
----------- | -------------- | ------------ | ----------- 
657442  | 01-Jan-1900  | 03-Mar-2007 | EMPLOYEE 
657512  | 04-Mar-2007  | 22-Jun-2009 | EMPLOYEE 
657983  | 23-Jun-2009  | 31-Dec-9999 | EMPLOYEE 

Un vantaggio di memorizzare i metadati di revisione in una tabella separata è che è facile da applicare in modo coerente a tutti i soggetti. Un altro è che è più facile espanderlo per includere altre cose, come rami o scenari, senza dover modificare ogni tabella. Il motivo principale è che mantiene le tabelle delle entità principali chiare e ordinate.

(I dati e l'esempio sopra sono fittizi - il mio database non modella i dipendenti).

16

Quello che hai qui si chiama una dimensione a variazione lenta (SCD). Ci sono alcuni metodi collaudati per trattare con esso:

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

Ho pensato di aggiungere che dal momento che nessuno sembra chiamarla per nome.

+1

Grazie per aver indicato un motivo di progettazione comune –