Svi postovi sa bloga: Baze podataka

SQL baze podataka nam omogućavaju izvestan nivo kontrole pristupa podacima. Ova kontrola se postiže upotrebom GRANT komande i njenih varijacija GRANT SELECT, GRANT INSERT, GRANT UPDATE, GRANT DELETE. Da bi se primenila ovakva pravila pristupa bitno je da svaki korisnik sistema ima svoje jedinstveni nalog na nivou baze podataka. Mnogi proizvođači baza podataka dopunjuju standardnu ponudu sa dodatnim pravima pristupa kao što su column-level security i row-level security.

PostgreSQL nema mogućnost definisanja column i row level pristupa podacima. Da li postoji mogućnost da se ove tehnologije simuliraju ako su nam potrebne? Postoji, ali nažalost zahteva dodatni trud.

Kako napraviti prava pristupa podacima u okviru PostgreSQL-a? Pokazaću kroz jedan mali primer.

Predpostavimo da u okviru informacionog sistema imamo i podsistem za kadrovsku evidenciju i obračun zarada. Predpostavimo da imamo tri korisnika sistema:
- Pera šef službe prodaje
- Mika radnik u službi prodaje
- Laza šef službe nabavke
Dakle, po organizacionoj šemi Pera i Laza su na istom hijerarhijskom nivou dok je Mika podređen Peri.

Kreiramo tri korisnika sistema:

CREATE ROLE pera LOGIN;
CREATE ROLE mika LOGIN;
CREATE ROLE laza LOGIN;

U delu podsistema za kadrovsko i plate imamo jednu tabelu:

CREATE TABLE radnici (
ime VARCHAR(10) NOT NULL PRIMARY KEY,
datum_rodjenja DATE NOT NULL,
-- sluzba u okviru firme kojoj radnik organizaciono pripada
-- sluzbe su kodirane po semi: N = nabavka, P = prodaja
sluzba CHAR(1) NOT NULL CHECK (sluzba IN ('N', 'P')),
plata NUMERIC(10,2) NOT NULL DEFAULT 0 CHECK (plata > 0.00)
);

Ovu tabelu ćemo popuniti test podacima

INSERT INTO radnici (ime_radnika, datum_rodjenja, sluzba, plata)
VALUES ('pera', '1969-04-04', 'P', 50000);
INSERT INTO radnici (ime_radnika, datum_rodjenja, sluzba, plata)
VALUES ('mika', '1977-02-09', 'P', 35000);
INSERT INTO radnici (ime_radnika, datum_rodjenja, sluzba, plata)
VALUES ('laza', '1979-12-07', 'N', 48000);

Pera i Laza kao šefovi imaju sva prava pristupa nad podacima, dok Mika moze samo da čita podatke.

GRANT SELECT, INSERT, UPDATE, DELETE ON radnici TO pera;
GRANT SELECT                         ON radnici TO mika;
GRANT SELECT, INSERT, UPDATE, DELETE ON radnici TO laza;

Na ovaj način smo sprečili Miku da unosi nove radnike u tabelu ili da menja postojeće podatke (da poveća sebi platu ) ).

Ovim smo ujedno i završili sa pravima pristupa koji nam se nude nad tabelama. Ali, šta ako nam ovakva prava pristupa nisu dovoljna? Kako dalje poboljšati bezbednost podataka?

Column-level security ili vertikalna prava pristupa

Javlja nam se nov zahtev pred naš sistem - Platu smeju da vide samo šefovi!

Klasičan pristup rešavanju ovog problema je da se početna tabela razdvoji na dve tabele koje su u relaciji 1:1 i nad kojima se posebno daju prava pristupa. Pa ajde da to i uradimo.

Prvo, kreiramo novu tabelu u kojoj će se čuvati podaci o platama.

CREATE TABLE plate (
ime VARCHAR(10) NOT NULL PRIMARY KEY REFERENCES radnici,
plata NUMERIC(10,2) NOT NULL DEFAULT 0 CHECK (plata > 0.00)
);

Drugo, prepisujemo podatke o platama iz tabele Radnici u tabelu Plate.

INSERT INTO plate (ime, plata)
SELECT ime, plata
FROM radnici;

Trece, brišemo kolonu plata iz tabele Radnici.

ALTER TABLE radnici DROP COLUMN plata;

Četvrto, dodeljujemo prava pristupa nad tabelom Plate šefovima.

GRANT SELECT, INSERT, UPDATE, DELETE ON plate TO pera;
GRANT SELECT, INSERT, UPDATE, DELETE ON plate TO laza;

Ovim smo potpuno sprečili Miku da vidi podatke o plate.

