Ko je uneo ili promenio podatke – misterija kolona CreatedUser, CreatedDate, UpdatedUser, UpdatedDate

Friday, 29.06.2012 – Zidar

Uvod

Podaci koje cuvamo u bazama podataka su zivi, menjaju se konstantno. Ponekad je sasvim u redu cuvati samo tekuce podatke, i promene nas ne interesuju. Mnogo cesce, medjutim, promene nas interesuju. Sta je sada, a sta je bilo pre. To nije jednostavno pratiti u relacionim bazama podataka. Nije jednostavno, ali svima treba i svi se nekako dovijaju.

Prvi pokusaj da se prate promene dolazi iz zelje da znamo ko je i kada uneo podatke i da li su podaci od tada promenjeni , ko ih je i kada promenio. To je minimum koji zelimo da pratimo. I njaveci pocetnici brzo nauce da dodaju u tabelu kolone

CreatedDate – kada je red dodat u tabelu,

CreatedUser – ko je dodao red u tabelu (‘kreirao rekord’),

UpdatedDAte – kada je podatak promenjen,

UpdatedUser – ko je promenio podatak

Najcesce se sav napor tu I zavrsava – dodamo kolone I uzdamo se u Boga da se kolone nekako popune. Pa kad zatreba, necemo znati sta je bilo ranije u tabeli, ali cemo barem moci da pitamo onoga ko je napravio promene sta je uradio i zasto.

U ovom clanku, cemu ovu elementarnu stvar podici na malo visi nivo, tako da bar izgledamo kao profesionalci. Kod je proveren na MS SQL 2005. Uz male izmene, sve bi radilo I na MS SQL 2000.

CreatedUser,CreatedDate,UpdatedUser, UpdatedDate

Neka je data baza sa dve tabele, MyMasterTable i NyLookupTable. Evo skripte za kreiranje tabela:

IF Object_ID(‘MyLookupTable’) IS NOT NULL DROP TABLE MyLookupTable

GO

CREATE TABLE MyLookupTable

(Code int NOT NULL PRIMARY
, MyDescription varchar(15) NOT NULL
,
CONSTRAINT unqMyLookupTable_MyDescription UNIQUE(MyDescription)
);

INSERT INTO MyLookupTable VALUES
(1,‘tra, la, la’);

INSERT INTO MyLookupTable VALUES
(2,‘bla bla, bla’);

INSERT INTO MyLookupTable VALUES
(3,‘trla baba lan’);

;

IF Object_ID(‘MyMasterTable’) IS NOT NULL DROP TABLE MyMasterTable

GO

CREATE TABLE MyMasterTable(

MyPK int NOT NULL PRIMARY KEY,
ColA int NULL
,
ColB varchar(15) NOT NULL,

CONSTRAINT
fkMyMasterTable_ColA_MyLookupTAble

FOREIGN
KEY (ColA) REFERENCES MyLookupTAble(Code)

)

GO

Dve tabele, primarni kljucevi, jedan strani kljuc i NULL/NOT NULL ogranicenja. Zbog ovoga NULL/NOT NULL, vec smo iznad proseka. 🙂
Znaci, pored podataka koji se cuvaju u tabeli, zelimo da pratimo ko je i kada uneo podatke, i ko je i kada promenio podatke. Ovo nam verovatno korisnik nece navesti u specifikaciji, ali mi smo profesionalci i znamo da je korisno znati ko sta radi i kada po tabeli, pa ovo radimo sebe radi.

Da bi znali ko je i kada uneo podatke, dodacemo dve kolone

– CreatedDate datetime – kada je red dodat u tabelu

– CreatedUser datetime – ko je dodao red u tabelu

Primetite da ne dozvoljavamo NULL vrednosti ni u jednoj od kolona. Obe kolone imaju DEFAULT vrednosti, zbog cega ne moramo da ih navodimo u listi INSERT komande.

Ovako bi izgledalo kreiranje tabele:

IF Object_ID(‘MyMasterTable’) IS NOT NULL DROP TABLE MyMasterTable

GO

CREATE TABLE MyMasterTable

