Projekt bazy danych dla zmian?

125

W projekcie mamy wymóg przechowywania wszystkich wersji (historii zmian) dla jednostek w bazie danych. Obecnie mamy 2 zaprojektowane propozycje dla tego:

np. dla Podmiotu „Pracownik”

Projekt 1:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"

Projekt 2:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- In this approach we have basically duplicated all the fields on Employees 
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName, 
      LastName, DepartmentId, .., ..)"

Czy jest inny sposób na zrobienie tego?

Problem z "Projektem 1" polega na tym, że musimy analizować XML za każdym razem, gdy potrzebujesz dostępu do danych. Spowolni to proces, a także doda pewne ograniczenia, na przykład nie możemy dodawać złączeń w polach danych wersji.

Problem z „Projektem 2” polega na tym, że musimy zduplikować każde pole we wszystkich obiektach (mamy około 70-80 elementów, dla których chcemy zachować poprawki).

Ramesh Soni
źródło
1
FYI: Na wszelki wypadek, gdyby to mogło pomóc .Sql server 2008 i nowsze mają technologię, która pokazuje historię zmian w tabeli. Odwiedź simple-talk.com/sql/learn-sql-server/ ... aby dowiedzieć się więcej i jestem pewien, że DB's jak Oracle też będzie miał coś takiego.
Durai Amuthan.H
Pamiętaj, że niektóre kolumny mogą same przechowywać XML lub JSON. Jeśli tak nie jest teraz, może się to zdarzyć w przyszłości. Lepiej upewnij się, że nie musisz zagnieżdżać takich danych w innych.
jakubiszon

Odpowiedzi:

38
  1. Czy nie umieścić to wszystko w jednej tabeli z IsCurrent atrybutu dyskryminatora. To po prostu powoduje problemy, wymaga kluczy zastępczych i wielu innych problemów.
  2. Projekt 2 ma problemy ze zmianami schematu. Jeśli zmienisz tabelę Employees, musisz zmienić tabelę EmployeeHistories i wszystkie powiązane z nią sprocesy. Potencjalnie podwaja wysiłek związany ze zmianą schematu.
  3. Projekt 1 działa dobrze i jeśli zostanie wykonany prawidłowo, nie kosztuje dużo pod względem wydajności. Możesz użyć schematu xml, a nawet indeksów, aby obejść możliwe problemy z wydajnością. Twój komentarz dotyczący parsowania XML jest poprawny, ale możesz łatwo utworzyć widok za pomocą xquery - który możesz dołączyć do zapytań i dołączyć do niego. Coś takiego...
CREATE VIEW EmployeeHistory
AS
, FirstName, , DepartmentId

SELECT EmployeeId, RevisionXML.value('(/employee/FirstName)[1]', 'varchar(50)') AS FirstName,

  RevisionXML.value('(/employee/LastName)[1]', 'varchar(100)') AS LastName,

  RevisionXML.value('(/employee/DepartmentId)[1]', 'integer') AS DepartmentId,

FROM EmployeeHistories 
Simon Munro
źródło
25
Dlaczego mówisz, aby nie przechowywać tego wszystkiego w jednej tabeli z wyzwalaczem IsCurrent. Czy mógłby mi Pan wskazać kilka przykładów, w których mogłoby to być problematyczne.
Nathan W
@Simon Munro A co z kluczem podstawowym lub kluczem klastrowym? Jaki klucz możemy dodać do tabeli historii projektu 1, aby przyspieszyć wyszukiwanie?
gotqn
Zakładam proste SELECT * FROM EmployeeHistory WHERE LastName = 'Doe'wyniki w pełnym skanie tabeli . Skalowanie aplikacji nie jest najlepszym pomysłem.
Kaii
54

Myślę, że kluczowe pytanie, które należy tutaj zadać, brzmi: „Kto / co będzie korzystało z historii”?

Jeśli ma to być głównie do raportowania / czytelnej historii, wdrożyliśmy ten schemat w przeszłości ...

Utwórz tabelę o nazwie „AuditTrail” lub coś, co ma następujące pola ...

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[OldValue] [varchar](5000) NULL,
[NewValue] [varchar](5000) NULL

Następnie możesz dodać kolumnę „LastUpdatedByUserID” do wszystkich swoich tabel, która powinna być ustawiana za każdym razem, gdy wykonujesz aktualizację / wstawianie do tabeli.

