Šta je novo?

Kako kreirati REDNI_BR pomocu TRIGGER-a

mariaana

Čuven
Učlanjen(a)
23.02.2003
Poruke
67
Poena
609
Alat: Delphi
Baza: IB SQL Server

Kako pomocu TRIGGER-a kreirati polje za redni broj (mora da ide po redu - bez preskakanja) u tabeli, koje ce uredno raditi i kada vise korisnika istovremeno kreira nove slogove? Cini mi se da su tu pored TRIGGER-a presudni parametri TRANSAKCIJE? SQL majstori - pomagajte!:(
 
Malo kasnim zbog boleshtina, ali nadam se da nije suvise kasno ...

Otvoris isql.
Prvo u bazi kreiras jedan generator, recimo GENERATE_REDNI_BR :

CREATE GENERATOR GENERATE_REDNI_BR;
(commit; )

Zatim kreiras trigger koji po ubacivanju recorda u tabelu MOJA_TABELA na odgovarajuce polje ubacuje generisani redni broj:

[code:1]
SET TERM !! ;
CREATE TRIGGER CREATE_REDNI_BR FOR MOJA_TABELA
BEFORE INSERT POSITION 0 AS
BEGIN
NEW.REDNI_BR = GEN_ID(GENERATE_REDNI_BR, 1);
END
SET TERM ; !!
[/code:1]

Podrazumevajuci, naravno, da se u tabeli MOJA_TABELA nalazi definisani column REDNI_BR u koji pohranjujes taj generisani broj. Funkcija gen_id koristi kao prvi parametar ime generatora, a korak kao drugi (dakle, i++ inkrement). Da bi imao lepe "poravnate" vrednosti, mozes po kreiranju generatora da mu zadas pocetnu vrednost recimo sa "SET GENERATOR GENERATE_REDNI_BR TO 100000;"

Iako mozes da imas po jedan generator po tabeli, nema nikakve prepreke cak ni da koristis jedan generator za sve svoje tabele.
 
Silver, nisam siguran da ovakvo resenje nece da preskace neke brojeve, ako korisnici odustanu (ne potvrde transakciju). Imam neki primer koji se vezuje na maximalni postojeci redni broj koji ide otprilike ovako:
[code:1]
SET TERM !!;
CREATE TRIGGER TG_RBR FOR DOKUMENT
BEFORE INSERT AS
BEGIN
IF (NEW.REDNI_BR IS NULL) THEN
BEGIN
SELECT MAX(REDNI_BR) + 1 FROM DOKUMENT
INTO NEW.REDNI_BR;
IF (NEW.REDNI_BR IS NULL) THEN
NEW.REDNI_BR = 1;
END
END!!
SET TERM ;!!
[/code:1]
Ovo mi radi uredno iz jedne klijentske aplikacije, ali kada vise korisnika radi onda pada.

Bitna stvar je ovde verovatno, i to da li je polje REDNI_BR zahtevano - NOT NULL (znaci da li mora da dobije vrednost jos kod klijenta ili moze da se definise tek kad stigne na server), a pretpostavljam i parametri transakcije?

Imam jos neke slicne primere, ali prvi put baratam sa server bazama pa ne mogu da pohvatam redosled i zavisnost dogadjanja u klijentskoj aplikaciji i u bazi na serveru. Na primer osnovno: kada se okida trigger before insert neke tabele? Pretpostavljam da korisnik barata sa odgovarajucim DataSetovima u svojoj aplikaciji, recimo dodaje novi slog, upisuje vrednosti polja, postuje, i tek ako potvrdi transakciju, podaci se prosledjuju pravoj bazi na serveru. Da li je to trenutak kada se okida ovaj trigger definisan na serveru?

Ako ima neko kratko i jasno objasnjenje bice dobrodoslo (svi helpovi i tutorijali pisu nasiroko o transakcijama, procedurama, triggerima ali nisam naisao na prakticno objasnjenje dogadjanja na relaciji klijent - server i kakvu 'svest' ima baza na serveru o otvorenim klijentskim aplikacijama i obrnuto). U svakom slucaju, moracu, kad stignem, da proradim vise elementarnih primera sa najjednostavnijom tabelom i generatorima, triggerima...ctp;
 
Ne, onaj moj primer ne bi preskakao brojeve. Zasto ? Zato sto je trigger/event definisan u samoj bazi podataka, a ne na klijent datasetu. Znaci, kada radis client/server setup, na klijent masini radis samo sa subsetom koji je "donesen" na lokalni disk, nacesce kao rezultat nekog sql upita. Sve promene koje na njemu radis se rade na tzv. "change logu", ukljucujuci te sve insert, edit, append, post/cancel operacije. Tek kada zatvaras transakciju sa commit, change log izmene se primenjuju na samoj bazi na serveru. Ili se change log izmene odbacuju sa rollback zatvaranjem transakcije. Tek kada se uradi taj insert na datasetu u "pravoj" bazi na serveru, okine se trigger i popuni polje.
Onaj gore primer je sasvim dovoljan da ti kreira sigurne unique brojeve za neki ID ili slicno (radi u praxi bez problema). Ako ti to ne sluzi kao neki key, nego bas kao polje redni broj, onda je najbolje da to sam vodis kontrolu iz samog programa. U suprotnom, ako se i obrise neki record, imaces "rupe" u tim brojevima svejedno.

Konkretan rad sa transakcijama zavisi od alata i komponenti, ali generalno vazi:
[code:1]
pripremi transakciju;
try
startuj transakciju;
...
radi sta treba;
...
transaction.commit;
except
on EDatabaseError do transaction.rollback;
...
end;
[/code:1]

Kod tebe izgleda pravi problem to koliko je transakcija unique. Kod Kylixa i dbExpressa je transakcija opisana samo jednim recordom, a samom transakcijom se upravlja preko database connectiona koji prima taj record kao parametar. Tu transaction record ima jedno ID polje (int) koje treba da bude unique. Tu napravis samo proceduru koja kombinuje recimo IP adresu i tekuce vreme, tako da nema konflikta medju raznim masinama, ali ni sudara izmedju transakcija vodjenih sa jedne masine.
 
silverglider je napisao(la):
Ne, onaj moj primer ne bi preskakao brojeve. Zasto ? Zato sto je trigger/event definisan u samoj bazi podataka, a ne na klijent datasetu. Znaci, kada radis client/server setup, na klijent masini radis samo sa subsetom koji je "donesen" na lokalni disk, nacesce kao rezultat nekog sql upita. Sve promene koje na njemu radis se rade na tzv. "change logu", ukljucujuci te sve insert, edit, append, post/cancel operacije. Tek kada zatvaras transakciju sa commit, change log izmene se primenjuju na samoj bazi na serveru. Ili se change log izmene odbacuju sa rollback zatvaranjem transakcije. Tek kada se uradi taj insert na datasetu u "pravoj" bazi na serveru, okine se trigger i popuni polje.
Evo odradio sam elementarni primer sa ovim triggerom ctp; (primer je u priligu). Jadnostavna tabela DOKUMENT sa tri polja, PK (prim_kljuc), RED_BR i neko VARCHAR polje (nije bitno). Dva generatora, jedan je vezan za prim_kljuc, a drugi upotrebljen za ovaj trigger za RED_BR. Na formi su IB elementi za DB, baza, transakcija, IBDataSet, DataSource i DBgrid za prikaz. Dugmetima OPEN, NOVI, POST pokrecem odgovarajuce elementarne akcije IBDataSeta. Transakcija je otvorena na pocetku (na form_create), a potvrdjujem je ili odbacujem na dugmeta COMMIT, ROLLBACK. Kolko ja vidim, na svaki NOVI (append) kreira se automatski prim_kljuc (kako je definisano u IBDataSet-u), a na svaki POST odmah se kreira vrednost RED_BR, sto ce reci da se TRIGGER izvrasava kad god klijent u svojoj aplikaciji ubacuje u DataSet-u novi slog! Ako u medjuvremenu 'zavirim' u pravu bazu na IB Serveru, vidim da jos uvek nema nikakvih novih slogova u 'pravoj' tabeli. Tek kad u aplikaciji potvrdim transakciju, novi redovi se prebacuju na Server bazu (ili se ne prebacuju ako odbacim transakciju).

Zakljucak: iventi u triggerima BEFORE/AFTER - DELETE/INSERT/UPDATE se odnose na desavanja u klijentskim DataSetovima. Kako meni izgleda, iako su definisani na serveru, klijentska aplikacija ima 'svest' o triggerima, jos dok barata sa lokalnim DataSet-ovima, kao sto ima 'svest' i o NOT NULL poljima, kljucevima, CONSTRAINT-ima i sl. Ili meni nista nije jasno :confused: :confused:
*************************
Drugo, u mojoj pravoj aplikaciji RED_BR jeste neki prirodan kljuc. Radi se o nekoj vrsti knjige upisnika, tako da je RED_BR ujedno i identifikator dokumenta koji se upisuje i u dokument (znas ono kad ti u opstini izda resenje gde pise br 1234/03), sto znaci da se mora pamtiti u bazi, a i valjalo bi da ide po redu. U pravu si da je onda akcija brisanja slogova vrlo kriticna - trebalo bi je posebno kontrolisati iz programa...ali recimo da je to posebna tema, da ne sirimo sad...
 
Opasno je hendlati kljucna polja sa korisnicke strane i zato svi ti select max(id) upiti kad-tad prsnu...
Predlazem dva resenja:
1. Definisati sequence u bazi nad tim poljem, naravno ukoliko to baza podrzava
2. Napraviti tabelu za poljima IME_TABELE i VREDNOST_KLJUCA i u insert triggeru za tabelu u koju insertujemo citati i uvecavati kljuc...

Nadam se da ste razumeli sta je pisac hteo da kaze...
 
Evo, nesto sam se ponovo vratio na ovu problematiku, i cini mi se malo rascistio stvari. Gornja primedba stoji: triggeri se okidaju vezano za akcije u klijentskim aplikacijama (bez obzira sto se izvrsavaju na serveru). Znaci before insert se okida kad god neki klijent ubacuje novi slog u svojoj aplikaciji (DataSet-u) zapravo pri postovanju novog recorda, bez obzira sta ce se kasnije desiti sa prosledjivanjem na server - da li ce transakcija biti potvrdjena ili odbacena. To pokazuje i gornji primer koji ne zavrsava posao (ne redja redni broj po redu cak ni za jednog korisnika). Ovde je dat novi primer koji resava problem na ispravan nacin - uz pomoc vec navedenog trigera koji bira MAX postojeci redni broj, dodaje jedan i to stavlja u novi redni broj.

Sta je ovde bitno?! Akcije postovanja novog sloga i prihvatanja-prosledjivanja transakcije moraju se izvrsavati vezano jedna za drugom (u ovom primeru na dugme POST/COMMIT). Na taj nacin izbegavamo da neko postuje u svojoj aplikaciji - potrosi redni broj a posle eventualno odustane od prosledjivanja podataka na server (transaction.Rollback) sto bi ostavljalo rupe u nizu rednih brojeva. Drugo bitno - parametri transakcije postavljeni na read_committed, na ovaj nacin klijentska aplikacija ima svest o u medjuvremenu ubacenim slogovima kod drugih klijenata pa nece biti preklapanja.

Probajte: prevedite program i kreirajte bazu (u istom folderu). Pokrenite vise instanci programa i u svakoj dodajte novi slog. Zatim neke potvrdite a neke odbacite - redni brojevi se redjaju uredno po redu i bez preskakanja. Tu je i dugme za osvezavanje podataka da mozete da vidite sta su druge aplikacije (korisnici) uneli u medjuvremenu. Sve kombinacije prihvatanja ili otkazivanja funkcionisu uredno. Ne vidim razlog da ovo ne radi ispravno i u mrezi.

Eto toliko, da ne ostane nedoreceno, a mozda jos nekom zatreba da ispostuje ovakav zahtev za jedno polje!

Pozdrav svima :wave;
 
Ne znam sta bi sa attachmentom, evo jos jednom
 
Vidim da si koristio funkciju USE_I_U_SVOJE_KLJUSE :happy:
 
Nazad
Vrh Dno