(

— stvarni podaci:

MyPK int NOT NULL PRIMARY KEY

, ColA int NULL

, ColB varchar(15) NOT NULL

— ko je i kada uneo podatke:

, CreatedDate datetime NOT NULL DEFAULT (GetDate())

, CreatedUser nchar(30) NOT NULL DEFAULT (system_user)

— veza sa lookup tabelom

, CONSTRAINT fkMyMasterTable_ColA_MyLookupTAble

FOREIGN KEY (ColA) REFERENCES MyLookupTAble(Code)

)

;

Ako pretpostavimo da niko nece menjati CreatedDate ili CreatedUser za sada smo OK Znacemo uvek ko je i kada uneo podatke u tabelu. To je lepo, ali mi hocemo da znamo i ko je i kada promenio podatke u tabeli? Ako nista drugo, uvek mozemo da pitamo coveka zasto je to uradio i ste je bilo u tabeli pre promene.

Zasto je pretpostavka da niko, namerno ili ne namerno, nece menjati podatke u kolonama za pracenje promena sumnjiva i opasna? Sumnjiva zato sto nemate garanciju da se to nece desiti. Opana zato sto cete imati pogresne podatke, a ovi podaci vam trebaju samo kada nesto inace krene lose.

Dodajmo kolone za pracenje promena u unetim podacima.

IF Object_ID(‘MyMasterTable’) IS NOT NULL DROP TABLE MyMasterTable

GO

CREATE TABLE MyMasterTable

(

— stvarni podaci

MyPK int NOT NULL PRIMARY KEY

, ColA int NULL

, ColB varchar(15) NOT NULL

— ko je i kada uneo podatke:

, CreatedDate datetime NOT NULL DEFAULT (GetDate())

, CreatedUser nchar(30) NOT NULL DEFAULT (system_user)

— Pracenje promena

, UpdatedDAte datetime NULL

, UpdatedUser nchar(30) NULL

— veza sa lookup tabelom

, CONSTRAINT fkMyMasterTable_ColA_MyLookupTAble

FOREIGN KEY (ColA) REFERENCES MyLookupTAble(Code)

)

;

Moramo da dozvolimo NULL vrednosti za UpdatedDAte i UpdatedUser, jer te kolone treba da dobiju vrednost tekkad dodje do promene. Kako cemo da popunimo Updated.. kolone? Automatski, pomocu trigera.

Minimalni triger izgleda ovako:

CREATE TRIGGER trgMyMasterTable_UPD ON
MyMasterTable

FOR UPDATE

AS

UPDATE MyMasterTable

SET

UpdatedDAte=getdate()

, UpdatedUser = system_user

FROM inserted AS I

JOIN MyMasterTable as
M ON I.MyPK = M.MyPk

;

Da vidimo da li sve ovo radi.

INSERT INTO MyMasterTable (MyPK,ColA,ColB) VALUES (1, 1, ‘Dobar!’);

INSERT INTO MyMasterTable (MyPK,ColA,ColB) VALUES (2, 2, ‘Bolji!’);

INSERT INTO MyMasterTable (MyPK,ColA,ColB) VALUES (3, 1, ‘Naj Bolji’);

Naravno da radi, vidi se ko je uneo podatke i kada:

SELECT * FROM MyMasterTable;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDAte



UpdatedUser



1



1



Dobar!



20120629 13:33:53.400



Test\Zidar



NULL



NULL



2



2



Bolji!



20120629 13:34:02.693



Test\Zidar



NULL



NULL



3



1



Naj Bolji



20120629 13:34:02.703



Test\Zidar



NULL



NULL


U redu sa MyPK = 3 rec ‘najbolji’ je pogresno otkucana.
Pokusajmo da ispravimo gresku.

UPDATE
MyMasterTable

SET ColB = ‘Najbolji’

WHERE MyPK = 3

Sta sada imamo u tabeli:

SELECT * FROM MyMasterTable;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDAte



UpdatedUser



1



1



Dobar!



20120629 13:33:53.400



Test\Zidar



NULL



NULL



2



2



Bolji!



20120629 13:34:02.693



Test\Zidar



NULL



NULL



3



1



Najbolji



20120629 13:34:02.703



Test\Zidar



20120629 13:36:44.210



Test\Zidar


Za sada, sve izgledad dobro. Medjutim, uvek ima mesta za poboljsanja. Evo nekoliko poboljsanja koja se u praksi cesto zaboravljaju:

Prvo, ne zaboravimo da se izmena desava uvek posle unosenja podataka, pa treba da bude UpdateDAte>CreateDate.

Drugo, kolone UpdateDate i Updateduser moraju ili obe da budu NULL ili obe da budu NOT NULL. Podaci ne smeju d apostoje u jednoj a nedostaju u drugoj koloni.

Trece, svakao ne zelimo da bilo ko naknadno menja neku od kolona koje prate promene.

Prva dva poboljsanja se lako resavaju upotrebom CHECK constraints.

— Datum izmena mora doci kasnije od unosa

ALTER TABLE MyMasterTable WITH
CHECK

ADD CONSTRAINT ckMyMasterTable_UpdPosleCreate

CHECK (CreatedDate <
UpdatedDAte)

;

Primetite da nismo stavili (CreatedDate < UpdatedDAte OR UPdatedDate IS NULL). Nema potreba, jer kad izraz je u CHECK constraint jendk NULL smatra se da je pravilo zadovoljeno (nije prekrseno)

Da li ogranicenje radi:

UPDATE
MyMasterTable

SET UpdatedDAte = ‘19600310’ — 10 Mart 1960, veoma star datum 🙂

WHERE MyPK = 3

Ne ide, dobijemo ovu poruku:

Msg 547, Level 16, State 0, Line 1

The UPDATE statement conflicted with
the CHECK constraint
“ckMyMasterTable_UpdPosleCreate”. The
conflict occurred in database
“Dejan”, table
“dbo.MyMasterTable”.

The statement has been
terminated.

Za drugo poboljsanje, “kolone UpdateDate i Updateduser moraju ili obe da budu
NULL ili obe da budu NOT NULLâ€
, ogranicenje moze da se napise na vise nacina. Ja cu da
uradim ovako ovako:

Mora da bude zadovoljeno:

Ako (UpdatedDAte IS NULL) onda (UpdatedUser IS NULL)

i

Ako (UpdatedDAte IS NOT NULL) onda (UpdatedUser IS NOT NULL)

Ovo su dva izraza tipa A=>B. A => B ne moze da se
predstavi u Transact SQL. Medjutim, A=>B moze da se napise kao ((NOT A) OR
B), sto se lako pretvara u CHECK ogranicenje.

ALTER TABLE MyMasterTable WITH
CHECK

ADD CONSTRAINT
ckMyMasterTable_UpdatedDAteUpdatedUser_vs_NULL

CHECK (

(NOT (UpdatedDAte IS NULL) OR UpdatedUser IS NULL)

AND

(NOT (UpdatedDAte IS NOT NULL) OR (UpdatedUser IS NOT NULL))

)

;

Oslobadjanjem od zagrada izraz u CHECK ogranicenju bi mogao
da se naoko uprosti, ali bi bio znatno tezi za citanje i razumevanje.

Da li poslednje ogranicenje radi:

UPDATE
MyMasterTable

SET
UpdatedDAte = NULL

WHERE MyPK = 3

Msg 547, Level 16, State 0, Line 1

The UPDATE statement conflicted with
the CHECK constraint
“ckMyMasterTable_UpdatedDAteUpdatedUser_vs_NULL”. The conflict occurred in
database “Dejan”, table
“dbo.MyMasterTable”.

The statement has been
terminated.

Primetite da se CHECK constraint se aktivirala pre trigera i
zaustavila tarnsakciju.

Testiranje nije gotovo:

UPDATE
MyMasterTable

SET
UpdatedUser = NULL

WHERE MyPK = 3

Msg 547, Level 16, State 0, Line 1

The UPDATE statement conflicted with
the CHECK constraint
“ckMyMasterTable_UpdatedDAteUpdatedUser_vs_NULL”. The conflict occurred in
database “Dejan”, table
“dbo.MyMasterTable”.

The statement has been
terminated.

Sta bi bilo kad bismo uradili ovo:

UPDATE
MyMasterTable

SET

UpdatedUser = NULL

, UpdatedDAte = NULL

WHERE MyPK = 3

;

Sada nas je CHECK pustio da prodjemo, ali je triger odradio
svoj deo posla – zabelezio je ko je i kada promenio podatke. Ovom smo dobili
laznu promenu – promenili smo vreme polsenje promene, a da se u stvari u tabeli nije nista promenilo.