Następnie możesz dodać wyzwalacz do każdej tabeli, aby przechwycić wszelkie wstawienia / aktualizacje, które mają miejsce, i utworzyć wpis w tej tabeli dla każdego zmienionego pola. Ponieważ tabela jest również dostarczana z „LastUpdateByUserID” dla każdej aktualizacji / wstawienia, możesz uzyskać dostęp do tej wartości w wyzwalaczu i użyć jej podczas dodawania do tabeli inspekcji.

Używamy pola RecordID do przechowywania wartości pola klucza aktualizowanej tabeli. Jeśli jest to klucz złożony, po prostu wykonujemy konkatenację ciągów znaków z „~” między polami.

Jestem pewien, że ten system może mieć wady - w przypadku mocno zaktualizowanych baz danych wydajność może zostać obniżona, ale w przypadku mojej aplikacji internetowej otrzymujemy znacznie więcej odczytów niż zapisów i wydaje się, że działa całkiem dobrze. Napisaliśmy nawet małe narzędzie VB.NET, które automatycznie zapisuje wyzwalacze na podstawie definicji tabeli.

Tylko myśl!

Chris Roberts
źródło
5
Nie ma potrzeby przechowywania wartości NewValue, ponieważ jest przechowywana w tabeli poddanej audytowi.
Petrus Theron
17
Ściśle mówiąc, to prawda. Ale - gdy w pewnym okresie czasu następuje wiele zmian w tym samym polu, przechowywanie nowej wartości sprawia, że ​​zapytania typu „pokaż wszystkie zmiany wprowadzone przez Briana” są o wiele łatwiejsze, ponieważ wszystkie informacje o jednej aktualizacji są przechowywane w jeden rekord. Tylko myśl!
Chris Roberts
1
Myślę, że sysnamemoże być bardziej odpowiednim typem danych dla nazw tabel i kolumn.
Sam
2
@Sam używając sysname nie dodaje żadnej wartości; to może być nawet mylące ... stackoverflow.com/questions/5720212/…
Jowen
19

Artykuł History Tables w programie Database Programmer blogu może być przydatna - obejmuje niektóre z kwestii poruszonych tu i omawia przechowywanie delt.

Edytować

W eseju History Tables autor ( Kenneth Downs ) zaleca prowadzenie tabeli historii składającej się z co najmniej siedmiu kolumn:

  1. Sygnatura czasowa zmiany,
  2. Użytkownik, który dokonał zmiany,
  3. Token identyfikujący rekord, który został zmieniony (gdzie historia jest utrzymywana oddzielnie od stanu bieżącego),
  4. Czy zmiana polegała na wstawieniu, aktualizacji czy usunięciu,
  5. Stara wartość,
  6. Nowa wartość,
  7. Delta (dla zmian wartości liczbowych).

Kolumny, które nigdy się nie zmieniają lub których historia nie jest wymagana, nie powinny być śledzone w tabeli historii, aby uniknąć nadużyć. Przechowywanie delty dla wartości liczbowych może ułatwić późniejsze zapytania, nawet jeśli można je wyprowadzić ze starych i nowych wartości.

Tabela historii musi być bezpieczna, a użytkownicy niesystemowi nie mogą wstawiać, aktualizować ani usuwać wierszy. Należy obsługiwać tylko okresowe czyszczenie, aby zmniejszyć całkowity rozmiar (i jeśli jest to dozwolone w przypadku użycia).

Mark Streatfield
źródło
14

Wdrożyliśmy rozwiązanie bardzo podobne do rozwiązania sugerowanego przez Chrisa Robertsa, które działa dla nas całkiem dobrze.

Jedyna różnica polega na tym, że przechowujemy tylko nową wartość. Stara wartość jest przecież przechowywana w poprzednim wierszu historii

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[NewValue] [varchar](5000) NULL

Powiedzmy, że masz tabelę z 20 kolumnami. W ten sposób musisz tylko zapisać dokładnie zmienioną kolumnę, zamiast zapisywać cały wiersz.

Kjetil Watnedal
źródło
14

Unikaj projektu 1; nie jest to zbyt przydatne, gdy trzeba będzie na przykład przywrócić stare wersje rekordów - automatycznie lub „ręcznie” za pomocą konsoli administratora.

Naprawdę nie widzę wad Projektu 2. Myślę, że druga, Tabela historii, powinna zawierać wszystkie kolumny obecne w pierwszej, tabeli Rekordy. Np. W mysql możesz łatwo stworzyć tabelę o takiej samej strukturze jak inna table ( create table X like Y). A kiedy masz zamiar zmienić strukturę tabeli Records w aktywnej bazie danych, i tak musisz użyć alter tablepoleceń - i nie ma dużego wysiłku, aby uruchomić te polecenia również dla tabeli historii.

