2012-08-09 26 views
17

Mam 2 tabele, Table-A i Table-A-History.Jak przechowywać rekordy historyczne w tabeli historii w SQL Server

  • Table-A zawiera wiersze danych bieżących.
  • Table-A-History zawiera dane historyczne

chciałbym mieć najbardziej aktualny wiersz moich danych w Table-A i Table-A-History zawierający wiersze historycznych.

mogę myśleć 2 sposoby, aby to osiągnąć:

  1. gdy nowy wiersz danych jest dostępna, należy przesunąć bieżący wiersz z Table-A do Table-A-History i zaktualizować Table-A wiersz z najnowszych danych (za pośrednictwem insert into select lub select into table)

    lub

  2. gdy nowy wiersz danych jest dostępny, aktualizuje Table-A jest rzędu i włożyć ew wiersz do Table-A-History.

Jeśli chodzi o wydajność, to metoda 1 lub 2 jest lepsza? Czy istnieje lepszy inny sposób na osiągnięcie tego?

+2

Czy rozważałeś użycie wyzwalaczy w 'Tabela-A', aby utworzyć dla ciebie wiersze' Tabela-A-Historia'? Upewnij się, że ustawiono je jako ostatnie ([sp_settriggerorder] (http://msdn.microsoft.com/en-us/library/ms186762.aspx)). – HABO

+0

Nie, nie mam. Przyjrzę się wyzwalaczom. Dzięki. – Mausimo

Odpowiedz

18

Rejestrowanie zmian to coś, co zwykle robiłem przy użyciu wyzwalaczy w tabeli bazowej, aby rejestrować zmiany w tabeli dziennika. Tabela dziennika zawiera dodatkowe kolumny do zapisu użytkownika bazy danych, akcji i daty/czasu.

create trigger Table-A_LogDelete on dbo.Table-A 
    for delete 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'delete-deleted', @Now, * 
     from deleted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogDelete', @order = 'last', @stmttype = 'delete' 
go 
create trigger Table-A_LogInsert on dbo.Table-A 
    for insert 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'insert-inserted', @Now, * 
     from inserted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogInsert', @order = 'last', @stmttype = 'insert' 
go 
create trigger Table-A_LogUpdate on dbo.Table-A 
    for update 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'update-deleted', @Now, * 
     from deleted 
    insert into Table-A-History 
    select SUser_SName(), 'update-inserted', @Now, * 
     from inserted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogUpdate', @order = 'last', @stmttype = 'update' 

Wyzwalacze logowania powinny zawsze być ustawione na ostrzał ostatni. W przeciwnym razie kolejny wyzwalacz może przywrócić pierwotną transakcję, ale tabela dziennika będzie już zaktualizowana. To jest mylący stan rzeczy.

4

Co należy powiedzie ć na temat metody 3: Dokonaj Table-A widoku przeciwko Table-A-History. Wstaw do Table-A-History i pozwól właściwej logice filtrowania wygenerować Table-A. W ten sposób wstawiasz tylko do jednego stołu.

+1

Pomyślałem, że powinienem oddzielić tabele, ponieważ Table-A będzie zawierać ~ 10K rekordów, które są często używane. Gdzie, jak w tabeli historii będzie ogromna i będzie używana dużo mniej. 5-10% czasu w porównaniu do tabeli A. Czy nie byłoby lepiej, gdyby baza danych przeszukiwała często rekordy 10K, zamiast ogromnego stołu, jeśli łączyłabym tabelkę-A i tabelę-A-historii – Mausimo

+0

Prawdopodobnie, być może nie, w zależności od stosunku wstawek do selekcji. Można również utworzyć indeksowany widok tabeli (http://msdn.microsoft.com/en-us/library/dd171921%28v=sql.100%29.aspx). To może rozwiązać problem wyszukiwania bezpośrednio. – mwigdahl

3

Mimo że zajmuje więcej miejsca, posiadanie tabeli historii zawierającej również najnowszy zapis pozwoli zaoszczędzić Ci bólu podczas pisania raportów i zobaczenia, jak i kiedy nastąpiły zmiany. Coś, o czym warto pomyśleć w mojej opinii.

Jeśli chodzi o wyniki, oczekuję, że będą identyczne. Ale na pewno nie chciałbyś usunąć rekordu ("ruch" opcji 1) z tabeli "non-hist", ponieważ używasz referencyjnej integralności między dwiema tabelami, prawda?

+0

W porządku, zaktualizowałbym rekord w tabeli A. Tabela Table-A-History używa klucza zastępczego i obcego klucza łączącego z Tabelą A – Mausimo

2

wolałbym metoda 1
Ponadto, mam też utrzymać dotychczasowy rekord w tabeli historii zbyt
to zależy od potrzeby.

3

Opcja 1 jest OK. Ale masz metody 4 też :)

  1. Wstaw nowy rekord do tabeli,

  2. Move stary zapis do archiwum tabeli na regularne bazy MySQL planującego. Możesz zaplanować archiwizację danych w czasie minimalnego obciążenia, na przykład w godzinach nocnych.

+1

Opps, przepraszam. Ale pomysł jest taki sam. W przypadku, gdy nie chcesz tracić na wydajności w godzinach dziennych, rób to po prostu w nocy ;-) – Vahan

+0

Myślę, że pytanie dotyczy nie tylko INSERT, ale również UPDATE i DELETE. Co się stanie, jeśli wstawiony wiersz zostanie zaktualizowany lub usunięty tego samego dnia? W takim przypadku nie można śledzić zmian dokonanych tego samego dnia. (Jeśli pytanie dotyczy tylko WSTAWIÓW, to tabela kontrolna nie jest potrzebna, ponieważ żadne dane nie zostaną zmienione przy użyciu tylko INSERT.) – beawolf

36

Zasadniczo użytkownik chce śledzić/kontrolować zmiany w tabeli, zachowując niewielki rozmiar podstawowej tabeli.

Istnieje kilka sposobów rozwiązania tego problemu. Wady i zalety każdego sposobu omówiono poniżej.

1 - Audyt tabeli z wyzwalaczami.

Jeśli szukasz audytu tabeli (wstawia, aktualizuje, usuwa), spójrz na mój sposób na odzyskanie niechcianych transakcji - SQL Sobota slajdów w/kod - http://craftydba.com/?page_id=880. Spust, który wypełnia tabelę kontroli, może przechowywać informacje z wielu tabel, jeśli tak zdecydujesz, ponieważ dane są zapisywane jako XML. W związku z tym można usunąć działanie, jeśli to konieczne, analizując plik XML. Śledzi, kto i co spowodowało zmianę.

Opcjonalnie można mieć tabelę kontroli w jej własnej grupie plików.

Description: 
    Table Triggers For (Insert, Update, Delete) 
    Active table has current records. 
    Audit (history) table for non-active records. 

Pros: 
    Active table has smaller # of records. 
    Index in active table is small. 
    Change is quickly reported in audit table. 
    Tells you what change was made (ins, del, upd) 

Cons: 
    Have to join two tables to do historical reporting. 
    Does not track schema changes. 

2 - Skuteczne randki ewidencji

Jeśli nie zamierzamy oczyścić dane z tabeli audytu, dlaczego nie zaznaczyć wiersz jako usunięte, ale zachować ją na zawsze? Wiele systemów, takich jak ludzie miękcy, używa efektywnych randek, aby pokazać, czy rekord nie jest już aktywny. W świecie BI nazywa się to tabelą wymiarową typu 2 (wolno zmieniające się wymiary). Zobacz artykuł instytutu hurtowni danych. http://www.bidw.org/datawarehousing/scd-type-2/ Każdy rekord ma datę rozpoczęcia i zakończenia.

Wszystkie aktywne rekordy mają datę końcową równą zero.

Description: 
    Table Triggers For (Insert, Update, Delete) 
    Main table has both active and historical records. 

Pros: 
    Historical reporting is easy. 
    Change is quickly shown in main table. 

Cons: 
    Main table has a large # of records. 
    Index of main table is large. 
    Both active & history records in same filegroup. 
    Does not tell you what change was made (ins, del, upd) 
    Does not track schema changes. 

3 - przechwytywania zmian danych (Feature Enterprise).

Micorsoft SQL Server 2008 wprowadził funkcję przechwytywania zmian. Podczas śledzenia zmian danych (CDC) za pomocą czytnika LOG po tym fakcie, , brakuje informacji o tym, kto i co dokonał zmiany. Szczegóły MSDN - http://technet.microsoft.com/en-us/library/bb522489(v=sql.105).aspx

To rozwiązanie zależy od uruchomionych zadań CDC. Wszelkie problemy z agentem sql spowodują opóźnienia w wyświetlaniu danych.

Zobacz tabele przechwytywania danych zmian. http://technet.microsoft.com/en-us/library/bb500353(v=sql.105).aspx

Description: 
    Enable change data capture 

Pros: 
    Do not need to add triggers or tables to capture data. 
    Tells you what change was made (ins, del, upd) the _$operation field in 
    <user_defined_table_CT> 
    Tracks schema changes.  

Cons: 
    Only available in enterprise version. 
    Since it reads the log after the fact, time delay in data showing up. 
    The CDC tables do not track who or what made the change. 
    Disabling CDC removes the tables (not nice)! 
    Need to decode and use the _$update_mask to figure out what columns changed. 

4 - Change Tracking Feature (wszystkie wersje).

Micorsoft SQL Server 2008 wprowadził funkcję śledzenia zmian. W przeciwieństwie do CDC, ma wszystkie wersje; Jednak zawiera szereg funkcji TSQL, które musisz wywołać, aby dowiedzieć się, co się stało.

Został zaprojektowany w celu synchronizacji jednego źródła danych z serwerem SQL za pośrednictwem aplikacji. W TechNet działa cała ramka synchronizacji.

http://msdn.microsoft.com/en-us/library/bb933874.aspx http://msdn.microsoft.com/en-us/library/bb933994.aspx http://technet.microsoft.com/en-us/library/bb934145(v=sql.105).aspx

przeciwieństwie do CDC, można określić, jak długo ostatnie zmiany w bazie danych przed usunięte. Ponadto, wstawienia i usunięcia nie rejestrują danych. Aktualizacje rejestrują tylko to, które pole zostało zmienione.

Ponieważ synchronizujesz źródło serwera SQL z innym obiektem docelowym, działa to dobrze. Nie nadaje się do audytu, jeśli nie piszesz okresowej pracy, aby dowiedzieć się o zmianach.

Wciąż będziesz musiał przechowywać te informacje gdzieś.

Description: 
    Enable change tracking 

Cons: 
    Not a good auditing solution 

Pierwsze trzy rozwiązania będą służyć do kontroli. Podoba mi się pierwsze rozwiązanie, ponieważ używam go w dużym stopniu w moim środowisku.

poważaniem

John

Code Snippet od prezentacji (Database Autos)

-- 
-- 7 - Auditing data changes (table for DML trigger) 
-- 


-- Delete existing table 
IF OBJECT_ID('[AUDIT].[LOG_TABLE_CHANGES]') IS NOT NULL 
    DROP TABLE [AUDIT].[LOG_TABLE_CHANGES] 
GO 


-- Add the table 
CREATE TABLE [AUDIT].[LOG_TABLE_CHANGES] 
(
    [CHG_ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL, 
    [CHG_DATE] [datetime] NOT NULL, 
    [CHG_TYPE] [varchar](20) NOT NULL, 
    [CHG_BY] [nvarchar](256) NOT NULL, 
    [APP_NAME] [nvarchar](128) NOT NULL, 
    [HOST_NAME] [nvarchar](128) NOT NULL, 
    [SCHEMA_NAME] [sysname] NOT NULL, 
    [OBJECT_NAME] [sysname] NOT NULL, 
    [XML_RECSET] [xml] NULL, 
CONSTRAINT [PK_LTC_CHG_ID] PRIMARY KEY CLUSTERED ([CHG_ID] ASC) 
) ON [PRIMARY] 
GO 

-- Add defaults for key information 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_DATE] DEFAULT (getdate()) FOR [CHG_DATE]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_TYPE] DEFAULT ('') FOR [CHG_TYPE]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_BY] DEFAULT (coalesce(suser_sname(),'?')) FOR [CHG_BY]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_APP_NAME] DEFAULT (coalesce(app_name(),'?')) FOR [APP_NAME]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_HOST_NAME] DEFAULT (coalesce(host_name(),'?')) FOR [HOST_NAME]; 
GO 