U svakom slucaju, CHECK constraint i trigger, u sadejstvu, nisu dozvolili da anuliramo podatke.

SELECT * FROM MyMasterTable;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDAte



UpdatedUser



1



1



Dobar!



20120629 15:02:16.940



Test\Zidar



NULL



NULL



2



2



Bolji!



20120629 15:02:27.820



Test\Zidar



NULL



NULL



3



1



Najbolji



20120629 15:02:27.830



Test\Zidar



20120629 15:05:06.337



Test\Zidar


Sta se desava ako pkusamo da uesemo UpdateUser i CreateUser
po zelji:

UPDATE
MyMasterTable

SET

UpdatedUser = ‘Yoggy Bear’

, UpdatedDAte = ‘2012-06-29 15:22:27.830’ — 20 minuta posle CreatedDate

WHERE MyPK = 3

;

SELECT * FROM MyMasterTable;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDAte



UpdatedUser



1



1



Dobar!



20120629 15:02:16.940



Test\Zidar



NULL



NULL



2



2



Bolji!



20120629 15:02:27.820



Test\Zidar



NULL



NULL



3



1



Najbolji



20120629 15:02:27.830



Test\Zidar



20120629 15:07:26.530



Test\Zidar


Triger je odradio svoj posao i nije uneti vreme koje smo mi zadali, nego vreme kad je proemna zaista upisana u atbelu. Podaci ipak nece biti vise tacni, jer smo izgubili stvarni datum promene. Ovo cemo da resimo kad budemo resavali poslednje od tri poboljsanja koje smo naveli: svakao ne zelimo da bilo ko naknadno menja neku od kolona koje prate promene.

Videli smo da se UpdateDate i UpdatedUser tesko mogu promeniti na nesto sto mi hocemo – trigger ce uvek upisati trenutak promene i ko je pokusao promenu, pa makar
menjali kolone UpdateDate i UpdatedUser.

Mozemo li da promenimo CraetedUser ili CreatedDate? Mozemo, ukoliko postujemo CHECK constraints.

UPDATE
MyMasterTable

SET

CreatedUser = ‘Piksi’ — korisnik uopste
ne postoji u sistemu

, CreatedDAte = ‘19020101’ — 100 godina ranije od originalnog CreatedDate

WHERE MyPK = 1

;

SELECT * FROM MyMasterTable;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDate



UpdatedUser



1



1



Dobar!



19020101 00:00:00.000



Piksi



20120629 15:19:03.993



Test\Zidar



2



2



Bolji!



20120629 15:02:27.820



Test\Zidar



NULL



NULL



3



1



Najbolji



20120629 15:02:27.830



Test\Zidar



20120629 15:11:32.287



Test\Zidar


Kako da sprecimo promenu CreatedDate, CreatedUser i UpdatedDAte,UpdatedUser?

Mozemo u trigeru da utvrdimo da li korisnik pokusava da promeni neku od ove cetiri kolone i da to sprecimo. U MS SQL, funkcija koja nam pomaze u ovom slucaju je UPDATE(<ime kolone>)

ALTER TRIGGER trgMyMasterTable_UPD ON
MyMasterTable

FOR UPDATE

AS

— Novo:

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

— Staro:

UPDATE MyMasterTable

SET

UpdatedDAte=getdate()

, UpdatedUser = system_user

FROM inserted AS I

JOIN MyMasterTable as
M ON I.MyPK = M.MyPk

;

GO

Triger je podugacak, ali vidite da su dodatne komande jednostavne. Ako se manja bilo koja od nasih kolona za pracenje proemna, obustavlja se cela tarnsakcija (rollback) i izlazi se iz trigera (return). Da
testiramo:

UPDATE
MyMasterTable

SET

CreatedUser = ‘Batman’ — korisnik uopste
ne postoji u sistemu

, CreatedDAte = ‘1906-06-29 13:33:53.400’ — 100 godina ranije od originalnog
CreatedDate

, UpdatedUser = ‘SpidreMan’ — korisnik uopste ne postoji u sistemu

, UpdatedDAte = ‘1959-06-29 13:33:53.400’ — 100 godina ranije od originalnog
CreatedDate

WHERE MyPK = 2