Uwagi

  • Tabela rekordów zawiera tylko ostatnią wersję;
  • Tabela historii zawiera wszystkie poprzednie wersje rekordów w tabeli Rekordy;
  • Klucz podstawowy tabeli historii jest kluczem podstawowym tabeli Records z dodatkiem RevisionId kolumną;
  • Pomyśl o dodatkowych polach pomocniczych, takich jak ModifiedBy- użytkownik, który utworzył daną rewizję. Możesz także chcieć mieć pole DeletedBydo śledzenia, kto usunął daną wersję.
  • Zastanów się, co DateModifiedpowinno oznaczać - albo oznacza to, gdzie ta konkretna wersja została utworzona, albo będzie oznaczać, kiedy ta konkretna wersja została zastąpiona inną. Pierwsza z nich wymaga, aby pole znajdowało się w tabeli Rekordy i na pierwszy rzut oka wydaje się być bardziej intuicyjna; drugie rozwiązanie wydaje się jednak być bardziej praktyczne w przypadku usuniętych rekordów (data usunięcia tej konkretnej zmiany). Jeśli zdecydujesz się na pierwsze rozwiązanie, prawdopodobnie będziesz potrzebować drugiego pola DateDeleted(tylko jeśli potrzebujesz go oczywiście). Zależy od Ciebie i tego, co chcesz nagrać.

Operacje w Projekcie 2 są bardzo trywialne:

Modyfikować
  • skopiuj rekord z tabeli Records do tabeli History, nadaj mu nowy RevisionId (jeśli nie jest już obecny w tabeli Records), obsłuż DateModified (zależy od tego, jak ją zinterpretujesz, zobacz uwagi powyżej)
  • kontynuuj normalną aktualizację rekordu w tabeli Rekordy
Usunąć
  • wykonaj dokładnie to samo, co w pierwszym kroku operacji Modyfikuj. Obsługuj odpowiednio DateModified / DateDeleted, w zależności od wybranej interpretacji.
Cofnij usunięcie (lub wycofaj)
  • weź najwyższą (lub jakąś konkretną?) wersję z tabeli historii i skopiuj ją do tabeli Records
Lista historii zmian dla konkretnego rekordu
  • wybierz z tabeli historii i tabeli rekordów
  • zastanów się, czego dokładnie oczekujesz od tej operacji; prawdopodobnie określi, jakich informacji potrzebujesz z pól DateModified / DateDeleted (patrz uwagi powyżej)

Jeśli wybierzesz Projekt 2, wszystkie polecenia SQL potrzebne do tego będą bardzo łatwe, a także konserwacja! Być może będzie o wiele łatwiej, jeśli użyjesz kolumn pomocniczych ( RevisionId, DateModified) również w tabeli Rekordy - aby utrzymać obie tabele na dokładnie tej samej strukturze (z wyjątkiem unikalnych kluczy)! Pozwoli to na proste polecenia SQL, które będą odporne na wszelkie zmiany struktury danych:

insert into EmployeeHistory select * from Employe where ID = XX

Nie zapomnij skorzystać z transakcji!

Jeśli chodzi o skalowanie , to rozwiązanie jest bardzo wydajne, ponieważ nie transformujesz żadnych danych z XML-a tam iz powrotem, tylko kopiujesz całe wiersze tabeli - bardzo proste zapytania, przy użyciu indeksów - bardzo wydajne!

TMS
źródło
12

Jeśli musisz przechowywać historię, utwórz tabelę-cień z tym samym schematem co tabela, którą śledzisz, oraz kolumną „Data wersji” i „Typ zmiany” (np. „Usuń”, „aktualizuj”). Napisz (lub wygeneruj - patrz poniżej) zestaw wyzwalaczy do wypełnienia tabeli audytu.

Stworzenie narzędzia, które odczyta systemowy słownik danych dla tabeli i wygeneruje skrypt, który utworzy tabelę-cień i zestaw wyzwalaczy do jej zapełnienia, jest dość proste.

Nie próbuj używać do tego XML, przechowywanie XML jest dużo mniej wydajne niż natywny magazyn tabel bazy danych, z którego korzysta ten typ wyzwalacza.