Dodatno da bismo olakšali posao čitanja podataka kreiraćemo jedan pogled kroz koji ćemo objediniti podatke iz tabela Radnici i Plate.

CREATE VIEW radnici_i_plate (
ime, datum_rodjenja, sluzba, plata
) AS
SELECT r.ime, r.datum_rodjenja, r.sluzba, p.plata
FROM radnici AS r LEFT OUTER JOIN plate AS p ON r.ime = p.ime;

Takođe, šefovima moramo dati pravo da koriste ovaj pogled

GRANT SELECT ON radnici_i_plate TO pera;
GRANT SELECT ON radnici_i_plate TO laza;

Sada naši šefovi sada lako mogu da pročitaju podatke o radnicima i njihovim platama pomoću upita

SELECT ime, datum_rodjenja, sluzba, plata
FROM radnici_i_plate

Promenom strukture podataka obezbedili smo nekakav column-level security, to jest sprečili smo pojedine korisnike da pristupaju kolonama sa osetljivim podacima.
Pošto smo izmenuli strukturu meni je draže da ovakva prava pristupa nazivam ‘vertikalna prava pristupa’.

Do sad smo upotrebiljavali isključivo standardan SQL i gornji primeri se mogu pokrenuti i na drugim database engine-ima, a ne samo na PostgreSQL-u.

U sledećem nastavku ćemo videti kako se u PostgreSQL-u može implementirati Oracle-ov row-level security.

Uvaženi kolega i student na bečkom Tehničkom univerzitetu - Đorđe “bags” Torbica - je za svoj diplomski rad odabrao temu “Backup und Recovery in Oracle 10g” pri čemu je morao navesti/usporediti i backup mogućnosti kod MySQL RDBMS-a u odnosu na Oracle XE. Ovih dana je Đorđe svoj rad uspješno odbranio i stekao Bachelor titulu.

Pošto sam ja savjetima pomagao Đorđu pri izradi tog rada, on je obećao nesebično dati taj svoj rad na uvid drugim zainteresovanim bazoljupcima, koji posjećuju blog baze-podataka.net .

Napomena: Seminarski rad je na njemačkom jeziku.

Download (PDF, 247 kB): Diplomski/Seminarski rad na temu “Backup und Recovery in Oracle 10g”

Nastavljamo sa pitanjima i odgovorima - Marko Bujas, student informatike, pita gdje se može nabaviti Oracle software, a posebno mu trebaju Forms i Reports Builder.

Ja sam student informatike i trenutno imam predmet baze podataka.
Bio bih vam zahvalan ako biste mi rekli gdje mogu skinuti Oracle nije bitno koja je verzija bitno mi je samo da sadrži form i report builder. samo mi nemojte reći da skinem sa službene stranice oracla.
ako imate neki link bio bih vam jako zahvalan.

sviđaju mi se baze podataka i želim to i jednog dana raditi, ali bez potrebnog softvera to i
nije baš moguće zato molim pomagajte.

Unaprijed vam se zahvaljujem

Oracle software se može nabaviti …
… kod lokalnih Oracle sjedišta i partnera, ali prije svega na službenoj “Oracle Software Downloads” web stranici, na kojoj je izlistan kompletan software za download. Potrebno je samo odabrati šta vam treba i  kliknuti na odgovarajući link.

Evo za Marka i direktni linkovi:
Oracle Database (preporučujem verziju 10g, jer 11g još nije stabilna i nije rasprostranjena kao verzije 9i i 10g)
Oracle Developer Suite 10g

Marko nek ti bude sa srećom na ispitu!

Vi pitate - mi odgovaramo!

Radovan B. iz Novog Sada je postavio pitanje:

Potreban mi je određeni mehanizam, koji bi mogao na serveru da odradi dinamičko izvršenje određene SQL komande na MYSQL bazi. Znači, ja da mogu da pozovem proceduru na serveru, koja će parsirati SQL komandu zavisno od uslova i koja će se po potrebi izvršiti… U MySQL-u mi nedostaje baš to na što sam navikao u ORACLE-u…

Da li postoji mehanizam u MYSQL bazi (na serveru) analogan komandama EXECUTE IMMEDIATE i DBMS_SQL, koji postoje u ORACLE bazi?

A odgovor je - Da! Dinamičko izvršavanje u MySQL-u postoji, a to se dobija korištenjem komandi PREPARE i EXECUTE statement. Postoje neka ograničenja za korištenje u triggerima i ne mogu se koristiti sve DDL komande, ali većina SQL izraza se ipak mogu koristiti.

Ukoliko i vi imate neko pitanje, pošaljite ga na adresu forwardNOSPAMbaze-podataka.net