;

Msg 3609, Level 16, State 1, Line 1

The transaction ended in the trigger. The
batch has been aborted.

SELECT * FROM MyMasterTable

;

Znaci, imamo cetiri kolone, dve Created.. i dve Updated…
Created.. kolone se popunjavaju preko DEFAULT vrednosti i ne smeju biti NULL.

UPpadted.. kolone se popunjavaju kroz triger, u momentu izmene. CHECK constraints sprecavaju nelogican unos podataka.

Triger ne dozvoljava da se menjaju kolone koje prate ko je i kada uneo ili izmenio podatke.

Moguca je jos jedna sitna popravka. I dalje mozemo da belezimo lazne promene.

Lazna promena je kad uradimo UPDATE, i damo novu vrednost koja je jednaka staroj vrednosti.

SELECT *
FROM
MyMasterTable
WHERE
MyPK=1;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDate



UpdatedUser



1



1



Dobar!



19020101 00:00:00.000



Piksi



20120629 15:19:03.993



Test\Zidar


Za MyPK = 1, colB = ‘Dobar!’.

Pokusajmo ovo:

UPDATE MyMasterTable

SET ColB = ColB — ista vrednost ce se upisati preko postojece

WHERE MyPK=1

;

SELECT *
FROM
MyMasterTable
WHERE
MyPK=1;


MyPK



ColA



ColB



CreatedDate



CreatedUser



UpdatedDate



UpdatedUser



1



1



Dobar!



19020101 00:00:00.000



Piksi



20120629 15:40:14.890



Test\Zidar


Podaci se nisu promenili, ali se datum u UpdatedDate promenio! To nije dobro.

Lazne promene otkrivamo u trigeru, tako sto uporedimo tabelu INSERTED sa nasom tabelom. Nacin na koji poredimo atbele inserted I nasu originalnu tabelu radi u MS SQL 2005. Verzija 2000 ne koristi EXCEPT, ali se moze koristiti LEFT JOIN.

ALTER TRIGGER trgMyMasterTable_UPD ON
MyMasterTable

FOR UPDATE

AS

— Najnovije: da li se Inesrted razlikuje od
MyMasterTable?

— Ako je Inserted = MyMasterTable onda EXCEPT vraca
prazan skup <=> NOT EXISTS

IF NOT EXISTS

(

SELECT I.MyPk, I.ColA, I.ColB

FROM Inserted AS I

EXCEPT

SELECT M.MyPk, M.ColA, M.ColB

FROM
MyMasterTable AS M

JOIN Inserted AS I ON I.MyPk = M.MyPk

)

BEGIN

rollback

return

END

— Novo:

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

IF UPDATE(CreatedDate)

BEGIN

rollback

return

END

— Staro:

UPDATE
MyMasterTable

SET

UpdatedDAte=getdate()

, UpdatedUser = system_user

FROM inserted AS I

JOIN
MyMasterTable as M ON
I.MyPK = M.MyPk

return

;

GO

SELECT *

FROM
MyMasterTable

WHERE MyPK=1

;

MyPK ColA ColB CreatedDate CreatedUser UpdatedDAte UpdatedUser

1 1 Dobar! 19020101 00:00:00.000 Piksi 20120629 15:40:14.890 Test\Zidar

UPDATE MyMasterTable

SET ColB = ColB — ista vrednost ce se upisati preko postojece

WHERE MyPK=1

;

Msg 3609, Level 16, State 1, Line 1

The transaction ended in the trigger. The batch has been aborted.

SELECT *
FROM
MyMasterTable
WHERE
MyPK=1;


Zakljucak

Pokazali smo kako se moze obezbediti elementarno i minimalno pracenje ko je i kada uneo ili promenio podatke.

Nista narocito, reci ce mnogi, svako dete zna da doda kolone Created… i Updated…. Istina je, ali smo ovde sve podigli na malo ozbiljniji nivo, time sto smo sprecili nelogicne podatke i upis laznih promena.

A ako pogledamo pazljivo CHECK ogranicenja i triger koji smo morali napraviti, i nije sve bas tako jednostavno. 🙂

