7 - 15:12:01.301242
FAQ1
W jaki sposób składować czas ostatniej modyfikacji tabeli?
W niektórych przypadkach chcemy wiedzieć, kiedy dana tabela bazy danych została zmodyfikowana. Istnieje kilka możliwych sposobów osiągnięcia tego celu.
1. Dodanie do tabeli pola Timestamp i zapisywanie w nim czasu ostatniej modyfikacji rekordu.
2. Przechowywanie informacji o ostatniej modyfikacji danej tabel w osobnej tabeli. Struktura takiej tabeli zawiera nazwę tabeli i pole TimeStamp ostatniej modyfikacji całej tabeli.
3. Realizacja przewidziana w tym FAQ - czyli generatory.
Pierwszy sposób
+ wiemy kiedy dana tabela została zmodyfikowana + wiemy również kiedy dowolny z rekordów został zmieniony + daje informacje rzeczywiste (dopiero po zatwierdzeniu transakcji) - dodatkowe pole zajmuje 8 bajtów danych na każdy rekord co może nie być bez znaczenia przy dużych tabelach z milionami rekordów - nie zawsze możemy dodać dodatkowe pole do tabeli nie zaburzając działania istniejącej aplikacji (np. w aplikacji może być instrukcja INSERT INTO .. SELECT * FROM itp.) - szybkość działania wymaga dodania dodatkowego indeksu descending zajmującego kolejne miejsce w bazie danych i spowalniającego operacje Insert i Update - nie daje informacji o modyfikacji w przypadku operacji usuńDrugi sposób można zrealizować dwojako
a) tabela zawierająca jeden rekord dla jednej monitorowanej tabeli - chyba same minusy? + daje informacje rzeczywiste (dopiero po zatwierdzeniu transakcji) - totalne wersjonowanie - masywne konflikty uaktualnień/konkurencyjności (lock) - brak informacji który dokładnie rekord został zmieniony b) tabela zawierająca "wiele" rekordów dla jednej modyfikowanej tabeli + daje informacje rzeczywiste (dopiero po zatwierdzeniu transakcji) - bardzo duża liczba wersji rekordów - wymaga usuwania starych rekordów a dla każdej modyfikacji tabeli źródłowej musi powstać nowy rekord. - brak informacji który dokładnie rekord został zmienionyTrzeci sposób wykorzystujący pewną sztuczkę z użyciem generatorów :)
+ szybkość działania + bezproblemowa konkurencyjność + brak wersjonowania - brak informacji który dokładnie rekord został zmieniony - informacje o zmianie pojawiają się dokładnie w chwili gdy generator został zmieniony niezależnie od tego czy był commit czy też rollbackImplementacja sposobu 1
Do tabeli dodajemy pole DT_MOD timestamp
1
2
3
4
5
6
2
3
4
5
6
CREATE TABLE TEST
(
ID INTEGER CONSTRAINT NK_TEST__ID NOT NULL CONSTRAINT PK_TEST PRIMARY KEY,
TABLE_DATA VARCHAR(100),
DT_MOD TIMESTAMP
);
Teraz tworzymy wyzwalacz który ustawi nam wartość pola DT_MOD w chwili utworzenia nowego rekordu lub jego zmiany
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
SET TERM ^ ;
CREATE TRIGGER MOD__TEST FOR TEST
ACTIVE BEFORE INSERT OR UPDATE /* OR DELETE */
POSITION 1000
AS
BEGIN
NEW.DT_MOD=CURRENT_TIMESTAMP;
END^
SET TERM ; ^
Dlaczego nie "OR DELETE"? - bo po usunięciu rekordu to i tak bez znaczenia - nie ma tego pola bo nie ma rekordu..
Teraz aby dowiedzieć się kiedy cała tabela została ostatnio zmodyfikowana wystarczy wykonać zapytanie:
1
2
2
SELECT MAX(DT_MOD) FROM TEST;
PLAN (TEST NATURAL)
Operacj jest wolna - tak więc trzeba dodać dodatkowy index malejący
1
2
3
4
2
3
4
CREATE DESCENDING INDEX IXD_TEST__DT_MOD ON TEST(DT_MOD);
SELECT MAX(DT_MOD) FROM TEST;
PLAN (TEST ORDER IXD_TEST__DT_MOD)
Implementacja sposobu 2a
Dodajemy dodatkową tabelę z nazwą tabeli i polem DT_MOD timestamp
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE TEST
(
ID INTEGER CONSTRAINT NK_TEST__ID NOT NULL CONSTRAINT PK_TEST PRIMARY KEY,
TABLE_DATA VARCHAR(100),
);
CREATE TABLE TABLE_DT_MOD
(
TABLE_NAME VARCHAR(31),
DT_MOD TIMESTAMP
);
CREATE INDEX IXD_TABLE_DT_MOD ON TABLE_DT_MOD(TABLE_NAME, DT_MOD);
Teraz tworzymy wyzwalacz który ustawi nam wartość pola DT_MOD rekordu dodatkowej tabeli w chwili utworzenia nowego rekordu lub jego zmiany
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
SET TERM ^ ;
CREATE TRIGGER TEST__DT_MOD FOR TEST
ACTIVE AFTER INSERT OR UPDATE OR DELETE
POSITION 1000
AS
BEGIN
/* to powoduje te problemy z konkurencyjnością */
UPDATE TABLE_DT_MOD SET DT_MOD=CURENT_TIMESTAMP WHERE TABLE_NAME='TEST';
END^
SET TERM ; ^
Teraz aby dowiedzieć się kiedy cała tabela została ostatnio zmodyfikowana wystarczy wykonać zapytanie:
1
SELECT DT_MOD FROM TABLE_DT_MOD WHERE TABLE_NAME='TEST';
Implementacja sposobu 2b
Dodajemy dodatkową tabelę z nazwą tabeli i polem DT_MOD timestamp
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE TEST
(
ID INTEGER CONSTRAINT NK_TEST__ID NOT NULL CONSTRAINT PK_TEST PRIMARY KEY,
TABLE_DATA VARCHAR(100),
);
CREATE TABLE TABLE_DT_MOD
(
TABLE_NAME VARCHAR(31),
DT_MOD TIMESTAMP
);
CREATE INDEX IXD_TABLE_DT_MOD ON TABLE_DT_MOD(TABLE_NAME, DT_MOD);
Teraz tworzymy wyzwalacz który doda nowy rekord z czasem ostatniej modyfikacji tabeli
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
SET TERM ^ ;
CREATE TRIGGER TEST__DT_MOD FOR TEST
ACTIVE AFTER INSERT OR UPDATE OR DELETE
POSITION 1000
AS
BEGIN
/* unikamy problemów z konkurencyjnością przez tworzenie nowych rekordów zamiast aktualizowania */
INSERT INTO TABLE_DT_MOD(TABLE_NAME, DT_MOD) VALUES('TEST', CURENT_TIMESTAMP);
/* usuwamy wszystkie wcześniejsze rekordy dla danej tabeli - tylko tu też mamy problemy z konkurencyjnoścą czyż nie? */
/* tak więc tą operację powinno wykonywać coś cyklicznie z zewnątrz */
DELETE FROM TABLE_DT_MOD WHERE TABLE_NAME='TEST' AND DT_MOD<CURENT_TIMESTAMP;
END^
SET TERM ; ^
Teraz aby dowiedzieć się kiedy cała tabela została ostatnio zmodyfikowana wystarczy wykonać zapytanie:
1
SELECT MAX(DT_MOD) FROM TABLE_DT_MOD WHERE TABLE_NAME='TEST';
Implementacja sposobu 3
Tworzymy generator który przechowuje dane timestamp :)
1
CREATE GENERATOR LAST_MOD__TEST;
Teraz potrzebujemy wyzwalacz na tabeli dla przykładu tabela TEST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SET TERM ^ ;
CREATE TRIGGER TEST_LAST_MOD FOR TEST
ACTIVE AFTER INSERT OR UPDATE OR DELETE
POSITION 0
AS
DECLARE VARIABLE VAR_CURRT TIMESTAMP;
DECLARE VARIABLE NEW_GEN BIGINT;
BEGIN
SELECT CURRENT_TIMESTAMP FROM RDB$DATABASE INTO :VAR_CURRT;
NEW_GEN = CAST(
EXTRACT(YEAR FROM :VAR_CURRT) ||
LPAD(EXTRACT(MONTH FROM :VAR_CURRT), 2, '0') ||
LPAD(EXTRACT(DAY FROM :VAR_CURRT), 2, '0') ||
LPAD(EXTRACT(HOUR FROM :VAR_CURRT), 2, '0') ||
LPAD(EXTRACT(MINUTE FROM :VAR_CURRT), 2, '0') ||
LPAD(CAST(EXTRACT(SECOND FROM CURRENT_TIMESTAMP)*10000 AS INTEGER), 6, '0')
AS BIGINT);
NEW_GEN = GEN_ID(LAST_MOD__TEST, -GEN_ID(LAST_MOD__TEST, 0) + :NEW_GEN);
/* EXECUTE STATEMENT 'SET GENERATOR LAST_MOD__TEST TO ' || :NEW_GEN; */
END^
SET TERM ; ^
Co robi wyzwalacz? Zmienia nam datę i czas na liczbę bigint którą składujemy w generatorze:
201401052217091234 - czyli 2014-01-05 22:17:09,1234
Do otrzymania spowrotem danych typu TimeStamp z takiego generatora, potrzebujemy dodatkowej uniwersalnej procedurki "GET_LAST_MOD_TIMESTAMP"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SET TERM ^ ;
CREATE PROCEDURE GET_LAST_MOD_TIMESTAMP (GEN_VAL BIGINT)
RETURNS (LAST_MOD_TIME TIMESTAMP )
AS
BEGIN
IF (GEN_VAL=0) THEN
LAST_MOD_TIME = NULL; ELSE
LAST_MOD_TIME =
CAST(
SUBSTRING(GEN_VAL FROM 1 FOR 4) || '-' ||
SUBSTRING(GEN_VAL FROM 5 FOR 2) || '-' ||
SUBSTRING(GEN_VAL FROM 7 FOR 2) || ' ' ||
SUBSTRING(GEN_VAL FROM 9 FOR 2) || ':' ||
SUBSTRING(GEN_VAL FROM 11 FOR 2) || ':' ||
SUBSTRING(GEN_VAL FROM 13 FOR 2) || '.' ||
SUBSTRING(GEN_VAL FROM 15 FOR 4)
AS TIMESTAMP
);
SUSPEND;
END^
SET TERM ; ^
Teraz można już pobierać i składować dane bez problemów z konkurencyjnością czy też wydajnością
1
2
3
4
5
6
7
2
3
4
5
6
7
SELECT
(SELECT
GLMT.LAST_MOD_TIME
FROM
GET_LAST_MOD_TIMESTAMP(GEN_ID(LAST_MOD__TEST, 0)) GLMT ) AS LAST_MOD_TIME_TABLE_TEST
FROM
RDB$DATABASE R
Pamiętaj jedynie jak już wcześniej wspomniałem - jako, że generatory nie są obiektami transakcyjnymi ich zmiana następuje natychmiast, niezależnie od tego czy wykonamy commit czy też rollback ani też kiedy wykonamy.
Możemy np. commit zrobić 10 minut później a generator już wcześniej pokazuje, że tabela została zmodyfikowana.
Używaj tego pomysłu jeśli uważasz go za wystarczająco ciekawy. A jak widać każdy sposób z wymienionych ma jakieś plusy lub minusy.
8 - 15:12:01.301242