Na sajtu elitesecurity.org povremeno se pojave mozgalice - nesvakidašnji zadaci koje treba rešiti primenom SQL jezika. Jedna od prvih mozgalica bila je “Kako obezbediti jedinstvenost ako su dozvoljene NULL vrednosti u koloni”. Drugim rečima, ako vrednost nije NULL onda mora biti jedinstvena, a višestruke NULL vrednosti su dozvoljene. U nekim sistemima rešenje je trivijalno, jer se indeksi ignorisu NULL vrednosti . MS SQL to ne dozvoljava (iako recimo Access, takodje MS proizvod, to naprimer dozvoljava).

U verzijama MS SQL zaključno sa 2005 morali smo da pribegavamo trikovima. Triggeri, inedksirani view ili korisničke funkcija su korišćeni da se problem reši. MS SQL 2008 uvodi novinu - “filtered indexing”.

U Microsoft SQL Server 2008 problem se rešava elegantno i efikasno definsanjem objekta koji se zove  “unique filtered index” koji  moze da isključi  NULL vrednosti. Neka je data tabela:

CREATE TABLE dbo.T1(col1 INT NULL, col2 VARCHAR(10) NOT NULL);

Pretpostavimo da želimo da obezbedimo jedinstvenost za podatke u koloni col1, a da dozvolimo visetruke NULL vrednosti. Dovoljno je da ovako definišete indeks:

CREATE UNIQUE NONCLUSTERED INDEX idx_col1_notnull
ON dbo.T1(col1)
WHERE col1 IS NOT NULL;

Da testiramo index, pokušajmo da unesemo sledeće:

INSERT INTO dbo.T1(col1, col2) VALUES(1, 'a');
INSERT INTO dbo.T1(col1, col2) VALUES(1, 'a');

Drugi INSERT naravno ne prolazi. Međutim,  nećete imati problema da unesete više redova sa  NULL u col1:

INSERT INTO dbo.T1(col1, col2) VALUES(NULL, 'a');
INSERT INTO dbo.T1(col1, col2) VALUES(NULL, 'a');

Negde sam pročitao i da su uvedeni i novi tipovi (podtipovi?) podataka - DateOnly i TimeOnly koji rade upravo to - čuvaj samo vreme i samo datumski deo.  Cool, eh ….

:-) 

Pošto mi se mnogo dopalo korišćenje UDF funkcija u CHECK uslovima, pokušao sam da odgovorim na pitanje, koje se često sreće na forumima: rezervacija soba bez prebukiranja. Evo kako može da se reši:
Kreiramo tabelu u koju se upisuju rezervacije soba u jednom hotelu.
Tabela ima 3 kolone (Sobe, ZauzetoOD, ZauzetoDo)

- Soba ne sme da se prebukira. Drugim rečima, interval zauzeća (ZauzetoOd, ZauzetoDo) ne sme da se preklopi ni sa jednim drugim intervalom zauzeca za posmatranu sobu.
- Na dan odlaska jednog gosta iz sobe, drugi gost može ući u sobu.
- Sobe se napuštaju do 11AM, a preuzimaju u 1PM.
- Vreme između 11 i 1 se koristi za pripremu sobe za novog gosta. Znači, preklapanje na krajevima intervala se dopušta.


CREATE TABLE Rezervacije 
(
 Soba int NOT NULL
 , ZauzetaOd smalldatetime NOT NULL
 , ZauzetaDo smalldatetime NOT NULL
 PRIMARY KEY (Soba, ZauzetaOd, ZauzetaDo)
)
GO
INSERT INTO Rezervacije VALUES(100,'20080115','20080125')
INSERT INTO Rezervacije VALUES(100,'20080126','20080130')
INSERT INTO Rezervacije VALUES(100,'20080205','20080215')
GO
SELECT * FROM Rezervacije
Soba        Transakcija ZauzetaOd                                              ZauzetaDo                                             
----------- ----------- ------------------------------------------------------ --------------------
100         1           2008-01-15 00:00:00                                    2008-01-25 00:00:00
100         2           2008-01-26 00:00:00                                    2008-01-30 00:00:00
100         3           2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)

Šta treba proveriti da bi se sprečilo preklapanje intervala?
Dva intervale (a,b) i (x,y) mogu da se preklapaju u sledecim slučajevima:

--A------B------------------->
-----X---------Y------------->
--------A------------B------->
----X----------Y------------->
-------A-------------B------->
-----------X---Y------------->
-----------A---B------------->
-------X--------------Y------>