Za ozbiljan rad, ovo sto smo dobili nije dovoljno. Sta vredi znati ko je promenio podatke, ako ne znamo sta smo imali pre promene. Naravno da ako znamo ko je napravio proemnu, uvek mozemo da pitamo tu osobu zasto je to uradjeno I sta je bilo pre promene, ali to nije bas najprofesionalnije ponasanje, zar ne.

Sledeci clanak ce pokazati kako mozemo da cuvamo istoriju – stari podaci ne nestaju nego se cuvaju u zasebnoj tabeli. Trebace nam trigeri, a trebace i znati kako se istorijski podaci citaju. Pokazacemo klasican nacin, poznat preko 30 godina, a onda mozda pokazati i neka modrnija resenja.

Pokazana resenja ne treba shvatiti kao recept za rad. Cilj je da pocnemo razmisljati o stvarima koje lako zaboravimo – sacuvati podatke, ali i spreciti mugucu zloupotrebu ili kontaminaciju podataka, na visem nivou od FOREIGN KEY.

  1. 4 Responses to “Ko je uneo ili promenio podatke – misterija kolona CreatedUser, CreatedDate, UpdatedUser, UpdatedDate”

  2. IZvinjavam se za ocajan format teksta. Jednostavno ne umem da preuzmem kontrolu nad editorm. Kucati u ono malo prozorce je veoam nekomforno, za mene skoro nemoguce. Jedino sto funkcionise jeda otkucam tekst u Word-u, pa ga sacuvam kao RTF i onda RTF cut/past u prozorce. Onda se pojave ogromni proredi i sto je najgore tabele sa rezultatima kverija imaju ekran ipo praznine ispred.

    Zna li neko da nauci matorca iz kamenog doba kako se koristi blog editor?

    🙂

    By Zidar on Jun 29, 2012

  3. SELECT UpdatedDate, ChangeDetails
    FROM baze_podataka
    WHERE author = 'Zidar';

    UpdatedDate | ChangeDetails
    ------------------- | --------------------------------------------------------------
    09:50 03.07.2012 | Malo sam ti popravio formatiranje ;) Dugujes mi pivo i cevape
    -------------------- | --------------------------------------------------------------

    By Dejan on Jul 3, 2012

  4. Pozdrav, pre svega pohvale za azurnost sajta,
    kada je rec o history tabelama za jednu ili dve tabele to nije problem napravitii, ali kada se radi o nekoliko desetina tabela to vec postaje problem i oduzima puno vremena, tada bi bilo dobro imati proceduru koja za odabranu tabelu pravi history tabelu i odgovarajuce trigere…

    By Miodrag on Sep 2, 2012

  5. Zidar, ovo je zaista lep i koristan clanak jer u svakoj kompaniji u kojoj sam radio sam se susretao sa ovim problemom.

    Tokom mog rada u Oraclu poslednjih 7-8 godina dosta stvari se promenilo i sada postoji mnogo stvari koje olaksavaju pracenje promena. Pretpostavljam da i MSSQL ima slicne stvari ili ako nema onda ce ih imati uskoro.

    Na pocetku u Oracle 9i sam koristio CDC tabele (Change Data Capture): http://docs.oracle.com/cd/B28359_01/server.111/b28313/cdc.htm
    Tu izaberemo vrednosti koje hocemo da pratimo i da li da snimamo samo nove vrednosti u toj tabeli promena ili samo stare vrednosti ili obe vrednosti. Ovo je jako efikasan nacin pracenja promena jer nas ne zanima da li se promena vrsi iz triggera, stored procedura ili manuelno. A nema ni triggera i sve je mnogo brze.

    Kasnije u Oracle 10g je izaslo nesto zaista divno, Flashback technology. To nas je nekoliko puta izvuklo i stvarno predstavlja izuzetno korisnu stvar. Samo izaberemo vreme za koje hocemo da radimo query iz tabele i to je to!

    SELECT *
    FROM baze_podataka
    AS OF TIMESTAMP TO_TIMESTAMP(‘2012-11-07 09:30:00’, ‘YYYY-MM-DD HH:MI:SS’)
    WHERE author = ‘Zidar’;

    Zar nije ovo najelegantnije? Narocito je korisno kada radis neke unit tests i hoces da uvek imas isti pocetni skup podataka koje koristis bez obzira da li je neko te podatke kasnije menjao.

    By srki on Mar 20, 2013

Post a Comment