-- 
-- 8 - Make DML trigger to capture changes 
-- 


-- Delete existing trigger 
IF OBJECT_ID('[ACTIVE].[TRG_FLUID_DATA]') IS NOT NULL 
    DROP TRIGGER [ACTIVE].[TRG_FLUID_DATA] 
GO 

-- Add trigger to log all changes 
CREATE TRIGGER [ACTIVE].[TRG_FLUID_DATA] ON [ACTIVE].[CARS_BY_COUNTRY] 
    FOR INSERT, UPDATE, DELETE AS 
BEGIN 

    -- Detect inserts 
    IF EXISTS (select * from inserted) AND NOT EXISTS (select * from deleted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'INSERT', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM inserted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

    -- Detect deletes 
    IF EXISTS (select * from deleted) AND NOT EXISTS (select * from inserted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'DELETE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

    -- Update inserts 
    IF EXISTS (select * from inserted) AND EXISTS (select * from deleted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'UPDATE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

END; 
GO 



-- 
-- 9 - Test DML trigger by updating, deleting and inserting data 
-- 

-- Execute an update 
UPDATE [ACTIVE].[CARS_BY_COUNTRY] 
SET COUNTRY_NAME = 'Czech Republic' 
WHERE COUNTRY_ID = 8 
GO 

-- Remove all data 
DELETE FROM [ACTIVE].[CARS_BY_COUNTRY]; 
GO 

-- Execute the load 
EXECUTE [ACTIVE].[USP_LOAD_CARS_BY_COUNTRY]; 
GO 

-- Show the audit trail 
SELECT * FROM [AUDIT].[LOG_TABLE_CHANGES] 
GO 

-- Disable the trigger 
ALTER TABLE [ACTIVE].[CARS_BY_COUNTRY] DISABLE TRIGGER [TRG_FLUID_DATA]; 

** Spójrz & dotyk tabeli audytu **

enter image description here

+0

Świetna lektura, dzięki za wgląd! Chcę tylko sprawdzić, czy zrozumiałem Twój fragment kodu. Masz tylko 1 tabelę "Zmiany tabeli dziennika", która będzie przechowywać zapisy z każdej innej tabeli, a rzeczywiste rekordy z tych tabel są przechowywane w formacie XML? W ten sposób masz tylko jedną tabelę kontroli? – Mausimo

+0

Fajną częścią jest to, że jest 2 w górę.Powiedzmy, że twoja firma robi podatki, twój okres przechowywania danych wynosi 7 lat. Możesz użyć kilku partycji tablic kontrolnych na chg_date. Zobacz moją prezentację na temat technik magazynowania danych. Z drugiej strony, jeśli sprzedajesz lody dla firmy, możesz zachować pokwitowania przez 2 lata. Wtedy jeden stół może być w porządku. –

+0

Czy "Wady" dla wyzwalaczy nie powinny zawierać zwiększonego czasu wstawiania/aktualizacji/usuwania? Czuję, że częścią właściwej decyzji jest zrównoważenie prędkości zapisu i prędkości zapytania. – Daniel

8

Najnowsze wersje serwera SQL (2016+ i Azure) mają tabele tymczasowe, które zapewniają dokładnie żądaną funkcjonalność, jako funkcja pierwszej klasy. https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables

Ktoś z Microsoftu prawdopodobnie przeczytał tę stronę. :)

+1

Dzięki za dodanie do tego. To pytanie pojawiło się jakiś czas temu. Przypadkowo pracuję nad nowym projektem, który ma podobne wymagania i faktycznie używam platformy Azure. Będę zaglądać do tabel czasowych. Twoje zdrowie! – Mausimo

Powiązane problemy