Ni jedan od ovih slučajeva nije dozvoljen. Mogli bismo da pokušamo da
napišemo izraz za preklapanje ovako:

Intervali (A,B) i (X,Y) se preklapaju kada je zadovoljeno
(
B Between X AND Y
OR
A Between X AND Y
OR
X Between A AND B
)

Ako uvedemo dodatni uslov, da se soba može preuzeti istog dana kada je
prethodni gost napušta, ne možemo više da koristimo BETWEEN.
U tom slučaju izraz više nije čitljiv, pa nam treba nešto jednostavnije.

Za pisanje, jednostavniji je dvostruko negativni uslov.
Dva intervala se NE preklapaju ako (A,B) leži potpuno van (X,Y)
Može se reći “Dva intervala se preklapaju, ako ni jedan ne leži potpuno izvan drugoga”

To je samo u dva slučaja:
(A,B) se završio pre nego što je (X,Y) počeo:

--------A---B----X--------Y----------->

(A,B) počinje tek kada je (X,Y) završen

-----------------X--------Y---A---B--->

Preklapanje: (NOT (B<=X OR A>=Y))   (1)
nema preklapanja:  (B<=X OR A>=Y)  (2)

Ako upotrebimo uslov (2) možemo napisati upit, koji prebrojava intervale koji se preklapaju sa (a,b):

DECLARE @a smalldatetime, @b smalldatetime, @Soba int
SELECT @a = '2008-01-31', @b = '2008-02-04', @Soba =100
-- X = ZauzetaOd, Y = ZauzetaDo
SELECT COUNT(*) FROM Rezervacije
WHERE (NOT ( (@b <= ZauzetaOd) OR (@a >= ZauzetaDo) ))
AND Soba = @Soba
-----------
          1
(1 row(s) affected)

Znači, ne želimo da dopustimo novu rezervaciju ako kveri

SELECT COUNT(*) FROM Rezervacije
WHERE (NOT ( (@b <= ZauzetaOd) OR (@a >= ZauzetaDo) ))
AND Soba = @Soba

vraća broj veći od nule.

CHECK constraint se proverava nakon unošenja novog reda u tabelu.
Ako je ograničenje narušeno, transakcija se odbacuje.
Zbog toga će navedeni kveri uvek vratiti barem broj 1 (jer svaki red se preklapa sa samim sobom).
Da to izbegnemo, zahtevaćemo da kveri ne vraća proveravani red.

Funkcija izgleda ovako:

CREATE FUNCTION dbo.PreklapanjeIntervala
 ( @a smalldatetime, @b smalldatetime, @Soba int)
 RETURNS smallint -- 0 = nema preklapanja, 1 = ima preklapanja
AS
 BEGIN
 DECLARE @Rezultat smallint
 SET @Rezultat = (
      SELECT COUNT(*) FROM Rezervacije
      WHERE (Soba = @Soba) -- sve radimo za tekucu sobu
      -- ovo bi trebalo da eliminise proveru samog sebe:
      AND NOT (Soba=@Soba AND ZauzetaOd=@a AND ZauzetaDo = @b)
      -- napokon provera preklapanja:
      AND NOT (
         (@b <= ZauzetaOd)
         OR
         (@a >= ZauzetaDo)
        )
     )
 RETURN @Rezultat
 
 END
GO

Sada može ovo: 

SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ---------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)
ALTER TABLE Rezervacije
DROP CONSTRAINT ck_NeSmeSePrebukiratiSoba
GO
-- CHECK CONSTRAINT koji sprecava prebukiranje soba :
ALTER TABLE Rezervacije
ADD CONSTRAINT
 ck_NeSmeSePrebukiratiSoba
 CHECK (  dbo.PreklapanjeIntervala(ZauzetaOd, ZauzetaDo, Soba) = 0 )
 GO
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ --------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)
Pokušajmo da prebukiramo sobu:
INSERT INTO Rezervacije VALUES(100,'2008-02-01','2008-02-04')
(1 row(s) affected)
INSERT INTO Rezervacije VALUES(100,'2008-01-20','2008-02-04')
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.
INSERT INTO Rezervacije VALUES(200,'2008-01-20','2008-02-04')
(1 row(s) affected)
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ---------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(5 row(s) affected)
INSERT INTO Rezervacije VALUES(300,'2008-01-20','2008-02-04')
(1 row(s) affected)

Da probamo sa sobom 200:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-04','2008-02-20')
(1 row(s) affected)
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ --------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
        200 2008-02-04 00:00:00                                    2008-02-20 00:00:00
        300 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(7 row(s) affected)