ConcernedOfTunbridgeWells
źródło
3
+1 za prostotę! Niektórzy będą przeprojektowywać ze strachu przed późniejszymi zmianami, podczas gdy przez większość czasu żadne zmiany nie zachodzą! Ponadto znacznie łatwiej jest zarządzać historiami w jednej tabeli, a rzeczywistymi rekordami w innej niż mieć je wszystkie w jednej tabeli (koszmar) z pewną flagą lub statusem. Nazywa się „KISS” i zwykle na dłuższą metę cię wynagrodzi.
Jeach
+1 całkowicie się zgadzam, dokładnie to, co mówię w mojej odpowiedzi ! Proste i wydajne!
TMS
8

Ramesh, byłem zaangażowany w tworzenie systemu opartego na pierwszym podejściu.
Okazało się, że przechowywanie wersji w formacie XML prowadzi do ogromnego wzrostu bazy danych i znacznego spowolnienia działania.
Moje podejście polegałoby na stworzeniu jednej tabeli na jednostkę:

Employee (Id, Name, ... , IsActive)  

gdzie IsActive to znak najnowszej wersji

Jeśli chcesz powiązać dodatkowe informacje z wersjami, możesz utworzyć oddzielną tabelę zawierającą te informacje i połączyć ją z tabelami encji za pomocą relacji PK \ FK.

W ten sposób możesz przechowywać wszystkie wersje pracowników w jednej tabeli. Zalety tego podejścia:

  • Prosta struktura bazy danych
  • Brak konfliktów, ponieważ tabela staje się tylko do dołączania
  • Możesz przywrócić poprzednią wersję, po prostu zmieniając flagę IsActive
  • Nie ma potrzeby łączenia, aby uzyskać historię obiektów

Pamiętaj, że powinieneś pozwolić, aby klucz podstawowy był nieunikalny.

aku
źródło
6
Użyłbym kolumny „RevisionNumber” lub „RevisionDate” zamiast lub oprócz IsActive, aby można było wyświetlić wszystkie wersje w kolejności.
Sklivvz,
Użyłbym „parentRowId”, ponieważ daje to łatwy dostęp do poprzednich wersji, a także możliwość szybkiego znalezienia zarówno bazy, jak i końca.
chacham15
6

Sposób, w jaki widziałem to w przeszłości, jest

Employees (EmployeeId, DateModified, < Employee Fields > , boolean isCurrent );

Nigdy nie „aktualizujesz” tej tabeli (z wyjątkiem zmiany poprawności isCurrent), po prostu wstaw nowe wiersze. Dla danego EmployeeId tylko 1 wiersz może mieć isCurrent == 1.

Złożoność utrzymania tego może być ukryta przez widoki i wyzwalacze "zamiast" (w Oracle zakładam podobne rzeczy w innych RDBMS), możesz nawet przejść do zmaterializowanych widoków, jeśli tabele są zbyt duże i nie mogą być obsługiwane przez indeksy) .

Ta metoda jest w porządku, ale możesz skończyć z kilkoma złożonymi zapytaniami.

Osobiście bardzo podoba mi się sposób, w jaki robisz to w Design 2, tak samo jak robiłem to w przeszłości. Jest łatwy do zrozumienia, prosty we wdrożeniu i prosty w utrzymaniu.

Tworzy również bardzo niewielkie obciążenie dla bazy danych i aplikacji, zwłaszcza podczas wykonywania zapytań odczytu, co prawdopodobnie będziesz robić w 99% przypadków.

Dość łatwo byłoby również zautomatyzować tworzenie tabel historii i wyzwalaczy do utrzymania (zakładając, że byłoby to zrobione przez wyzwalacze).

Matthew Watson
źródło
4

Weryfikacja danych jest jednym z aspektów koncepcji czasowej bazy danych o okresie ważności . Przeprowadzono wiele badań i pojawiło się wiele wzorców i wytycznych. Napisałem obszerną odpowiedź z wieloma odniesieniami do tego pytania dla zainteresowanych.

Henrik Gustafsson
źródło
4

Podzielę się z wami moim projektem, który różni się od obu projektów tym, że wymaga jednej tabeli na każdy typ jednostki. Najlepszym sposobem opisania projektu bazy danych jest użycie ERD, oto moje:

wprowadź opis obrazu tutaj

W tym przykładzie mamy jednostkę o nazwie pracownik . user table przechowuje rekordy użytkowników, a entity i entity_revision to dwie tabele, które przechowują historię wersji dla wszystkich typów encji, które będziesz mieć w systemie. Oto jak działa ten projekt:

Dwa pola entity_id i revision_id