Unesimo ponovo isti interval:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-04','2008-02-20')
Server: Msg 2627, Level 14, State 1, Line 1
Violation of PRIMARY KEY constraint 'PK__Rezervacije__239E4DCF'.
Cannot insert duplicate key in object 'Rezervacije'.
The statement has been terminated.

Pokušajmo ponovo približno isti interval:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-08','2008-02-20')
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ------------------------------------------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
        200 2008-02-04 00:00:00                                    2008-02-20 00:00:00
        300 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(7 row(s) affected)

Šta ako naknadno promenimo neku rezervaciju, tako da se narušava zabrana dvostrukog bukiranja?
Pokušajmo prvu rezervaciju za sobu 100 (15 januar - 25 Januar 2008) da produžimo do 1 februara 2008.
Ista soba je već bukirana od 26 Januara do 30 januara.

UPDATE Rezervacije
SET ZauzetaDo = '2008-02-01'
WHERE Soba = 100
AND ZauzetaOd = '2008-01-15'
AND ZauzetaDo = '2008-01-25'
Server: Msg 547, Level 16, State 1, Line 1
UPDATE statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.

Pokazali smo dve stvari - kako se proverava preklapanje intervala i kako se to koristi u funkciji da se obebedi zabrana prebukiranja soba.

 :-)
 

Pošto mi se mongo dopalo korišćenje UDF funkcija u CHECK uslovima, pokušao sam da odgovorim na pitanje koje se često sreće na forumima: rezesrvacija soba bez prebukiranja. Evo ako može da se reši:
Kreiramo tabelu u koju se upisuju rezervacije soba u jednom hotelu.
Tbela ima 3 kolone (Sobe, ZauzetoOD, ZauzetoDo)
Soba ne sme da se prebukira. Drugim recima, interval zauzeca (ZauzetoOd, ZauzetoDo)
Ne sme da se preklopi ni sa jednim drugim intervalom zauzeca za posmatranu sobu.
Na dan odlaska jednog gosta iz sobe, drugi gost moze uci u sobu.
Sobe se napustaju do 11AM a preuzimaju u 1PM. Vreme izmeddju 11 i 1 se koristi
za pripremu sobe za novog gosta. Znaci, preklapanje na krajevima intervala se dopusta.


CREATE TABLE Rezervacije 
(
 Soba int NOT NULL
 , ZauzetaOd smalldatetime NOT NULL
 , ZauzetaDo smalldatetime NOT NULL
 PRIMARY KEY (Soba, ZauzetaOd, ZauzetaDo)
)
GO
INSERT INTO Rezervacije VALUES(100,'20080115','20080125')
INSERT INTO Rezervacije VALUES(100,'20080126','20080130')
INSERT INTO Rezervacije VALUES(100,'20080205','20080215')
GO
SELECT * FROM Rezervacije
Soba        Transakcija ZauzetaOd                                              ZauzetaDo                                             
----------- ----------- ------------------------------------------------------ --------------------
100         1           2008-01-15 00:00:00                                    2008-01-25 00:00:00
100         2           2008-01-26 00:00:00                                    2008-01-30 00:00:00
100         3           2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)

Šta treba proveriti da bi se sprečilo preklapanje intervala?
Dva intervale (a,b) i (x,y) mogu da se preklapaju u sledecim slučajevima:

--A------B------------------->
-----X---------Y------------->
--------A------------B------->
----X----------Y------------->
-------A-------------B------->
-----------X---Y------------->
-----------A---B------------->
-------X--------------Y------>

Ni jedan od ovih slucajeva nije dozvoljen. Mogli bismo da pokušamo da
napisemo izraz za preklapanje ovako:

Intervali (A,B) i (X,Y) se preklapaju kada je zadovoljeno
(
B Between X AND Y
OR
A Between X AND Y
OR
X Between A AND B
)

Ako uvedemo dodatni uslov, da se soba može preuzeti istog dana kada je
prethodni gost napušta, ne možemo više da koristimo BETWEEN.
U tom slučaju izraz više nije čitljiv, pa nam treba nešto jednostavnije.

Za pisanje, jednostavniji je dvostruko negativni uslov.
Dva intervala se NE preklapaju ako (A,B) lezi potpuno van (X,Y)
Moze se reci “Dva intervala se preklapaju ako ni jedan ne lezi potpuno izvan drugoga”

To je samo u dva slučaja:
(A,B) se završio pre nego što je (X,Y) počeo:

--------A---B----X--------Y----------->

(A,B) počijnje tek kada je (X,Y) završen

-----------------X--------Y---A---B--->

Preklapanje: (NOT (B<=X OR A>=Y))   (1)
nema preklapanja:  (B<=X OR A>=Y)  (2)

Ako upotrebimo uslov (2) možemo napisati kveri koji
prebrojava intervale koji se preklapaju sa (a,b):

DECLARE @a smalldatetime, @b smalldatetime, @Soba int
SELECT @a = '2008-01-31', @b = '2008-02-04', @Soba =100
-- X = ZauzetaOd, Y = ZauzetaDo
SELECT COUNT(*) FROM Rezervacije
WHERE (NOT ( (@b <= ZauzetaOd) OR (@a >= ZauzetaDo) ))
AND Soba = @Soba
-----------
          1
(1 row(s) affected)

Znači, ne želimo da dopustimo novu rezervaciju ako kveri

SELECT COUNT(*) FROM Rezervacije
WHERE (NOT ( (@b <= ZauzetaOd) OR (@a >= ZauzetaDo) ))
AND Soba = @Soba

vraća broj veći od nule.

CHECK constraint se proverava nakon unosenja novog reda u tabelu.
Ako je ogranicenje narušeno, transakcija se odbacuje.
Zbog toga se navedebni kveri uvek vratiti barem broj 1
(jer svaki red se preklapa sa samim sobom)
Da to izbegnemo, zahtevaćemo da kveri ne vraca proveravani red.

Funkcija izgleda ovako:

CREATE FUNCTION dbo.PreklapanjeIntervala
 ( @a smalldatetime, @b smalldatetime, @Soba int)
 RETURNS smallint -- 0 = nema preklapanja, 1 = ima preklapanja
AS
 BEGIN
 DECLARE @Rezultat smallint
 SET @Rezultat = (
      SELECT COUNT(*) FROM Rezervacije
      WHERE (Soba = @Soba) -- sve radimo za tekucu sobu
      -- ovo bi trebalo da eliminise proveru samog sebe:
      AND NOT (Soba=@Soba AND ZauzetaOd=@a AND ZauzetaDo = @b)
      – napokon provera preklapanja:
      AND NOT (
         (@b <= ZauzetaOd)
         OR
         (@a >= ZauzetaDo)
        )
     )
 RETURN @Rezultat
 
 END
GO

Sada može ovo: 

SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ---------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)
ALTER TABLE Rezervacije
DROP CONSTRAINT ck_NeSmeSePrebukiratiSoba
GO
-- CHECK CONSTRAINT koji sprecava prebukiranje soba :
ALTER TABLE Rezervacije
ADD CONSTRAINT
 ck_NeSmeSePrebukiratiSoba
 CHECK (  dbo.PreklapanjeIntervala(ZauzetaOd, ZauzetaDo, Soba) = 0 )
 GO
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ --------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
(3 row(s) affected)
Pokušajmo da prebukiramo sobu:
INSERT INTO Rezervacije VALUES(100,'2008-02-01','2008-02-04')
(1 row(s) affected)
INSERT INTO Rezervacije VALUES(100,'2008-01-20','2008-02-04')
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.
INSERT INTO Rezervacije VALUES(200,'2008-01-20','2008-02-04')
(1 row(s) affected)
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ---------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(5 row(s) affected)
INSERT INTO Rezervacije VALUES(300,'2008-01-20','2008-02-04')
(1 row(s) affected)

Da probamo sa sobom 200:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-04','2008-02-20')
(1 row(s) affected)
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ --------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
        200 2008-02-04 00:00:00                                    2008-02-20 00:00:00
        300 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(7 row(s) affected)

Unesiom ponovo isti interval:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-04','2008-02-20')
Server: Msg 2627, Level 14, State 1, Line 1
Violation of PRIMARY KEY constraint 'PK__Rezervacije__239E4DCF'.
Cannot insert duplicate key in object 'Rezervacije'.
The statement has been terminated.

Pokušajmo ponovo priblizno isti interval:

INSERT INTO Rezervacije
  (Soba,  ZauzetaOd,   ZauzetaDo)
VALUES  (200,  '2008-02-08','2008-02-20')
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.
SELECT * FROM Rezervacije
Soba        ZauzetaOd                                              ZauzetaDo                                             
----------- ------------------------------------------------------ ------------------------------------------------------
        100 2008-01-15 00:00:00                                    2008-01-25 00:00:00
        100 2008-01-26 00:00:00                                    2008-01-30 00:00:00
        100 2008-02-01 00:00:00                                    2008-02-04 00:00:00
        100 2008-02-05 00:00:00                                    2008-02-15 00:00:00
        200 2008-01-20 00:00:00                                    2008-02-04 00:00:00
        200 2008-02-04 00:00:00                                    2008-02-20 00:00:00
        300 2008-01-20 00:00:00                                    2008-02-04 00:00:00