Każda jednostka w twoim systemie będzie miała swój własny unikalny identyfikator. Twój obiekt może przejść przez zmiany, ale jego entity_id pozostanie taki sam. Musisz zachować ten identyfikator jednostki w tabeli pracowników (jako klucz obcy). W tabeli encji należy również zapisać typ swojego podmiotu (np. „Pracownik”). Teraz, jeśli chodzi o revision_id, jak wskazuje jego nazwa, śledzi on wersje encji. Najlepszym sposobem, jaki znalazłem, jest użycie atrybutu Employer_id jako revision_id. Oznacza to, że będziesz mieć zduplikowane identyfikatory wersji dla różnych typów podmiotów, ale nie jest to dla mnie gratką (nie jestem pewien co do Twojego przypadku). Jedyną ważną uwagą jest to, że kombinacja entity_id i revision_id powinna być unikalna.

W tabeli entity_revision znajduje się również pole stanu, które wskazuje stan wersji. To może mieć jeden z trzech stanów: , lublatestobsoletedeleted (nie opierając się na bieżąco korekt pomaga wiele, aby zwiększyć zapytań).

Ostatnia uwaga na temat revision_id: nie utworzyłem klucza obcego łączącego Employer_id z revision_id, ponieważ nie chcemy zmieniać tabeli entity_revision dla każdego typu jednostki, którą moglibyśmy dodać w przyszłości.

WPROWADZENIE

Dla każdego pracownika , którego chcesz wstawić do bazy danych, dodasz również rekord do encji i rewizji encji . Te dwa ostatnie rekordy ułatwiają śledzenie przez kogo i kiedy rekord został wstawiony do bazy danych.

AKTUALIZACJA

Każda aktualizacja istniejącego rekordu pracownika zostanie zaimplementowana jako dwie wstawki, jedna w tabeli pracowników i jedna w rewizji encji. Drugi pomoże ci dowiedzieć się, przez kogo i kiedy rekord został zaktualizowany.

USUNIĘCIE

W celu usunięcia pracownika do rewizji entity_revision wstawiany jest rekord określający usunięcie i wykonane.

Jak widać w tym projekcie, żadne dane nigdy nie są zmieniane ani usuwane z bazy danych, a co ważniejsze, każdy typ jednostki wymaga tylko jednej tabeli. Osobiście uważam ten projekt za naprawdę elastyczny i łatwy w użyciu. Ale nie jestem pewien co do Ciebie, ponieważ Twoje potrzeby mogą być inne.

[AKTUALIZACJA]

Mając obsługiwane partycje w nowych wersjach MySQL, uważam, że mój projekt ma również jedną z najlepszych wydajności. Można podzielić entitytabelę za pomocą typepola, podczas gdy partycjonować entity_revisionza pomocą jego statepola. SELECTZnacznie zwiększy to liczbę zapytań, zachowując prostotę i przejrzystość projektu.

Mehran
źródło
3

Jeśli rzeczywiście potrzebujesz ścieżki audytu, pochyliłbym się w stronę rozwiązania tabeli audytów (wraz ze zdenormalizowanymi kopiami ważnej kolumny w innych tabelach, np. UserName .). Pamiętaj jednak, że gorzkie doświadczenie wskazuje, że pojedyncza tabela audytu będzie ogromnym wąskim gardłem na drodze; tworzenie indywidualnych tabel audytu dla wszystkich kontrolowanych tabel prawdopodobnie jest warte wysiłku.

Jeśli chcesz śledzić rzeczywiste wersje historyczne (i / lub przyszłe), standardowym rozwiązaniem jest śledzenie tej samej jednostki z wieloma wierszami przy użyciu kombinacji wartości początkowej, końcowej i czasu trwania. Możesz użyć widoku, aby ułatwić dostęp do bieżących wartości. Jeśli podejmiesz takie podejście, możesz napotkać problemy, jeśli dane wersjonowane odwołują się do danych podlegających zmianom, ale bez wersji.

Hank Gay
źródło
3

Jeśli chcesz zrobić pierwszą, możesz użyć XML również dla tabeli Pracownicy. Większość nowszych baz danych umożliwia wykonywanie zapytań w pola XML, więc nie zawsze jest to problem. I może być prostszy sposób na dostęp do danych pracowników, niezależnie od tego, czy jest to najnowsza, czy wcześniejsza wersja.