(7 row(s) affected)

Šta ako naknadno promenimo neku rezervaciju, tako da se narusšva zabrana dvostrukog bukiranja?
Pokušajmo prvu rezervaciju za sobu 100 (15 januar - 25 Januar 2008) da produžimo do 10 Marta 2008.
Ista soba je već bukirana od 26 Januara do 30 januara.

UPDATE Rezervacije
SET ZauzetaDo = '2008-02-25'
WHERE Soba = 100
AND ZauzetaOd = '2008-01-15'
AND ZauzetaDo = '2008-01-25'
Server: Msg 547, Level 16, State 1, Line 1
UPDATE statement conflicted with TABLE CHECK constraint 'ck_NeSmeSePrebukiratiSoba'.
The conflict occurred in database 'Zadaci', table 'Rezervacije'.
The statement has been terminated.

Pokazali smo dve stvari ‘ kako se proverava preklapanje intervala i kako se to koristi u funkciji da se obebedi zabrana prebukiranja soba.

 :-)
 

MS SQL  2000 doneo je jednu lepu novinu - mogućnost pisanja korisničkih funkcija. Uz to je tiho došla još jedna novina, slabo dokumentovana,  iako se može naći u Boks On Line. Korisničke funkcije mogu se pozvati iz CHECK constraints. Funkcije ko funkcije, mogu da pozivaju redove iz posmatrane tabele, ali i drugih tabela. Ovo nam omogućava da izbegnemo proceduralno programiranje (trigere) i da neka ograničenja za koje smo morali koristiti Stored Procedures ili čak front-end funkcije, sada možemo da postavimo na nivo tabele, tamo gde i treba.

Na jednostavnom primeru pokazaćemo kako to radi.

Dobrotvorna organizacija daje pomoc gradjanima. Svaki gradjanin ima svoj ‘racun’. Novac se isplacuje višekratno, u manjim iznosima. Nikome se ne sme isplatiti više od 100 dinara ukupno.

Krairana je tabela ‘Zaduzenja’ sa tri kolone - Transakcija, Racun, IznosZaduzenja.
IznosZaduzenja je ono sto se isplaćuje u jednoj transakciji. Ukupan zbir IznosZaduzenja po računu ne sme da predje 100.

Ovo su ograničenja koja želimo da postavimo:
Pretpostavljamo IznosZaduzenja mora biti pozitivan, ne mozž biti nula ili manji.
Ukupan zbir svih IznosZaduzenja po Racunu ne sme preći 100.

CREATE TABLE Zaduzenja – DROP TABLE Accounnt
(Transakcija int PRIMARY KEY
, Racun int NOT NULL
, IznosZaduzenja money NOT NULL CHECK (IznosZaduzenja > 0)
)

Ovako nasa tabela postuje  prvo ograničenje. Kako da sprečimo da se nekome isplati više od 100 dinara?

MS SQL, od verzije 2000 pa naovamo ima jednu malo poznatu osobinu - u CHECK constraints mogu se koristiti user defined functions.
Iskoristimo to. Napišimo prvo funkciju koja izračunava ukupno zaduženje po računu. Funkcija bi mogla ovako da izgleda:

CREATE FUNCTION dbo.UkupnoZaduzenje (@Racun int)
RETURNS money
AS
BEGIN
 DECLARE @Retval money
 SET @Retval = (SELECT SUM(IznosZaduzenja)
     FROM Zaduzenja WHERE Racun = @Racun
     )
 RETURN COALESCE(@Retval,0) – inace vraca NULL kad ne postoji ni jedan red u in line kveriju
END

Funkciju pozivamo ovako:
SELECT dbo.UkupnoZaduzenje(100)

Ovako pravimo CHECK constraint koji koristi nasu funkciju:


ALTER TABLE Zaduzenja
–DROP CONSTRAINT ck_UkupnoZaduzenjeManjeod100
ADD CONSTRAINT ck_UkupnoZaduzenjeManjeod100
 CHECK
  (
   100 >= dbo.UkupnoZaduzenje (Racun)
  )

Da vidimo da li radi:

INSERT INTO Zaduzenja VALUES (1,100,50)
(1 row(s) affected)


SELECT * FROM Zaduzenja
Transakcija Racun       IznosZaduzenja       
———– ———– ———————
          1         100               50.0000

(1 row(s) affected)

Dodajmo jos 25 dinara na isti racun:
INSERT INTO Zaduzenja VALUES (2,100,25)
(1 row(s) affected)

Sada imamo u tabeli Zaduzenja:
Transakcija Racun       IznosZaduzenja       
———– ———– ———————
          1         100               50.0000
          2         100               25.0000

Ukupno azduzenje za Racun=100 jeste 75.
Pokušajmo da dodamo 30 dinara, što bi podiglo ukupno zaduženje na 105.

INSERT INTO Zaduzenja VALUES (3,100,30)

Rezultat:
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with COLUMN CHECK constraint ‘ck_UkupnoZaduzenjeManjeod100′.
The conflict occurred in database ‘master’, table ‘Zaduzenja’, column ‘Racun’.
The statement has been terminated.

Još 25 moze da prodje:
INSERT INTO Zaduzenja VALUES (3,100,25)

(1 row(s) affected)

i ništa vise:

INSERT INTO Zaduzenja VALUES (4,100,0.001)

Ponovo dobijamo
Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with COLUMN CHECK constraint ‘ck_UkupnoZaduzenjeManjeod100′.
The conflict occurred in database ‘Zadaci’, table ‘Zaduzenja’, column ‘Racun’.
The statement has been terminated.

SELECT * FROM Zaduzenja

Transakcija Racun       IznosZaduzenja       
———– ———– ———————
          1         100               50.0000
          2         100               25.0000
          3         100               25.0000

(3 row(s) affected)

Ne moramo više da poštovanje ograničenja obezbeđujemo na front endu, nit SQL programiranjem (trigeri i stored procedure). Kod u funkciji nije proceduralni, ne maskiramo funkcijom nikakav kursor.  Onako kako i treba da bude.

-)

Sun je već uvrstio sve MySQL proizvode u svoj portfolio, a akcenat je stavljen na MySQL Enterprise verziju.

Ukratko - prema službenim objavama, cilj će biti uska povezanost svih komponenti potrebnih za funkcionisanje jednog informacionog sistema, odnosno integrisana ponuda hardwarea (Sunovi serveri), softwarea (Solaris OS i OpenSolaris, Java, MySQL i td.) i servisnih usluga (consulting, implementation, support …), tako da je ova integrisana end-to-end kombinacija prvenstveno namijenjena srednjim i velikim firmama. A šta sa malim firmama?

Male će se vjerovatno morati osloniti na MySQL community verziju, te PHP i Apache na nekom shared serveru. Support i konsultacije će im pružati Google, a implementaciju jedan od samoukih srednjoš… pardon studenata… :)

Agencija Gartner piše da će fokus biti i na OpenSource tržištu, odnosno na jačanju SAMP platforme (umjesto L u LAMP, stavljeno je S za Solaris), ali ja bih tu platformu prije nazvao SAJM (Solaris Apache Java MySQL) ili eventualno SAMJP, a prihod bi se trebao ostvarivati uglavnom preko licenci za support i consulting.

Jonathan Schwartz, Sun CEO, je pored klasičnih menadžerskih/marketinških izjava kao npr. ova:
Our enterprise customers worldwide can now take advantage of MySQL’s market-leading open source database on your choice of platform, OS and language with less risk — for up to a 90% lower total cost of ownership over many traditional database solutions.”

napisao i da će Sun nastaviti uspješnu saradnju sa kompletnom OpenSource zajednicom, te da će M u LAMP platformi i dalje biti njen sastavni dio.

Za one koji se pitaju “I šta sad?“, Mårten Mickos, bivši MySQL CEO, a sadašnji Senior Vice President - Database Group, Sun - daje konkretan odgovor:”Simply put, it’s business as usual, only better.

But now you can leverage MySQL’s and Sun’s joint ability to provide you a complete suite of support options that enable you to rapidly build and deploy efficient, effective and secure applications and services. You can enjoy your total cost of ownership saving and benefit from our expanding roadmap and rates of innovation.

Čitav proces spajanja ove dvije firme će trajati još nekoliko mjeseci, tako da ćemo konkretne rezultate ove akvizicije vidjeti tek krajem ove godine. Do tada - hvala na pažnji - slijedi vremenska prognoza.

TOAD je po meni najbolji alat za rad sa bazama podataka, a koristim ga za rad sa Oracle i MySQL bazama.

Pored komercijalnih verzija, možete skinuti i Freeware verzije, koje važe 60 dana. Nakon isteka probnog roka, samo deinstalirate taj Freeware i ponovo instalirate.

Freeware verzije možete naći na ovoj adresi: http://www.toadworld.com/Downloads/Freeware/tabid/80/Default.aspx

blogodak blog

Blogodak?

Blogodak je vaš pogled na domaću blogosferu. Prijavite se i napravite sopstvenu listu blogova koje pratite.

O projektu

Podrška

MyCity.co.yu

DevProTalk