Spróbowałbym jednak drugiego podejścia. Można to uprościć, mając tylko jedną tabelę Employees z polem DateModified. EmployeeId + DateModified byłby kluczem podstawowym i można zapisać nową wersję, po prostu dodając wiersz. W ten sposób archiwizowanie starszych wersji i przywracanie wersji z archiwum jest również łatwiejsze.

Innym sposobem na zrobienie tego może być model przechowalni danych autorstwa Dana Linstedta. Zrobiłem projekt dla holenderskiego biura statystycznego, który korzystał z tego modelu i działa całkiem dobrze. Ale nie sądzę, by było to bezpośrednio przydatne w codziennym użytkowaniu bazy danych. Możesz jednak mieć kilka pomysłów, czytając jego artykuły.

Mendelt
źródło
2

Co powiesz na:

  • Numer identyfikacyjny pracownika
  • Data modyfikacji
    • i / lub numer wersji, w zależności od tego, jak chcesz go śledzić
  • ModifiedByUSerId
    • plus wszelkie inne informacje, które chcesz śledzić
  • Pola pracowników

Tworzysz klucz podstawowy (EmployeeId, DateModified) i aby uzyskać „bieżące” rekordy, po prostu wybierz MAX (DateModified) dla każdego identyfikatora pracownika. Przechowywanie IsCurrent jest bardzo złym pomysłem, ponieważ po pierwsze można go obliczyć, a po drugie, dane są zbyt łatwe do utraty synchronizacji.

Możesz także utworzyć widok zawierający tylko najnowsze rekordy i głównie używać go podczas pracy w aplikacji. Zaletą tego podejścia jest to, że nie masz duplikatów danych i nie musisz zbierać danych z dwóch różnych miejsc (aktualnych w Employees i zarchiwizowanych w EmployeesHistory), aby uzyskać całą historię lub wycofać itp.) .

gregmac
źródło
Wadą tego podejścia jest to, że tabela będzie rosła szybciej niż w przypadku korzystania z dwóch tabel.
cdmckay
2

Jeśli chcesz polegać na danych historycznych (do celów raportowania), powinieneś użyć struktury podobnej do tej:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds the Employee revisions in rows.
"EmployeeHistories (HistoryId, EmployeeId, DateModified, OldValue, NewValue, FieldName)"

Lub globalne rozwiązanie do aplikacji:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, OldValue, NewValue, FieldName)"

Możesz zapisać swoje wersje również w formacie XML, wtedy masz tylko jeden rekord dla jednej wersji. Będzie to wyglądać następująco:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, XMLChanges)"
dariol
źródło
1
Lepiej: korzystaj ze źródeł zdarzeń :)
dariol
1

Mieliśmy podobne wymagania i odkryliśmy, że często użytkownik chce tylko zobaczyć co zostało zmienione, niekoniecznie cofając jakiekolwiek zmiany.

Nie jestem pewien, jaki jest twój przypadek użycia, ale zrobiliśmy to, aby utworzyć i sprawdzić tabelę, która jest automatycznie aktualizowana o zmiany w jednostce biznesowej, w tym przyjazną nazwę wszelkich odniesień do kluczy obcych i wyliczeń.

Za każdym razem, gdy użytkownik zapisuje swoje zmiany, przeładowujemy stary obiekt, uruchamiamy porównanie, zapisujemy zmiany i zapisujemy jednostkę (wszystko odbywa się w ramach jednej transakcji w bazie danych na wypadek problemów).

Wydaje się, że działa to bardzo dobrze dla naszych użytkowników i oszczędza nam bólu głowy związanego z posiadaniem całkowicie oddzielnej tabeli kontroli z tymi samymi polami, co nasza jednostka biznesowa.

mattruma
źródło
0

Wygląda na to, że chcesz śledzić zmiany w określonych obiektach w czasie, np. ID 3, „bob”, „123 main street”, a następnie inny ID 3, „bob” „234 elm st” itd., Zasadniczo będąc w stanie zwymiotować historię zmian pokazującą każdy adres, pod którym był „bob”.

Najlepszym sposobem na to jest posiadanie pola „is current” w każdym rekordzie i (prawdopodobnie) znacznika czasu lub FK do tabeli daty / czasu.

Wstawki muszą wtedy ustawić „jest aktualny”, a także wyłączyć ustawienie „jest aktualny” na poprzednim rekordzie „jest aktualny”. Zapytania muszą określać „jest aktualne”, chyba że chcesz mieć całą historię.

Istnieją dalsze poprawki, jeśli jest to bardzo duża tabela lub spodziewana jest duża liczba poprawek, ale jest to dość standardowe podejście.

Steve Moon
źródło