Sunteți pe pagina 1din 175

INTRODUCERE

Începând cu anul universitar 2006-2007 şi, implicit, prima promoţie de bolognişti1,


disciplina Baze de date I a fost introdusă în al doilea an de studii şi la specializarea
Contabilitate şi Informatică de Gestiune. Probabil că mulţi şi-au adus aminte de bancul cu
„asta ne mai trebuia acuma”. Oricum, împreună cu studenţii de la specializarea Informatică
Economică, veterani în luptele de stradă cu bazele de date, vă puteţi reuni, măcar periodic, sub
sigla „Suferinţă fără frontiere”.
Poate suna paradoxal, dar nu avem de gând să vă ţinem o pledoarie lacrimogenă
despre importanţa bazelor de date pentru contabilitate şi tot ceea ce ţine de un sistem
informaţional. O (mică) parte dintre dvs. poate va găsi ceva interesant în cele peste 150 de
pagini. Ceea ce dorim să vă consiliem (iniţial voiam să scriem „să vă atragem atenţia”, dar
suna prea apăsător) este că, pentru a rămâne cu ceva fixat din curs şi, implicit, pentru a-l
promova, nu este atât de important să lecturaţi cartea de faţă, ci mai ales să o înţelegeţi şi să
exersaţi.
Pentru a ne încadra în limita procustiană de pagini, am scos din manual rezumatele
capitolelor şi tipurile de probleme de la examen. Acestea, împreună cu diverse listinguri
(secvenţe de comenzi SQL), diagrame şi alte materiale suplimentare sunt postate pe portal, la
secţiunea afectată disciplinei. Vă încurajăm, de asemenea, să folosiţi grupul de discuţii al
disciplinei petru a vă exprima neclarităţile (nedumeririle, consternările etc.) în speranţa unor
eventuale lămuriri (sau măcar intimidări ale profesorilor).
Încheiem urându-vă un sincer „să vă fie bazele de date uşoare !” şi, de asemenea,
propunem să începem materia cu un mic moment de reculegere pentru toate viitoarele
„victime” ale cursului Baze de date I.

Noiembrie 2006 - Ianuarie 2010

Echipa BD1: Marin Fotache, Cătălin Strâmbei, Liviu Creţu, Octavian Dospinescu, Florin Sîrbu

1
Beneficiari ai prevederilor Convenţiei de la Bologna
2 Baze de date I

Capitolul 1

DESPRE BAZE DE DATE

Obiective:

I. Argumentarea importanţei bazelor de date pentru sistemele


informaţionale actuale
II. Definirea bazelor de date, prin comparaţie cu organizarea datelor în
fişiere independente
III. Prezentarea frugală a principalelor modele de structurare a datelor
în bazele de date

Rezultate aşteptate:

Nici unul
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 3

1.1 NEVOIA DE BAZE DE DATE

După cum discutam în cursul de Instrumente software pentru afaceri, folosim bazele de date
pentru că avem memoria prea scurtă şi stăm prost cu calculele. Ca şi în alte privinţe, avem nevoie de
baze de date pentru că suntem nişte limitaţi. Deoarece trăim într-o lume aşezată pe un morman de
hârtii & hârţoage, ne este cu neputinţă să reconstituim ceea ce am făcut adineori, darămite ieri,
săptămâna trecută sau acum un an sau cinci. Necazul e că, de cele mai multe ori, trebuie să ştim nu
numai ce-am făcut noi, dar ce-au făcut şi colegii şi partenerii de afaceri.
Simplificând şi exagerând nepermis lucrurile, am putea spune că există doi poli între care
poate fi poziţionată orice problemă informatică. Pe de o parte, cel al chestiunilor interesante, nu
neapărat cu formule şi calcule complexe, care reclamă un anumit grad de ingeniozitate în rezolvare -
mult invocata şi aşteptata "fisă". Celălalt pol regrupează probleme în care complexitatea calculelor
rareori depăşeşte nivelul celor patru operaţii aritmetice elementare - adunare, scădere şi încă două; în
schimb, volumul informaţiilor şi zecile/sutele moduri de regrupare şi agregare a lor este deconcertant.
Fără a face concurenţă vreunui manual de filosofie, putem spune că, din păcate, ca şi în viaţă,
ponderea problemelor din a doua categorie – să le spunem plicticoase – este mult mai mare decât
ponderea problemelor cu adevărat interesante. Aceasta ar fi vestea proastă. Vestea bună este că se
câştigă enorm de mulţi bani din chestiunile plicticoase. O veste intermediară ar fi că, în majoritate,
problemele pe care le are de rezolvat un informatician presupun elemente din ambele categorii. De
fapt, cei doi poli de care vorbim au o existenţă virtuală, fiind utili mai degrabă din raţiuni didactice &
pedagogice.
Cert este că, încă de la începuturile sale, informatica a fost confruntată nu numai cu efectuarea
de calcule sofisticate, ştiinţifice, dar şi cu stocarea şi gestionarea unui volum de informaţii din ce în ce
mai mare. Astfel încât apariţia unor instrumente software dedicate gestiunii şi prelucrării datelor a fost
doar o problemă de timp.
Prin urmare, avem nevoie de baze de date pentru a păstra, într-un format utilizabil, date şi
informaţii legate de evenimente, tranzacţii etc. şi, la nevoie, de a le regăsi şi prelucra după cum ne cer
împrejurările. Bazele de date nu reprezintă singurul instrument de stocare şi prelucrare a informaţiilor.
Şi într-un banal fişier .DOC (document Word, WordPerfect...), prezentare PowerPoint sau foaie de
calcul (Excel, Lotus 1-2-3...) păstrăm date. Ca să nu mai vorbim de pagini Web. Problema este că în
documente, foi de calcul, prezentări, fişiere HTML etc. datele sunt slab structurate. Pentru a localiza
informaţiile trebuie folosite instrumente de căutare care să depisteze prezenţa unor cuvinte cheie,
eventual în preajma unor alte cuvinte cheie (cu o afacere de genul acesta s-au umplut de bani cei de la
Google).
Încheiem acest mini-paragraf cu două veşti. Cea bună este că, la acest moment, bazele de date
reprezintă cel mai bine structurat mod de păstrare şi scotocire a informaţiilor. Este motivul pentru care
piaţa produselor de lucru cu bazele de date se exprimă în valori de ordinul miliardelor de dolari.
Vestea rea ţine de faptul că, dintre toate datele şi informaţiile pe care le vehiculăm/gestionăm/prelu-
crăm pe hârtie, la telefon, pe bandă sau disc magnetic, optic etc., doar o mică parte sunt preluate şi
preluabile în bazele de date.
4 Baze de date I

1.2 LA ÎNCEPUT A FOST FIŞIERUL


Prima care a resimţit acut nevoia unor instrumente software dedicate administrării unor
cantităţi imense de informaţii a fost armata SUA. În a doua parte a anilor '50 Departamentul Apărării
al SUA a format un grup de specialişti pentru eborarea unui limbaj destinat aplicaţiilor administrative,
în care dificultatea majoră ţinea de volumul imens de resurse materiale şi financiare ce trebuia
"chivernisit" şi pentru care erau necesare rapoarte dintre cele mai diverse. La acel moment apăruse
FLOWMATIC, limbaj precursor celui care a fost considerat câteva decenii regele informaticii
economice – COBOL (Common Business Oriented Language).
Arhitectura aplicaţiilor de acest tip – specifică nu numai COBOL-ului, ci multor limbaje din a
III-a generaţie, denumită flat-files architecture – tradusă, într-o doară, în româneşte drept fişiere
independente – este reprezentată în figura 1.1. Specific acestui mod de lucru, referit ca file-based sau
flat files (fişiere independente), este faptul că fiecare dată (Data1, Data2,... Datan) este descrisă (nume,
tip, lungime), autonom, în toate fişierele în care apare. Mai mult, descrierea fiecărui fişier de date
(câmpurile care-l alcătuiesc, tipul şi lungimea fiecăruia, modul de organizare (sercvenţial, indexat,
relativ etc.)) este obligatorie în toate programele care îl “citesc” sau modifică. Între FIŞIER1,
FIŞIER2, ... FIŞIERn nu există nici o relaţie definită explicit.
Data 1
Data 2 Raport 1

Data 3
FIŞIER 1 PRELUCRARE 1 Fişier de
Data 4 legături

Data 2
Raport 4
Data 4
FIŞIER 2 PRELUCRARE 2 Raport 3
Data 5
Raport 2
Data 6

Data 1 Raport 5
FIŞIER 3 PRELUCRARE 3
Data 5

Data 7
Data 8
DATE FIŞIERE PRELUCRĂRI IEŞIRI
Figura 1.1. Sistem informatic bazat pe organizarea datelor în fişiere independente

Spre exemplu, Data2 este prezentă în două fişiere de date, FIŞIER1 şi FIŞIER2. Dacă, prin
program, se modifică formatul sau valoarea acesteia în FIŞIER1, modificarea nu se face automat şi în
FIŞIER2; prin urmare, o aceeaşi dată, Data2, va prezenta două valori diferite în cele două fişiere, iar
necazurile bat la uşă: informaţiile furnizate de sistemul informatic sunt redundante şi prezintă un mare
risc de pierdere a coerenţei.
Se poate proiecta un mecanism de menţinere a integrităţii datelor, astfel încât actualizarea unei
date într-un fişier să atragă automat actualizarea tuturor fişierelor de date în care aceasta apare, însă, în
sistemele mari, care gestionează volume uriaşe de informaţii, implementarea unui asemenea mecanism
este extrem de complexă şi costisitoare. În plus, fişierele de date sunt uneori proiectate şi
implementate la distanţe mari în timp, în formate diferite: de exemplu, FIŞIER1 este posibil să fi fost
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 5

creat cu ajutorul limbajului COBOL, FIŞIER2 în FORTRAN iar FIŞIER3 în BASIC. În asemenea
condiţii, punerea în operă a mecanismului de menţinere a integrităţii devine o utopie.
Chiar numai şi din cele prezentate mai sus, se pot desprinde câteva dezavantaje ale organizării
datelor după modelul fişierelor independente:
- Redundanţa şi inconsistenţa datelor: o aceeaşi dată apare în mai multe fişiere; în aceste cazuri există
riscul modificării acesteia într-un fişier fără a face modificările şi în toate celelalte fişiere.
- Dificultatea accesului. Într-o întreprindere, o aceeaşi informaţie este exploatată de mai mulţi
utilizatori. Spre exemplu, pentru departamentul care se ocupă cu gestiunea stocurilor, intrările de
materiale trebuie ordonate pe magazii (depozite) şi repere, în timp ce pentru departamentul care se
ocupă cu decontările cu partenerii de afaceri ai întreprinderii, intrările trebuie ordonate pe furnizori ai
materialelor. Or, fişierele tradiţionale nu facilitează accesarea datelor după mai multe criterii, specifice
diferiţilor utilizatori sau grupuri de utilizatori.
- Izolarea datelor: când datele sunt stocate în formate diferite, este dificil de scris programe care să
realizeze accesul într-o manieră globală a tuturor celor implicate în derularea unei tranzacţii.
- Complexitatea apăsătoare a actualizărilor. O actualizare presupune adăugarea, modificarea sau
ştergerea unor informaţii din fişiere. Cum prelucrările se desfăşoară în timp real, de la mai multe
terminale (în mediile multi-utilizator), pot apare situaţii conflictuale atunci când doi utilizatori doresc
modificarea simultană a unei aceleaşi date. Rezolvarea acestui gen de conflicte presupune existenţa
unui program-supervizor al prelucrărilor, care este greu de realizat cu o multitudine de fişiere, create la
distanţă în timp şi, în formate diferite.
- Problemele de securitate ţin de dificultatea creării unui mecanism care să protejeze pe deplin datele
din fişiere de accesul neautorizat.
- Probleme legate de integritatea datelor. Informaţiile stocate în fişiere sunt supuse la numeroase
restricţii semantice. Toate aceste restricţii alcătuiesc mecanismul de integritate a datelor, deosebit de
complex în mediile de lucru multi-utilizator şi eterogene.
- Inabilitatea de a obţine răspunsuri rapide la probleme ad-hoc simple.
- Costul ridicat se datorează gradului mare de redundanţă a datelor, eforturilor deosebite ce trebuie
depuse pentru interconectarea diferitelor tipuri de fişiere de date şi pentru asigurarea funcţionării
sistemului în condiţiile respectării unui nivel minim de integritate şi securitate a informaţiilor.
- Inflexibilitatea faţă de schimbările ulterioare, ce sunt inerente oricărui sistem informaţional.
- Modelarea indecvată a lumii reale.
Aceste dezavantaje sunt mai mult decât convingătoare, încât vă puteţi întreba dacă au existat
aşa inconştienţi care să-şi arunce banii pe apa... fişierelor independente. Ei bine, o serie de aplicaţii
dezvoltate în anii '60 sau '70 au fost moştenite şi folosite până zilele noastre. De ce ? Datorită
consistentelor sume investite, care au putut fi amortizate (trecute pe costuri) doar în ani buni, chiar
decenii. Un alt motiv a fost însă funcţionalitatea şi viteza unor asemenea aplicaţii, precum şi
experienţa acumulată de o largă cateogorie de profesionişti în ale IT-ului.

1.3 CE ESTE O BAZĂ DE DATE ?


Sintagma bază de date apare pentru prima dată în titlul unei conferinţe organizate la Santa
Monica (California) în 1964 de System Development Corporation. Consacrarea definitivă a termenului
6 Baze de date I

este marcată de publicarea în anul 1969, de către CODASYL, în cadrul unei conferinţe dedicate
limbajelor de gestiune a datelor, a primului raport tehnic în care este prezentat conceptul de bază de
date. Faţă de modelul fişierelor independente, noutatea o constituie existenţa unui fişier de descriere
globală a bazei, astfel încât să se poată asigura independenţa programelor faţă de date, după cum o
arată şi figura 1.2.
Avantajele organizării informaţiilor în baze de date decurg tocmai din existenţa acestui fişier
de descriere globală a bazei, denumit, în general, dicţionar de date (alte titulaturi: repertoar de date
sau catalog de sistem). Extragerea şi modificarea datelor, altfel spus, lucrul cu fişierele de date, se
derulează exclusiv prin intermediul dicţionarului în care se găsesc informaţii privitoare la structura
datelor şi restricţiile îndeplinite de acestea.
O bază de date (BD) reprezintă un ansamblu structurat de fişiere, care grupează datele
prelucrate în aplicaţiile informatice ale unei persoane, grup de persoane, întreprinderi, instituţii etc.
Formal, BD poate fi definită ca o colecţie de date aflate în interdependenţă, împreună cu descrierea
datelor şi a relaţiilor dintre ele, sau ca o colecţie de date utilizată într-o organizaţie, colecţie care
este automatizată, partajată, definită riguros (formalizată) şi controlată la nivel central.

B A Z A DE D A T E

Fişier de date 1

Fişier de date 2 Dicţionar


de date

Fişier de date n

Aplicaţia 1 Aplicaţia 2 Aplicaţia 3

Figura 1.2. Schemă de principiu a unei baze de date

Atunci când vorbim despre o bază de date, trebuie avute în vedere două aspecte fundamentale
aceste acesteia, schema şi conţinutul. Organizarea bazei de date se reflectă în schema sau structura sa,
ce reprezintă un ansamblu de instrumente pentru descrierea datelor, a relaţiilor dintre acestea, a
semanticii lor şi a restricţiilor la care sunt supuse. Ansamblul informaţiilor stocate în bază la un
moment dat constituie conţinutul sau instanţierea sau realizarea acesteia. În timp ce volumul prezintă
o evoluţie spectaculoasă în timp, schema unei baze rămâne relativ constantă pe tot parcursul utilizării
acesteia. Corespunzător celor două aspecte complementare, schemă/conţinut, limbajele de programare
dedicate bazelor de date se împart în limbaje de definire a datelor (DDL – Data Definition Language)
şi limbaje de manipulare a datelor (DML - Data Manipulation Language). Limbajele aflate în uz, cum
ar fi SQL-ul, prezintă opţiuni atât pentru declararea structurii, cât şi pentru editarea conţinutului şi
consultarea/interogarea bazei.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 7

Într-un sistem informatic ce utilizează BD, organizarea datelor poate fi analizată din mai multe
puncte de vedere şi pe diferite paliere. De obicei, abordarea se face pe trei nivele: fizic sau intern,
conceptual sau global şi extern.
Nivelul fizic (sau intern). Reprezintă modalitatea efectivă în care acestea sunt "scrise" pe
suportul de stocare - disc magnetic, disc optic, bandă magnetică etc.
Nivelul conceptual (sau global). Este nivelul imediat superior celui fizic, datele fiind privite
prin prisma semanticii lor; interesează conţinutul lor efectiv, ca şi relaţiile care le leagă de alte date.
Reprezintă primul nivel de abstractizare a lumii reale observate. Obiectivul acestui nivel îl constituie
modelarea realităţii considerate, asigurându-se independenţa bazei faţă de orice restricţie tehnologică
sau echipament anume. Toţi utilizatorii îşi exprimă nevoile de date la nivel conceptual, prezentându-le
administratorului bazei de date, acesta fiind cel care are o viziune globală necesară satisfacerii tuturor
cerinţelor informaţionale.
Nivelul extern. Este ultimul nivel de abstractizare la care poate fi descrisă o bază de date.
Structurile de la nivelul conceptual sunt relativ simple, însă volumul lor poate fi deconcertant. Iar dacă
la nivel conceptual baza de date este abordată în ansamblul ei, în practică, un utilizator sau un grup de
utilizatori lucrează numai cu o porţiune specifică a bazei, în funcţie de departamentul în care îşi
desfăşoară activitatea şi de atribuţiile sale (lor). Simplificarea interacţiunii utilizatori-bază, precum şi
creşterea securităţii bazei, sunt deziderate ale unui nivel superior de abstractizare, care este nivelul
extern. Astfel, structura BD se prezintă sub diferite machete, referite, uneori şi ca sub-scheme, scheme
externe sau imagini, în funcţie de nevoile fiecărui utilizator sau grup de utilizatori.
Luând în considerare cele trei nivele de abstractizare, schematizarea unui sistem de lucru cu o
bază de date se poate face ca în figura 1.3.

Utilizator A1 Utilizator A2 Utilizator B1 Utilizator B2 Utilizator B3

Comenzi Comenzi
Aplicaţie Aplicaţie Aplicaţie
autonome autonome

Schemă externă Imagine A Schemă externă Imagine B


A (nivel extern) B (nivel extern)

Interfaţă A Interfaţă B
dintre nivelele dintre nivelele
global şi extern global şi extern

SISTEM DE
Schema Imagine globală GESTIUNE A
conceptuală BAZEI
(nivel global)
(globală) DE DATE

Interfaţă dintre nivelele


fizic şi global

Definirea structurii
interne de stocare Baza de date memorată pe disc
(Schema internă)

Figura 1.3. Schematizare a unui sistem de lucru cu o bază de date


8 Baze de date I

Posibilitatea modificării structurii la un nivel, fără a afecta structura nivelului sau nivelurilor
superioare, se numeşte autonomie a datelor stocate în bază, analizabilă pe două paliere.
Autonomia fizică reprezintă posibilitatea modificării arhitecturii bazei la nivel intern, fără ca
aceasta să necesite schimbarea schemei conceptuale şi rescrierea programelor pentru exploatarea bazei
de date. Asemenea modificări sunt necesare uneori pentru ameliorarea performanţelor de lucru (viteză
de acces, mărimea fişierelor etc.). Tot autonomia fizică este cea care asigură portarea bazei de date de
pe un sistem de calcul pe altul fără modificarea schemei conceptuale şi a programelor.
Autonomia logică presupune posibilitatea modificării schemei conceptuale a bazei (modificare
datorată necesităţii rezolvării unor noi cerinţe informaţionale) fără a rescrie programele de exploatare.
Autonomia logică a datelor este mai greu de realizat decât autonomia fizică, deoarece programele de
exploatare sunt dependente, în foarte mare măsură, de structura logică a datelor pe care le consultă şi
actualizează, în ciuda existenţei dicţionarului de date. Fireşte, un element important îl reprezintă şi
anvergura modificării schemei conceptuale.
Datele stocate într-o BD prezintă, într-o măsură mai mare sau mai mică, următoarele
caracteristici:
partajabilitate – disponibilitate pentru un mare număr de utilizatori şi aplicaţii;
persistenţă – existenţă permanentă, din momentul preluării în bază până în momentul
actualizării sau ştergerii;
securitate – protejarea de accesul neautorizat, atât în ceea ce priveşte citirea şi copierea, cât şi
modificarea şi ştergerea;
validitate – referită şi ca integritate sau corectitudine – priveşte gradul de adecvare dintre
datele din bază şi realitatea, procesele pe care le reflectă aceste date;
consistenţă – ori de câte ori diverse aspecte ale proceselor sau fenomenelor reale sunt preluate
în bază sub forma a doua sau mai multor entităţi sau atribute, aceste entităţi/atribute trebuie să
fie în concordanţă unele ce celelalte, să respecte relaţiile existente între aspectele
proceselor/fenomenelor reale;
nonredundanţă - pe cât posibil, o entitate din realitate ar trebui să aibă un singur corespondent
în baza de date;
independenţă – priveşte autonomia logică şi fizică evocate mai sus.

1.4 MODELE DE ORGANIZARE A DATELOR ÎN BD


Nucleul unei baze de date îl reprezintă dicţionarul de date ce conţine structura bazei, structură
care se materializează prin instrucţiuni scrise cu ajutorul unui limbaj de definire a datelor (DDL).
Analiza, proiectarea şi implementarea structurii (schemei) bazei se realizează utilizând un model de
date. Un asemenea model reprezintă un ansamblu de instrumente conceptuale care permit descrierea
datelor, relaţiilor dintre ele, a semanticii lor, ca şi a restricţiilor la care sunt supuse.
Modelul datelor este o reprezentare a obiectelor lumii reale şi a evenimentelor asociate lor.
Presupune un demers de abstractizare care se concentrează pe aspectele esenţiale ale organizaţiei/apli-
caţiei, furnizând conceptele de bază şi notaţiile care vor permite utilizatorilor bazelor de date să
comunice clar şi rapid informaţiile şi cunoştinţele lor despre datele organizaţiei.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 9

O grupare "tradiţională" a modelelor utilizate în bazele de date delimitează trei categorii:


modele logice bazate pe obiect, modele logice bazate pe înregistrare şi modele fizice. Din punctul
nostru de vedere, interesează numai nivelele conceptual şi extern de abstractizare a datelor; de aceea,
vom prezenta, în linii mari, numai reprezentanţii principali ai primelor două categorii.
Modelul ierarhic. Primele produse-software (Sisteme de Gestiune a Bazelor de Date - SGBD)
lucrau bu baze de date ierarhice. Structura datelor este prezentată sub forma unui arbore - vezi figura
1.4 -, partea superioară a arborelui fiind rădăcina (arborele este văzut "cu verdele în jos"). Un nod-tată
poate avea mai multe noduri-fii. Un fiu nu poate exista independent de tatăl său. Legătura
(reprezentată prin linie) se face exclusiv între tată şi fii. Orice fiu poate fi şi tată, deci poate avea, la
rândul său, fii.
Astfel, în clasa a IX-a A (profil Uman) există doi elevi: Pop I. Vasile ce are numărul matricol
4545 şi domiciliază pe strada Primăverii nr. 22, iar al doilea, Ion V. Viorel, are matricolul 4550 şi
locuieşte pe strada Corupţiei nr. 13 bis. Vasile a luat la fizică (profesor Cernat Dan, născut pe 21
aprilie 1968) un 5 pe 8 februarie şi un 9 pe 15 februarie. La istorie (profesor Pal Dana, născută pe 12
noiembrie 1975) stă ceva mai bine, având un 8 (căpatat pe 21 februarie), un 7 (pe 28 februarie) şi un 8
(pe 7 martie). În acelaşi mod poate fi intepretată figura pentru Ion V. Viorel, cu menţiunea specială că
istoria nu e punctul lui forte.

Figura 1.4. Arbore de structură specific modelului ierarhic

Modul în care înregistrările se înlănţuie presupune folosirea unor pointeri (un soi de adrese
fizice) ce nu sunt reprezentaţi în figură (din considerente umanitare). Aflarea informaţiilor din baza de
date presupune navigarea între înregistrăr cu ajutorul pointerilor. În plus, modificarea structurii bazei
de date presupune actualizarea înlănţuirii pointerilor, ceea ce extrem de laborios. A altă problemă a
acestui model o reprezintă imposibilitatea reprezentării cazului în care un copil este, pardon,
"rezultatul" a mai mulţi taţi. Situaţia nu este atât de scandaloasă cum v-aţi închipuit, şi aceasta doarece
modelul ierarhic nu face nici o referire la mama fiului.
Figura 1.4 este relevantă pentru gradul de redundanţă impus de reprezentarea ierarhică. Codul,
denumirea, numărul de ore ale fiecărei discipline, precum şi datele despre profesor, apar pentru fiecare
elev, deşi acestea sunt comune la nivel de an de studii (numele disciplinei, numărul de ore) sau la nivel
de clasă (profesorul titular).
10 Baze de date I

Modelul reţea. Este o dezvoltare a modelului ierarhic, prin care se pot reprezenta şi situaţiile
în care un fiu "posedă" mai mulţi taţi. Înregistrările sunt privite în BD ca o colecţie de grafuri cu o
structură asemănătoare celei din figura 1.5. Navigarea se face tot prin pointeri.

Figura. 1.5. Graf specific reprezentării utilizând modelul reţea

Modelul reţea elimină o serie de redundanţe specifice modelului ierarhic. Spre exemplu, cazul
ilustrat în figura 1.4 poate fi reprezentat după logica modelului reţea ca în figura 1.5. Se observă că
fiecare disciplină (şi profesorul care o predă la clasa respectivă) apare o singură dată, fiind legată prin
linii de toate notele obţinute de fiecare dintre elevi.
Modelul relaţional a fost următorul în ordinea cronologică şi rămâne cel care domină copios
piaţa bazelor de date şi la acest moment, motiv pentru care cea mai mare parte a acestui curs îi este
dedicată.
Modelul obiectual. Începând cu anii ‟60, în programare şi, ceva mai târziu, în analiză şi
proiectare, orientarea pe obiecte (OO) a avut un succes uriaş, reuşit să depăşească metodologiile
structurate. Pe baza acestui succes, s-a crezut că şi în materie de baze de date, modelul obiectual îl va
surclasa pe cel relaţional. Rezultatele sunt însă deprimante pentru suporterii OO-ului, piaţa SGBD OO
fiind sub 6% din valoarea totală a pieţii bazelor de date.
Modelul relaţional-obiectual. Este un model mai recent ce încearcă să valorifice deopotrivă
atuurile relaţionalului cu orientarea pe obiecte. Deşi privit mai degrabă cu neîncredere în cercurile
teoreticienilor, acest model se impune încet-încet datorită marilor producători de software dedicat
bazelor de date. Ca şi în cazul modelului „curat OO”, tehnologia relaţional-obiectuală nu face...
obiectul cursului de faţă, fiind studiată la discipla Baze de date avansate din ciclul II (specializarea
Informatică economică).
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 11

Capitolul 2

MODELUL RELAŢIONAL

Obiective:
I. Explicarea noţiunilor fundamentale ale modelului raţional:
relaţie/tabelă, domeniu, atribut/coloană, tuplu/linie
II. Discutarea restricţiilor implementabile în bazele de date relaţionale:
restricţia de domeniu, atomicitarea, nenulitatea, unicitatea, restricţia
referenţială, regulile de validare la nivel de atribut şi înregistrare
III. Exemplificarea schemei şi conţinutului unei baze de date relaţionale
12 Baze de date I

Modelul relaţional de organizare a datelor s-a conturat în două articole publicate în 1969 şi
1970 de către E.F. Codd, matematician la centrul de cercetări din San Jose (California) al firmei
IBM2. În acel moment, tehnologia bazelor de date era centrată pe modelele ierarhic şi reţea, modele ce
depind într-o mai mare măsură de organizarea internă a datelor pe suportul de stocare (celebrii pointeri
pe care i-am pomenit acum câteva pagini). Codd a propus o structură de date tabelară, independentă de
tipul de echipamente şi software de sistem pe care este implementată, structură "înzestrată" cu o serie
de operatori pentru extragerea datelor. Deşi puternic matematizat, modelul relaţional este relativ uşor
de înţeles. Faţă de modelele ierarhice şi reţea, modelul relaţional prezintă câteva avantaje:
propune structuri de date uşor de utilizat;
ameliorează independenţa logică şi fizică;
pune la dispoziţia utilizatorilor limbaje ne-procedurale;
optimizează accesul la date;
îmbunătăţeşte integritatea şi confidenţialitatea datelor;
ia în calcul o largă varietate de aplicaţii;
abordează metodologic definirea structurii bazei de date.

Un model de date are trei piloni: componenta structurală, adică modul în care, efectiv, la nivel
logic, datele sunt stocate în bază, componenta de integritate, adică regulile ce pot fi declarate pentru
datele din bază şi o componentă manipulatorie, adică modul în care obţinem informaţii din bazele de
date (ceea ce presupune o serie de operatori aplicabili uneia sau mai multor relaţii). Modelului
relaţional îi este asociată teoria normalizării, care are ca scop prevenirea comportamentului aberant al
relaţiilor în momentul actualizării lor, eliminarea datelor redundante şi înlesnirea înţelegerii legăturilor
semantice dintre date (vezi capitolul 3). Prezentul capitol va fi dedicat componentei structurale şi celei
de integritare din modelul relaţional.
Deşi puternic contestat, şi cu neajunsurile sale, modelul relaţional de organizare a bazelor de
date rămâne cel mai utilizat. Cu foarte puţine excepţii, toate aplicaţiile software realizate pentru bănci,
buticuri, universităţi (ordinea este, în ciuda aparenţelor, pur întâmplătoare) sunt realizate cu
produse/instrumente ce gestionează baze de date relaţionale.

2.1 STRUCTURĂ
Modelul relaţional al datelor se poate defini printr-o serie de structuri de date (relaţii alcătuite
din tupluri), operaţii aplicate asupra structurilor de date (selecţie, proiecţie, joncţiune etc.) şi reguli de
integritate care să asigure consistenţa datelor (chei primare, restricţii referenţiale s.a.).
La modul simplist, o bază de date relaţională (BDR) poate fi definită ca un ansamblu de relaţii
(tabele); fiecare tabelă (sau tabel), alcătuită din linii (tupluri), are un nume unic şi este stocată pe
suport extern (de obicei disc). La intersecţia unei linii cu o coloană se găseşte o valoare atomică
(elementară). O relaţie conţine informaţii omogene legate de anumite entităţi, procese, fenomene:
CĂRŢI, STUDENŢI, LOCALITĂŢI, PERSONAL, FACTURI etc. Spre exemplu, în figura 2.1 este
reprezentată tabela CLIENŢI.

2
Extrase din lucrarea [Codd70] se găsesc la adresa http://www.acm.org/classics/nov95/. De asemenea, o
excelentă analiză a articolelor lui Codd este în [Date98].
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 13

Figura 2.1. Relaţia (tabela) CLIENŢI

În teoria relaţională se foloseşte termenul relaţie. Practica, însă, a consacrat termenul tabelă
(engl. table). Un tuplu sau o linie este o succesiune de valori de diferite tipuri. În general, o linie
regrupează informaţii referitoare la un obiect, eveniment etc., altfel spus, informaţii referitoare la o
entitate: o carte (un titlu sau un exemplar din depozitul unei biblioteci, depinde de circumstanţe), un/o
student(ă), o localitate (oraş sau comună), un angajat al firmei, o factură emisă etc. Figura 2.2 conţine
al doilea tuplu din tabela CLIENŢI, tuplu referitor la forma MODERN SRL. Linia de mai sus este
alcătuită din patru valori ce desemnează: codul, numele, adresa şi codul poştal al localităţii şi adresei
referitoare la clientul MODERN SRL.

Figura 2.2. Un tuplu al tabelei CLIENŢI

Teoretic, orice tuplu reprezintă o relaţie între clase de valori (în cazul nostru, între patru clase
de valori); de aici provine sintagma baze de date relaţionale, în sensul matematic al relaţiei, de
asociere a două sau mai multe elemente. Fireşte, toate tuplurile relaţiei au acelaşi format (structură),
ceea ce înseamnă că în tabela CLIENŢI, din care a fost extrasă linia din figura 2.2, fiecare linie este
constituită dintr-o valoare a codului, o valoare a numelui, o valoare a adresei, şi o valoare a codului
poştal. Ordinea tuplurilor nu prezintă importanţă din punctul de vedere al conţinutul informaţional al
tabelei.
Fiecare atribut este caracterizat printr-un nume şi un domeniu de valori pe care le poate lua.
Domeniul poate fi definit ca ansamblul valorilor acceptate (autorizate) pentru un element component
al relaţiei:
într-o tabelă destinată datelor generale ale angajaţilor, pentru atributul Sex, domeniul este
alcătuit din două valori: Femeiesc şi Bărbătesc;
pentru atributul Regiune, domeniul, deşi limitat, este ceva mai mare; valorile autorizate pot
fi: Dobrogea, Banat, Transilvania, Oltenia, Muntenia, Moldova.
domeniul atributului Jud este alcătuit din indicativele auto (două caractere) ale fiecărui judeţ
(plus Bucureşti).
14 Baze de date I

domeniul atributului Judet este alcătuit din numele fiecărui judeţ (plus Bucureşti).
domeniul unui atribut precum PreţUnitar, care se referă la preţul la care a fost vândut un
produs/serviciu, este cu mult mai larg, fiind alcătuit din orice valoarea cuprinsă între 1 şi
99999999 lei (ceva mai noi).
Fireşte, în funcţie de specificul bazei de date, domeniul poate fi extins sau restrâns după
cerinţe. De exemplu, la regiunile luate în discuţie mai sus, mai pot fi adăugate şi provincii precum:
Bucovina, Maramureş, Crişana.

Şi acum, câteva senzaţii tari (urmează un pic de matematică) ! Dacă notăm cu D1 domeniul
atributului CodClient, cu D2 domeniul atributului NumeClient, cu D3 domeniul pentru Adresa, şi cu
D4 domeniul atributul CodPostal, se poate spune că fiecare linie a tabelei CLIENŢI este un tuplu de
patru elemente, iar relaţia în ansamblu corespunde unui subansamblu din ansamblul tuturor tuplurilor
posibile alcătuite din patru elemente, ansamblu care este produsul cartezian al celor patru domenii.
În general, orice relaţie R poate fi definită ca un subansamblu al produsului cartezian de n domenii Di:
R D1 x D2 x D3 x ...x D n ,
n fiind denumit gradul sau ordinul relaţiei. Relaţiile de grad 1 sunt unare, cele de grad 2 - binare, ...,
cele de grad n - n-are. Această definiţie pune în evidenţă aspectul constant al relaţiei, de independenţă
în timp (se spune că în acest caz relaţia este definită ca un predicat).
O a doua definiţie abordează o relaţie R ca un ansamblu de m-uplete (m-tupluri) de valori:
R = { t1, t2 , ..., t k , ..., t m} , unde t k (dk1,dk2 , ..., dki ,...,dkn ) în care:
dk1 este o valoare în D1, dk2 este o valoare în D2, … , dkn este o valoare în Dn;
n - reprezintă ordinul lui R;
m - cardinalitatea lui R.

Pentru un (mic) plus de claritate, vezi figura 2.3.

Figura 2.3. Ilustrarea celei de-a doua definiţii a unei relaţii

Reprezentarea sub formă de tabelă, deci ca ansamblu de tupluri, pune în evidenţă aspectul
dinamic, variabil al relaţiei. Abordarea predicativă (prima definiţie) sau ansamblistă (a doua definiţie)
reprezintă un criteriu important de delimitare a limbajelor de interogare a bazelor de date relaţionale.

Reţinem corespondenţa noţiunilor relaţie-tabelă, tuplu-linie şi atribut-coloană.


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 15

Numărul de tabele pe care le conţine o bază de date, atributele “adunate” în fiecare tabelă,
domeniul fiecăruia dintre atribute prezintă diferenţe majore de la o bază la alta, chiar dacă uneori
reflectă acelaşi tip de procese. Intrăm astfel în sfera proiectării bazelor de date, a dependenţelor şi
normalizării.
Relaţia CLIENŢI conţine informaţii despre firmele cărora compania noastră le vinde
produsele pe care producem şi/sau comercializăm. Fiecare linie se referă la un singur client. În figura
2.1 pe a treia linie a tabelei apare o valoarea curioasă notată NULL. Valoarea NULL este considerată o
metavaloare şi indică faptul că, în acel loc, informaţia este necunoscută sau inaplicabilă (în acest caz
nu cunoaştem adresa clientului Modern SRL). Valoarea NULL este diferită, însă de valorile 0 sau
spaţiu. Uneori, importanţa sa este (din păcate) majoră în expresii şi funcţii, după cum ştiu cei cu
oarecare experienţă în limbajului SQL.

În încheiere, principalele caracteristici ale unei relaţii sunt sistematizate după cum urmează:
În cadrul unei baze de date, o relaţie prezintă un nume distinct de al celorlalte relaţii.
Valoarea unui atribut într-un tuplu oarecare conţine a singură valoare (o valoare atomică sau
elementară).
Fiecare atribut are un nume distinct.
Orice valoare a unui atribut face parte din domeniul pe care a fost definit acesta.
Ordinea dispunerii atributelor în relaţie nu prezintă importanţă.
Fiecare tuplu este distinct, adică nu pot exista două tupluri identice.
Ordinea tuplurilor nu influenţează conţinutul informaţional al relaţiei.

2.2 RESTRICŢII
De ce ne interesează restricţiile într-o bază de date ? Termenul de restricţie este oarecum
iritant, atât pentru studenţi, cât şi pentru profesori, deoarece semnalează existenţa unor constrângeri
instituite şi oarecum obligatorii, şi, de vreme ce sunt impuse, însemnă că nu sunt prea plăcute (decât,
în cel mai bun caz, pentru cel care le-a instituit). Partea cea mai enervantă este că respectarea
restricţiilor este (supra)vegheată de o anumită autoritate înzestrată cu anumite instrumente de
constrângere, de la bastoane de cauciuc, la creşterea şi scăderea impozitelor, salariilor, banilor de
buzunar etc.
Ei bine, în bazele de date, restricţiile sunt ceva mai acceptabile. Cei care lucrează cu bazele de
date sunt foarte interesaţi în declararea restricţiilor, pentru că, odată definite, de respectarea lor se va
îngriji sistemul de gestiune a bazelor de date (adică programele de lucru cu bazele de date). Esenţial
este că, ajutaţi de restricţii, putem creşte gradul de corectitudine şi de încredere al datelor din bază. În
cele ce urmează vor fi prezentate pe scurt cele mai importante restricţii definibile într-o bază de date
relaţională: restricţia de domeniu, de atomicitate, de unicitate, referenţială şi restricţiile-utilizator.

2.2.1 Restricţia de domeniu


După cum am văzut în paragraful anterior, un atribut este definit printr-un nume şi un
domeniu. Orice valoare a atributului trebuie să se încadreze în domeniul definit. Există mai multe
moduri de percepţie a acestei restricţii.
16 Baze de date I

O parte din informaticieni substituie domeniul tipului atributului: numeric, şir de caractere,
dată calendaristică, logic (boolean) etc. şi, eventual, lungimii (numărul maxim de poziţii/caractere pe
care se poate “întinde” un atribut). După cum se observă, este luat în calcul numai aspectul sintactic al
domeniului. Faptul că indicativul auto al unui judeţ (vezi plăcuţele de înmatriculare) poate fi una din
valorile: IS, TM, B etc. reprezintă o restricţie de comportament sau, mai simplu, o restricţie definită de
utilizator.
Cea de-a doua categorie priveşte domeniul deopotrivă sintactic şi semantic. Astfel, domeniul
sintactic al atributului Jud (indicativul judeţului) este un şir de două caractere, obligatoriu litere (sau o
literă şi un spaţiu, pentru Bucureşti), şi, chiar mai restrictiv, literele sunt obligatoriu majuscule. Din
punct de vedere semantic, indicativul poate lua una din valorile: IS, TM …
Majoritatea SGBD-urilor permit definirea tuturor elementelor ce caracterizează domeniul
(sintactic şi semantic) atributului Jud prin declararea tipului şi lungimii atributului şi prin aşa-numitele
reguli de validare la nivel de câmp (field validation rule). Sunt însă şi produse la care domeniul poate
fi definit explicit, sintactic şi semantic, dându-i-se un nume la care vor fi legate atributele în momentul
creării tabelelor.

2.2.2 Atomicitate
Conform teoriei bazelor de date relaţionale, orice atribut al unei tabele oarecare trebuie să fie
atomic, în sensul imposibilităţii descompunerii sale în alte atribute. Implicit, toate domeniile unei baze
de date sunt musai atomice (adică elementare). În aceste condiţii, se spune că baza de date se află în
prima formă normală sau prima formă normalizată (1NF).
Astăzi, atomicitatea valorii atributelor a devenit o ţintă predilectă a “atacurilor duşmănoase” la
adresa modelului relaţional, datorită imposibilităţii înglobării unor structuri de date mai complexe,
specifice unor domenii ca: proiectare asistată de calculator, baze de date multimedia etc. Mulţi autori,
dintre care merită amintiţi cu deosebire Chris J. Date şi Hugh Darwen, se opun ideii de atomicitate
formulată de Codd.
La drept vorbind, singurul lucru cert despre atomicitate este relaticitatea acesteia. Să luăm un
exemplu de atribut compus (non-atomic) - Adresa. Fiind alcătuită din Stradă, Număr, Bloc, Scară, Etaj,
Apartament, discuţia despre atomicitatea adresei pare de prisos, iar descompunerea sa imperativă.
Trebuie însă să ne raportăm la obiectivele bazei. Fără îndoială că pentru BD a unei filiale CONEL sau
ROMTELECOM, sau pentru poliţie, preluarea separată a fiecărui element constituent al adresei este
foarte importantă. Pentru un importator direct, însă, pentru un mare en-grossist sau pentru o firmă de
producţie lucrurile stau într-o cu totul altă lumină. Partenerii de afaceri ai acestora sunt persoane
juridice, iar adresa interesează numai la nivel general, caz în care atributul Adresa nu este considerat a
fi non-atomic.
Alte exemple de atribute ce pot fi considerate, în funcţie de circumstanţe, simple sau compuse:
DataOperaţiuniiBancare (Data + Ora), BuletinIdentitate (Seria+Număr), NrÎnmatriculareAuto (privit
global, sau pe cele trei componente: număr, judeţ, combinaţie trei de litere).
O relaţie (tabelă) în 1NF nu trebuie să conţină atribute care se repetă ca grupuri (grupuri
repetitive). Într-o altă formulare, toate liniile unei tabele trebuie să conţină acelaşi număr de atribute.
Fiecare celulă a tabelei (intersecţia unei coloane cu o linie), altfel spus, valoarea unui atribut pe o linie
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 17

(înregistrare), trebuie să fie atomică. Detalii despre atomicitate şi grupuri repetitive găsiţi în [Fotache
2005]. Noi o să reluăm această discuţie şi în capitolul 3.

2.2.3 Nenulitate
Modelul relaţional acceptă ca, atunci când nu se cunoaşte valoarea unui atribut pentru o
anumită entitate, sau când pentru acel obiect, entitate, persoană etc. atributul este inaplicabil, să se
folosească (meta)valoare NULL. După cum am discutat, celui de-al treilea client din figura 2.1 nu i
cunoaşte adresa. Dacă am avea o tabelă PRODUSE cu atributele CodProdus, DenumireProdus, UM,
Culoare, este posibil ca, pentru anumite sortimente, cum ar tricouri, cămăşi, jachete, atributul să fie
important, în timp ce pentru altele, precum vodcă, cafea, lapte atributul culoare să nu furnizeze nici o
informaţie, adică să nu fie aplicabil. Iar dacă laptele poate fi doar alb, vodca chiar că nu are culoare
(sunt foarte mulţi specialişti în acest domeniu, îi puteţi întreba !).
Într-o bază de date relaţională avem posibilitatea de a le impune unora dintre atribute (sau
tuturora) să aibă întotdeauna valori specificate, altfel spus, le interzicem valorile nule, în timp cel altor
atribute li se pot permite valori nule. Ca regulă, atributele importante, ce ţin de identificarea sau
caracterizarea unei entităţi, proces, fenomen, precum şi cele implicitate în calculul unor informaţii
importante, sunt declarate NOT NULL, iar atributele fără importanţă deosebită pot fi mai relaxate.

2.2.4 Unicitate
După cum am discutat în primul paragraf al acestui capitol, într-o relaţie nu pot exista două
linii identice (două linii care prezintă aceleaşi valori pentru toate atributele). Mai mult, majoritatea
relaţiilor prezintă un atribut, sau o combinaţie de atribute, care diferenţiază cu siguranţă un tuplu de
toate celelalte tupluri ale relaţiei. Cheia primară a unei relaţii (tabele) este un atribut sau un grup de
atribute care identifică fără ambiguitate fiecare tuplu (linie) al relaţiei (tabelei). După Codd, există trei
restricţii pe care trebuie să le verifice cheia primară:
unicitate: o cheie identifică un singur tuplu (linie) al relaţiei.
compoziţie minimală: atunci când cheia primară este compusă, nici un atribut din cheie nu
poate fi eliminat fără distrugerea unicităţii tuplului în cadrul relaţiei; în cazuri limită, o cheie
poate fi alcătuită din toate atributele relaţiei.
valori non-nule: valorile atributului (sau ale ansamblului de atribute) ce desemnează cheia
primară sunt întotdeauna specificate, deci ne-nule şi, mai mult, nici un atribut din compoziţia
cheii primare nu poate avea valori nule; această a treia condiţie se mai numeşte şi restricţie a
entităţii.
Joe Celko inventariază patru proprietăţi dezirabile pentru o cheie3:
Familiaritate: valorile cheii să fie uşor de înţeles pentru utilizatori.
Stabilitate: valorile cheii nu trebuie să fie volatile.
Minimalitate
Simplitate: sunt de preferat chei scurte şi simple.

3
[Celko99], p.247
18 Baze de date I

Tot el atrage atenţia ca cele patru proprietăţi pot intra, uneori, în conflict. O cheie stabilă poate să se
dovedească complexă şi dificil de gestionat. Identificarea cheii primare pentru o relaţie face parte
(împreună cu stabilirea tabelelor prin gruparea atributelor, stabilirea celorlalte restricţii) din procesul
de proiectare a bazei de date. Uneori, precum în cazul tabelei JUDEŢE, stabilirea cheii primare nu
ridică probleme. Cum fiecare judeţ are un indicativ auto unic, rezultă că atributul Jud candidează la
postul de cheie a relaţiei. La fel se poate spune şi despre denumirea judeţului. Rezultă că relaţia
JUDEŢE prezintă două chei candidate: Jud şi Judet. Alegerea unei dintre cheile candidat are în vedere
criterii precum lungimea, uşurinţa în reţinere şi/sau editare. Fiind mai scurt, desemnăm atributul Jud
drept cheie primară, situaţie în care Judet devine cheie alternativă.
În tabela CLIENŢI cheia primară este simplă - CodClient, CodClient reprezintă un număr unic
asociat fiecărei firme căreia i-am făcut vânzări. Există însă suficiente cazuri în care cheia primară este
compusă din două, trei s.a.m.d. sau, la extrem, toate atributele relaţiei. Să luăm spre analiză o relaţie
PERSONAL care conţine date generale despre angajaţii firmei. Fiecare tuplu al relaţiei se referă la un
angajat, atributele fiind: Nume, Prenume, DataNaşterii, Vechime, SalariuTarifar.
- atributul Nume nu poate fi cheie, deoarece chiar şi într-o întreprindere de talie mijlocie, este
posibil să existe doi angajaţi cu acelaşi nume.
- dacă apariţia a două persoane cu nume identice este posibilă, atunci apariţia a două persoane
cu acelaşi Prenume este probabilă.
- nici unul din aceste atributele DataNaşterii, Vechime, SalariuTarifar nu poate fi "înzestrat"
cu funcţiunea de identificator.
În acest caz, se încearcă gruparea a două, trei, patru s.a.m.d. atribute, până când se obţine
combinaţia care va permite diferenţierea clară a oricărei linii de toate celelalte. Combinaţia
Nume+Adresă pare, la primele două vederi, a îndeplini "cerinţele" de identificator. Ar fi totuşi o
problemă: dacă în aceeaşi firmă (organizaţie) lucrează împreună soţul şi soţia ? Ambii au, de obicei,
acelaşi nume de familie şi, tot de obicei, acelaşi domiciliu. Este adevărat, cazul ales nu este prea
fericit. Dar este suficient pentru “compromiterea” combinaţiei.
Următoarea tentativă este grupul Nume+Prenume+Adresă, combinaţie neoperantă dacă în
organizaţie lucrează tatăl şi un fiu (sau mama şi o fiică) care au aceleaşi nume şi prenume şi domiciliul
comun. Ar rămâne de ales una dintre soluţiile (Nume+Prenume+Adresă+Vechime) sau
(Nume+Prenume+Adresa+DataNaşterii).
Oricare din cele două combinaţii prezintă riscul violării restricţiei de entitate, deoarece este
posibil ca, la preluarea unui angajat în bază, să nu i se cunoască adresa sau data naşterii, caz în care
atributul respectiv ar avea valoarea NULL. Dificultăţile de identificare fără ambiguitate a angajaţilor au
determinat firmele ca, la angajare, să aloce fiecărei persoane un număr unic, număr denumit Marcă.
Prin adăugarea acestui atribut la cele existente, pentru relaţia PERSONAL problema cheii primare este
rezolvată mult mai simplu. Actualmente, sarcina este simplificată şi prin utilizarea codului numeric
personal (CNP), combinaţie de 13 cifre care prezintă avantajul că rămâne neschimbată pe tot parcursul
vieţii persoanei.
Pe lângă noţiunile cheie-candidat, cheie primară, cheie alternativă, modelul relaţional
utilizează şi sintagma supercheie. După Celko4 o supercheie poate fi definită ca un set de coloane ce
îndeplineşte condiţia de cheie (unicitate), dar care conţine cel puţin un subset care este, el însuşi, cheie.

4
[Celko99], p.52
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 19

Cu alte cuvinte, din cele trei condiţii ale cheii primare, o supercheie o îndeplineşte numai pe prima
(unicitate), fără a avea însă compoziţia minimală şi fără a pune problema restricţiei de entitate (valorile
nule ale atributelor componente).
Domeniul unui atribut care este cheie primară într-o relaţie este denumit domeniu primar.
Dacă într-o relaţie există mai multe combinaţii de atribute care conferă unicitate tuplului, acestea sunt
denumite chei candidate. O cheie candidată care nu este identificator primar este referită ca şi cheie
alternativă.

2.2.5 Restricţia referenţială


O bază de date relaţională este alcătuită din relaţii (tabele) aflate în legătură. Stabilirea
legăturii se bazează pe mecanismul cheii străine şi, implicit, a restricţiei referenţiale. Figura 2.4
prezintă o relaţie în care sunt implicate tabelele FACTURI şi CLIENŢI.

Figura 2.4. O restricţie referenţială între FACTURI (tabelă-copil) şi CLIENŢI (părinte)

Atributul CodClient joacă un rol de “agent de legătură” între tabelele CLIENŢI şi FACTURI.
Pentru relaţia CLIENŢI, atributul CodClient este cheie primară, în timp ce în tabela FACTURI,
CodClient reprezintă coloana de referinţă sau cheia străină, deoarece numai pe baza valorilor sale se
poate face legătura cu relaţia părinte CLIENŢI.
Cheile străine sau coloanele de referinţă sunt deci atribute sau combinaţii de atribute care pun
în legătură linii (tupluri) din relaţii diferite. Tabela în care atributul de legătură este primară se
numeşte tabelă-părinte (în cazul nostru, CLIENŢI), iar cealaltă tabelă-copil.
Legat de noţiunea de cheie străină apare conceptul de restricţie referenţială. O restricţie de
integritate referenţială apare atunci când o relaţie face referinţă la o altă relaţie. Când două tabele
(relaţii), T1 şi T2, prezintă atributul sau grupul de atribute notat CH, care, pentru T1, este cheie
primară, iar pentru T2 cheie străină, dacă în T2 se interzice apariţia de valori nenule ale CH care nu
există în nici un tuplu din T1, se spune că între T2 şi T1 s-a instituit o restricţie referenţială.
Instituirea restricţiei referenţiale între tabela CLIENŢI (părinte) şi FACTURI (copil) permite
cunoaşterea, pentru fiecare client, a denumirii localităţii şi a judeţului în care-şi are sediul. Dacă în
FATURI ar exista vreo linie în care valoarea atributului CodClient ar fi, spre exemplu 9988, este clar
că acea linie ar fi orfană (nu ar avea linie corespondentă în tabela părinte CLIENŢI).
20 Baze de date I

Observaţii
1. Pentru mulţi utilizatori şi profesionişti ai bazelor de date, denumirea de "relaţional" desemnează faptul că o
bază de date este alcătuită din tabele puse în legătură prin intermediul cheilor străine. Aceasta este, de fapt, a
doua accepţiune a termenului de BDR, prima, cea "clasică", având în vedere percepţia fiecărei linii dintr-o
tabelă ca o relaţie între clase de valori.
2. Majoritatea SGBD-urilor prezintă mecanisme de declararea şi gestionare automată a restricţiilor referenţiale,
prin actualizări în cascadă şi interzicerea valorilor care ar încălca aceste restricţii.
3. Respectarea restricţiilor referenţiale este una din cele mai complicate sarcini pentru dezvoltatorii de aplicaţii
ce utilizează baze de date. Din acest punct de vedere, tentaţia este de a “sparge” baza de date în cât mai
puţine tabele cu putinţă, altfel spus, de a avea relaţii cât mai “corpolente”. Gradul de fragmentare al bazei
ţine de normalizarea bazei de date, care, ca parte a procesului de proiectare a BD, se bazeză pe dependenţele
funcţionale, multivaloare şi de joncţiune între atribute.

2.2.6 Restricţii-utilizator
Restricţiile utilizator mai sunt denumite şi restricţii de comportament sau restricţii ale
organizaţiei. De obicei, aceste restricţii iau forma unor reguli de validare la nivel de atribut, la nivel de
linie/tabelă sau a unor reguli implementate prin declanşatoare (triggere).
O restricţie la nivel de atribut poate preveni introducerea în baza de date a unor valori din alte
intervale decât cele stabilite, în alte formate decât cele acceptate etc. Forma clasică a unei restricţii la
nivel de atribut este o expresie în care apar constante, funcţii-sistem şi, nu în ultimul rând, atributul
respectiv. La orice editare a atributului cu pricina (declanşată în cazul inserării unei linii în tabela din
care face parte, sau la modificarea sa) expresia este evaluată şi dacă rezultatul evaluării este TRUE
(adevărat), atunci inserarea/modificarea este permisă, iar dacă rezultatul este FALSE, atunci
inserarea/modificarea este blocată.
Expresia care defineşte o restricţie la nivel de înregistrare poate conţine două sau mai multe
atribute şi este evaluată la inserarea sau modificarea oricărei linii din tabelă. Tabela FACTURI
conţine, printre altele două atribute “valorice”, unul pentru păstrarea valorii totale (inclusiv TVA)
facturii respective şi un altul care indică “doar” cuantumul TVA colectate pentru factură. Majoritatea
produselor şi serviciilor comercializate la noi în ţară au un procent al taxei pe valoarea adăugată de
19%. Există, însă, şi produse la care procentul poate fi 9% sau chiar scutite de TVA (0%). De aceea,
TVA colectată pentru o factură este mai mică poate fi egală sau decât 19% din valoarea fără tva. Să
punem sub formă de formulă: ValoareaTotală = ValoareaFărăTVA + TVA. Dacă factura conţine
numai produse cu 19%: ValoareaTotală = ValoareaFărăTVA + 0.19 * ValoareaFărăTVA, sau
ValoareaTotală = 1.19 * ValoareaFărăTVA. Dar tabela noastră are doar atributele ValoareTotală şi
TVAColectată, aşa că înlocuim ValoareaFărăTVA prin diferenţa celorlalte două. Scriem:
ValoareTotală = 1.19 * (ValoareTotală – TVAColectată), şi după calcule de matematică superioară,
TVAColectată * 1.19 = 0.19 * ValoareTotală, altfel spus TVAColectată = ValoareTotală * 0.19 / 1.19.
Există şi alte tipuri de restricţii a căror validare presupune “citirea” unor date aflate în tabele
diferite. Spre exemplu, se poate institui o regulă care interzice emiterea unei noi facturi (o nouă
vânzare) dacă datoriile firmei client sunt mai mari de 200000 RON, iar directorul acesteia nu este
membru în partidul/partidele de guvernământ. Acestea reclamă întrebuinţarea unor proceduri speciale,
numite declanşatoare (triggere). Curioşii n-au decât să urmeze specializarea Informatică Economică.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 21

2.3 SCHEMA ŞI CONŢINUTUL UNEI BAZE DE DATE RELAŢIONALE


Aşa cum era prezentat în primul capitol, există două aspecte complementare de abordare a
bazelor de date: schema (structura, intensia) şi conţinutul (instanţierea, extensia). Pentru o BD
relaţională conţinutul unei relaţii este reprezentat de ansamblul tuplurilor ce o alcătuiesc la un moment
dat. Schema unei baze de date conţine denumiri ale tabelelor, numele, tipul şi lungimea atributelor,
restricţii de unicitate, de non-nulitate, restricţii la nivel de atribut, linie şi alte eventuale tipuri de
restricţii de comportament, precum şi restricţii referenţiale. La acestea se adaugă cele privind
drepturile utilizatorilor, definiţia şi restricţiile tabelelor virtuale, indecşi etc. Foarte importante în
lumea profesioniştilor dezvoltării de aplicaţii cu bazele de date sunt procedurile stocate – programe de
forma funcţiilor, procedurilor, pachetelor şi mai ales declanşatoarelor (triggerelor) – care, după cum le
spune şi numele sunt memorate în schema bazei de date şi fac parte integrantă din aceasta.
Schema unei baze de date poate fi reprezentată grafic, avantajul principal fiind o mai bună
sugestivitate. Iată câteva reguli pentru reprezentarea grafică a unei BDR5:
 O tabelă se reprezintă pe două linii, pe prima fiind scris numai numele relaţiei iar pe cea de-a doua
numele atributelor. Ordinea prezentării atributelor nu are importanţă. Totuşi, în scopul uşurării
înţelegerii schemei, coloanele care desemnează cheia primară se dispun succesiv (bineînţeles, atunci
când sunt mai multe). La fel şi coloanele care constituie o cheie străină. În general, cheia primară este
plasată la marginea stângă a tabelei (prima sau primele coloane).
 Numele coloanelor ce alcătuiesc cheia primară se subliniază cu o linie continuă, iar cele care
alcătuiesc identificatorii secundari se subliniază cu linie punctată.
 Numele unei coloane facultative este scris între paranteze.
 Dacă o cheie străină este alcătuită din mai multe coloane, se utilizează acolada pentru a le grupa.
 O restricţie referenţială este reprezentată printr-o săgeată care pleacă de la numele coloanei de
referinţă (sau de la vârful acoladei, în cazul unui grup de referinţă) şi are vârful în atributele tabelei la
care se face referinţa (tabela părinte).

În capitolele care vor urma, va fi utilizată cu precădere o bază de date “martor”, denumită VÎNZĂRI, a
cărei schemă simplificată este cea din figura 2.5. Discuţia tabelelor, atributelor şi restricţiilor legate de această
bază de date se află în Anexa 1 (vezi portalul disciplinei).

Pe lângă tabele, atribute şi restricţii, dicţionarul de date al unei BD relaţionale conţine multe
alte tipuri de obiecte, dintre care vom zăbovi foarte puţin asupra tabelelor virtuale (view-uri) şi
procedurilor stocate.
O tabelă virtuală stabileşte o legătură semantică între relaţii statice şi/sau alte relaţii dinamice,
nefiind definită explicit, prin tupluri proprii, ca o relaţie de bază (statică), ci printr-o expresie
relaţională. Conţinutul (instanţierea) relaţiei virtuale depinde, la un moment oarecare dat, de conţinutul
tabelelor de bază din care derivă. Pentru a înţelege mai bine diferenţa, explicaţia se poate rezuma
astfel: tabela virtuală este cea pentru care pe disc se memorează numai schema, nu şi conţinutul.

5
[Hainaut94], pp. 24-25
22 Baze de date I

Figura 2.5. Schema simplificată a bazei de date VÎNZĂRI

Tabelele virtuale oferă oricărui utilizator al unei baze de date posibilitatea prezentării datelor
în funcţie de nevoile sale specifice. De asemenea, raţiuni de securitate şi confidenţialitate a anumitor
informaţii pot conduce la izolarea unor date faţă de utilizatorii neautorizaţi, lucru deplin posibil prin
intermediul imaginilor. Pornind de la aceleaşi tabele de bază, se pot crea un mare număr de tabele
virtuale, în funcţie de situaţie. Tabelele virtuale constituie suportul creării schemelor externe. Odată
definită, o tabelă virtuală poate fi referită ca o tabelă de bază oarecare. De asemenea, ca şi relaţiile de
bază, şi "imaginile" pot fi actualizate, ceea ce atrage modificarea tabelelor statice din care derivă. În
aceste situaţii apar o serie de probleme. Este clar că modificarea conţinutului tabelelor de bază
presupune modificarea conţinutului tabelei (sau tabelelor) derivate. Însă ce informaţii pot fi modificate
în tabelele de bază, pornind de la modificările tabelei virtuale ? Soluţiile implementate în SGBDR-
urile actuale diferă de la caz la caz.
O procedură stocată este o secvenţă de program (cod) care face parte integrantă din baza de
date. Avantajele utilizării procedurilor stocate decurg din faptul că acestea sunt parte din structura
(schema) bazei, fiind păstrare în dicţionarul de date (catalogul sistem). Există mai multe tipuri de
proceduri stocate: funcţii-utilizator pentru validarea tabelelor, funcţii pentru calculul unor valori
implicite, proceduri/funcţii de validare la nivel de linie sau tabelă, funcţii/proceduri de calcul a unor
expresii complexe etc.
Declanşatorul (trigger) este un tip special de procedură stocată care este executată automat
când un eveniment predefinit (inserare, actualizare sau ştergere) modifică o tabelă. Utilitatea
declanşatoarelor este evidentă la formularea unor restricţii mai complexe decât “suportă” comenzile
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 23

CREATE/ALTER TABLE, actualizarea automată a unor atribute calculate, jurnalizarea modificărilor


suferite de baza de date, păstrarea integrităţii referenţiale etc.
Există diferenţe sensibile între SGBD-uri şi în ceea ce priveşte sintaxa declanşatoarelor,
deoarece acestea sunt redactate în extensiile procedurale ale SQL care prezintă diferenţieri majore de
la un producător la altul. Poate fi evocată, spre exemplu, diversitatea declanşatoarelor în Oracle, DB2
etc. faţă de Visual FoxPro.
Astfel, dacă în VFP există numai trei tipuri de declanşatoare, pentru adăugare, pentru
modificare şi pentru ştergere, în standardul SQL şi SGBD-urile puternice (ex. Oracle), pentru fiecare
din cele trei operaţiuni există o primă separare între declanşatoare lansate înaintea operaţiei
(actualizării) şi cele după operaţiune. La această delimitare se mai adaugă şi cea dintre triggere la nivel
de linie, ce intră în acţiune la fiecare inserare/modificare/ştergere a unei linii şi cele la nivel de
comandă care se execută o singură dată, indiferent câte linii afectează comanda de actualizare
respectivă (row versus statement).
24 Baze de date I

CAPITOLUL 3

PROIECTAREA SCHEMEI BAZELOR DE DATE FOLOSIND


NORMALIZAREA

Obiective:
I. Conştientizarea nevoii de normalizare
II. Expunerea cadrului general al normalizării prin descompunere şi prin sinteză
III. Definirea dependenţelor funcţionale, inclusiv a celor parţiale şi tranzitive
IV. Discutarea noţiunilor de relaţie universală şi a condiţiilor necesare pentru prima
formă normalizată
V. Prezentarea detaliată a metodologiei de aducere a unei baze de date în formele
normale 2, 3 şi Boyce-Codd
VI. Definirea dependenţelor multi-valoare şi celei de-a patra forme normale
V. Înţelegerea folosirii cheilor surogat şi dependenţelor de incluziune
VI. Ilustrarea întregului demers al normalizării pentru mai multe cazuri practice
VII. Conştientizarea câtorva dificultăţi şi capcane ale normalizării

Rezultate aşteptate:
A. Înţelegerea modului în care poate fi adaptată normalizarea la proiectarea
schemei bazelor de date
B. Formarea capacităţii de a proiecta schema oricărei baze de date, pornind de la
cerinţele unei aplicaţii reale
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 25

3.1 CE ESTE NORMALIZAREA ?


Dintre multiplele metodologii şi instrumente de proiectare a structurii unei baze de date, în
acest capitol (şi curs) vom trata doar normalizarea. Motivul principal este acela că normalizarea
lucrează strict cu terminologia relaţională, fără a mai defini noţiuni specifice metodologiilor de tip
Entităţii-Relaţii (E/R), Object-Role Modeling etc: clasă de entităţi/relaţii, cardinalităţi, specializare,
generalizare etc.
Totuşi, normalizarea este un demers complex. Chiar dacă pe parcursul acestui capitol voi
încerca să fiu cât mai limpede cu putinţă, cred că este necesară recurgerea la materialul adiţional din
care este extras prezentul material (pentru cei neatenţi, tocmai sunteţi în mijlocul unui moment
publicitar)6.

3.1.1 Nevoia de normalizare


Unul dintre cele mai bune argumente în favoarea normalizării ţine de punerea în evidenţă a
ceea ce s-ar întâmpla în absenţa sa. Să luăm un prim exemplu, cel din figura 3.1 care este dedicat
stocării datelor privitoare la studenţii Facultăţii de Economie şi Administrarea Afacerilor (sau oricare
alta), mai ales în ceea ce priveşte situaţia şcolară a acestora. Fiecare student are un identificator unic -
Matricol, este înscris la o specializare într-un an de studii şi susţine examenele la disciplinele din
planul de învăţământ. Orice disciplină are alocat un număr de credite prin planul de învăţământ al
specializării. Fiecare student poate susţine un examen de mai multe ori până îl promovează (sau este
exmatriculat). Astfel, Zăineanu Ion a picat examenul la Baze de date I în prima sesiune (pe 29 ianuarie
2004), dar l-a luat în restanţe (pe 12 februarie).

STUDENŢI_EXAMENE
Matricol NumePrenume An Specializare CodDisc
EL13455 Popovici I Vasile 3 Informatică economică AI3501
EL13456 Zăineanu W Ion 3 Informatică economică AI3501
EL13457 Abălaşei R Zicu 3 Informatică economică AI3501
EL13455 Popovici I Vasile 3 Informatică economică AI3502
EL13456 Zăineanu W Ion 3 Informatică economică AI3502
EL13457 Abălaşei R Zicu 3 Informatică economică AI3502
EL13456 Zăineanu W Ion 3 Informatică economică AI3501
EL13457 Abălaşei R Zicu 3 Informatică economică AI3502
EL13458 Şpagă M Michael 3 Informatică economică AI3503

DenumireDisc NrCredite DataExamen Nota


Baze de date I 6 29/01/2004 8
Baze de date I 6 29/01/2004 4
Baze de date I 6 29/01/2004 9
Programare vizuală şi RAD 7 01/02/2004 10
Programare vizuală şi RAD 7 01/02/2004 8
Programare vizuală şi RAD 7 01/02/2004 4
Baze de date I 6 12/02/2004 8
Programare vizuală şi RAD 7 15/02/2004 9
Analiza sistemelor informaţionale 6 04/02/2004 7
Figura 3.1 Relaţie supusă normalizării

6
Fotache, M. – Proiectarea bazelor de date. Normalizare şi postnormalizare. Implementări SQL şi Oracle, Editura
Polirom, Iaşi, 2005
26 Baze de date I

Deoarece fiecare linie se referă la un examen susţinut la o anumită dată de un student, cheia
primară a relaţiei este combinaţia (Matricol, CodDisc, DataExamen).

Redundanţe
Este lesne de observat că relaţia de mai sus conţine redundanţe. Astfel, dacă un student
susţine, pe parcursul primelor n semestre de studii, 25 de examene, atunci matricolul, numele, anul şi
specializarea sunt prezente în toate cele 25 de linii. Dacă pentru o disciplină s-au consemnat în baza de
date 1500 de examinări, atunci nu numai codul, ci şi denumirea şi numărul de credite alocat disciplinei
apar de acelaşi număr de ori.
Cu cât baza de date este mai mare, cu atât risipita de spaţiu este mai importantă. Chiar dacă
spaţiul de stocare nu mai este o problemă din punctul de vedere al costului, obezitatea unei tabele
precum cea de mai sus poate atrage serioase probleme de viteză în exploatare (timpi de aşteptare la
preluarea noilor note etc.).

Anomalii la inserare
Să luăm în discuţie studenţii din anul I. După examenul de admitere, ce are loc în lunile iulie
şi septembrie aceştia sunt înmatriculaţi. Dacă, însă, baza de date are schema relaţiei de mai sus,
preluarea este imposibilă până în momentul primului examen al studentului respectiv. Aceasta
deoarece în cheia primară sunt incluse şi atributele CodDisc şi DataEx, iar modelul relaţional
interzice valori nule pentru atributele-cheie (restricţia de entitate). Ori, la data înmatriculării, studenţii
din anul I nu au nici un examen susţinut (asta ar mai trebui !), iar prima sesiune e abia în ianuarie
viitor, aşa că şi CodDisc şi DataEx sunt în acel moment NULL.

Anomalii la modificare
Presupunem că într-o furtunoasă şedinţă de catedră se decide ca disciplina Programare
vizuală şi RAD să fie redenumită Programare I, titulatură sub care vă apărea şi în foile matricole ale
studenţilor din actualul an III care vor absolvi, mai devreme sau mai târziu, specializarea Informatică
economică. În baza de date există câteva sute de înregistrări referitoare la studenţi examinaţi la această
disciplină, şi toate trebuie modificate. Dacă, dintr-un motiv sau altul, modificarea se face pe numai o
parte dintre liniile cu pricina, putem afirma că datele îşi pierd consistenţa. După O'Neil & O'Neil,
avem de-a face cu o anomalie de modificare într-o relaţie atunci când modificarea valorii unui atribut
atrage obligativitatea actualizării aceleaşi valori pe mai multe linii7.

Anomalii la ştergere
Anomaliile la ştergere se manifestă atunci când prin eliminarea unei linii dintr-o tabelă se
pierd involuntar nu numai informaţiile despre entitatea reflectată pe linia respectivă, ci şi alte
informaţii. Astfel, dacă ştergem ultima linie din relaţia de mai sus, cea care conţine nota examenului la
Analiza sistemelor informaţionale susţinut de studentul Şpagă Michael pe 4 februarie 2004, se pierd
nu numai datele despre examenul respectiv, ci şi toate informaţiile despre studentul Şpagă M. Aceasta
deoarece aceasta era singura linie în care apărea studentul cu pricina.

7
[O'Neil & O'Neil 01 ] p.357
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 27

3.1.2 Definirea normalizării


Proiectarea bazelor de date relaţionale (BDR) presupune definirea atributelor, gruparea lor în
tabele, stabilirea legăturilor dintre ele, a mecanismelor de integritate şi securitate, fiind un proces
dificil în care cunoştinţele teoretice, experienţa, inteligenţa, intuiţia şi creativitatea proiectanţilor se
îmbină într-un mod mai mult sau mai puţin armonios. Structura finală a BD trebuie să asigure un
echilibru între, pe de o parte, obţinerea unui volum maxim de informaţii într-un interval de timp cât
mai scurt şi, pe de altă parte, eliminarea anomaliilor de stocare şi actualizare, toate în condiţiile unui
grad apreciabil de securitate şi integritate.
Văzută ca rigoare (matematică sau nu), artă (în sensul de curat… interpretabilă, dar şi de
bazată pe intuiţie, creatoare), “meserie” (deprinsă prin imitaţie şi desăvârşită prin experienţă), ceea ce
se poate spune cu siguranţă despre normalizare este, vorba lui C. J. Date, că nu reprezintă un panaceu.
Există două mari abordări ale normalizării, descompunerea şi sinteza. Ambele discută despre
relaţia iniţială (universală) şi o serie de forme normalizare ale bazei de date. Însă, în timp ce prin
decompunere relaţia iniţială este „spartă” (descompusă) în relaţii tot mai mici, prin sinteză se porneşte
de la dependenţele dintre atributele din bază şi se construieşte schema bazei direct într-o formă
normală acceptabilă.
Teoretic, schema unei BD poate conţine o singură relaţie (tabelă) ce grupează toate atributele
bazei. Aceasta este relaţia universală. Dincolo de avantajul compactării, relaţia universală ridică
serioase probleme privind redundanţa datelor şi generează anomalii importante la adăugarea,
modificarea sau ştergerea unor linii (tupluri), după cum văzut în frugalul exemplu din paragraful 3.1.1.
Ca şi consecinţă directă, este necesară spargerea bazei în mai multe tabele care sunt legate prin
restricţii de integritate referenţială. Este tocmai obiectivul central al normalizării. Spargerea relaţiei nu
trebuie făcută oricum, deoarece apare riscul pierderilor de informaţii. De asemenea, un număr prea
mare de tabele necesită un efort sporit pentru controlul bazei şi respectarea restricţiilor. În plus, trebuie
să ţinem seama de faptul că obţinerea multor informaţii dintr-o bază de date fragmentată este posibilă
prin operaţiunea de joncţiune, mare consumatoare de resurse. Normalizarea îşi propune ca principale
obiective:
minimizarea spaţiului necesar stocării datelor;
minimizarea riscului apariţiei de date inconsistente în cadrul BD;
minimizarea anomaliilor ce pot apărea la actualizare (inserarea datelor, dar mai ales
modificare şi ştergere).;
ameliorarea structurii bazei, reprezentarea diverselor conexiuni dintre atributele bazei.
diminuarea nevoii de reorganizare periodică a modelului.
Obiect al numeroase studii, nu se poate afirma că există o unanimitate de idei şi instrumente
privind normalizarea. Importanţa normalizării nu trebuie absolutizată, deoarece adesea aceasta vine
uneori în contradicţie cu parametri extrem de importanţi ai aplicaţiilor de lucru cu bazele de date:
viteză de acces, securitate mărită, resurse hard/soft disponibile, reducerea traficului pe reţea etc. Se
poate spune că o BD riguros normalizată nu este neapărat una optimă.
Teoria clasică a normalizării este construită în jurul a cinci forme care în literatura noastră sunt
referite ca normale sau normalizate. Codd, părintele modelului relaţional, a definit iniţial trei forme
normale, notate prin 1NF, 2NF şi 3NF. Întrucât, într-o primă formulare, definiţia 3FN ridica ceva
probleme, Codd şi Boyce au elaborat o nouă variantă, cunoscută sub numele Boyce-Codd Normal
28 Baze de date I

Form (BCNF). Deşi este vorba, în principiu, de o formulare mai riguroasă a aceleaşi 3FN, BCNF este
prezentată separat în majoritatea lucrărilor.
Inceput

Relaţie universală - R

nu Există grupuri da
repetitive ?
Atomizarea atributelor

nu Relaţia R are da
cheie primară ?
Descompunere în relaţii
ce au cheie primară

1 FN

nu Există DF da
parţiale ?
Aducerea relaţiilor în 2FN

2FN

nu Există DF da
tranzitive ?
Aducerea relaţiilor în 3FN

3 FN

nu Toţi determinanţii da
sunt chei candidate ?
Aducerea relaţiilor
în BCFN

BCFN

nu da
Există DF
multivaloare ?
Aducerea relaţiilor în 4FN

4 FN

nu Există dependenţe da
de joncţiune ?
Aducerea relaţiilor în 5FN

5 FN

Sfâ rşit

Figura 3.2. Etapele clasice ale normalizării bazelor de date relaţionale

Formele 4 şi 5, legate de numele lui Ronald Fagin, sunt tratate mai cu reţinere, ca să nu spun
pudoare, în literatura consacrată analizei şi proiectării bazelor de date. Ba chiar unele lucrări cu tentă
mai pragmatică se opresc declarat la 3FN, pe care o consideră suficientă în elaborarea BDR.
Fundamentul normalizării BDR îl constituie dependenţele dintre atribute. Primele trei forme
normale pot fi determinate pe baza dependenţelor funcţionale elementare (totale) şi tranzitive. Forma a
patra (4FN) se bazează pe dependenţele multivaloare, în timp ce a cincea formă normală (5FN) pe
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 29

dependenţele de joncţiune. Problema este că dependenţele multivaloare, şi (mai ales) cele de joncţiune
sunt dificil de identificat şi rar întâlnite în practică.
Normalizarea BDR poate fi imaginat ca un proces prin care, pornindu-se de la relaţia iniţială R
(universală), se realizează descompunerea succesivă a acesteia în sub-relaţii după succesiunea din
figura 3.28. Relaţia R poate fi ulterior re-construită din cele n relaţii obţinute în urma normalizării, prin
operaţii de joncţiune aplicate asupra acestora.
Nu putem încheia acest crochiu al normalizării fără a insista asupra faptului că, esenţialmente,
ingredientul acesteia este de natură semantică. Normalizarea reflectă relaţii care se manifestă între
atribute ale bazei. Este imposibil de normalizat o relaţie fără a cunoaşte fenomenul, procesul pe care îl
reflectă.

3.1.3 Descompunere fără pierdere de informaţii


Descompunerea succesivă a relaţiei universale trebuie să se bazeze exclusiv pe dependenţele
între atribute, altminteri este posibilă pierderea de informaţii. Pentru detalii, vezi [Fotache 2005],
paragraful 2.5 (pp.44-46)

3.1.4 Punctul de pornire - relaţia universală


Prima etapă a normalizării constă în identificarea informaţiilor ce trebuie stocate şi gestionate
cu ajutorul bazei de date. Este momentul în care la aplicaţiile complexe intră în scenă analiştii de
sistem, cei care observă cum se derulează procesele, operaţiunile ce constituie obiectul aplicaţiei/bazei
de date, discută cu toate categoriile de utilizatori, încearcă să identifice soluţii anterioare de la aceeaşi
organizaţie sau de la organizaţii similare pentru a putea anticipa problemele cele mai dificile etc.
Pentru aplicaţiile mari munca analiştilor şi proiectanţilor este extrem de laborioasă şi consumatoare de
timp şi nervi. Este unul dintre motivele pentru care această categorie profesională are salarii mari şi
este destul de "curtată". Noi vom încerca să ne plasăm într-o zonă mai "aerisită", fără a cădea in
derizoriu.
Noţiunea de relaţie universală este unul dintre destulele ingrediente ale modelului relaţiona
care încep prin a părea jenant de banal, şi din care, în final, fiecare înţelege ce vrea, fără a ajunge la un
punct comun, ci doar la un „nor de puncte” de vedere comun. Noi o să considerăm relaţia universală
drept relaţia alcătuită din toate atributele identificate ca fiind relevante pentru aplicaţie şi toate
tuplurile posibile cu valorile acestor atribute9. Prin urmare, schema relaţiei universale conţine toate
atributele bazei de date.

Baza de date VÎNZĂRI


Pe parcusul acestui capitol şi următoarelor ne propunem să elaborăm (şi) schema unei prime
baze de date prin care să poată fi gestionate informaţiile relevante despre vânzările unei firme
comerciale, de producţie etc. Pentru o mai bună vizualizare, se poate recurge la o formă tabelară de
prezentare a datelor, în care coloanele ar fi: numele atributului, aşa cum este folosit în baza de date
(ceva mai scurt), numele întreg al atributului, tipul atributului, lungimea, explicaţii suplimentare,
restricţii etc.

8
Vezi şi [Oprea99], p.343
9
Din acest punct de vedere, de plasăm mai aproape de accepţiunea lui Chris Date ([Date04], p.195)
30 Baze de date I

Tabelul 3.1 este ceva mai modest, având doar două coloane, dedicate numelui atributului şi
unor explicaţii sumare. Atributele care interesează în mod deosebit sunt cele legate de:
clienţi: codul, denumirea, adresa, codul poştal, localitateă, judeţul (inclusiv regiunea), codul
fiscal, numărul de telefon;
produse: codul, denumirea, unitatea de măsură, grupa (textile, încălţăminte, băuturi
răcoritoare, băuturi spirtoase etc.), procentul de TVA aplicat;
facturi: numărul, data întocmirii, clientul, observaţii (note despre clauze din factură), apoi
produsele vândute în factura respectivă: linia pe care apare fiecare produs, cantitatea şi preţul
facturat.
încasări: codul unic asociat fiecărei încasări, date despre documentul pe baza căruia se face
încasarea (ordin de plată, cec etc.), numărul facturii încasate, tranşa încasată din factura
respectivă.
Tabel 3.1. VÎNZĂRI - dicţionarul datelor (simplificat)

Atribut Descriere
Jud Indicativul auto al judeţului (2 caractere)
Judeţ Denumirea judeţului
Regiune Regiunea ţării din care face parte judeţul
CodPost Codul poştal al unei adrese
Localitate Denumirea oraşului sau satului
Comuna Denumirea comunei din care face parte satul curent
CodCl Codul firmei client
DenCl Denumirea firmei-client
CodFiscal Codul fiscal al firmei client
StradaCl Strada pe care se află sediul clientul
NrStradaCl Numărul la care se afla adresa clientului
BlocScApCl Eventualele informaţii suplimentare despre adresa clientului: bloc, scară, etaj, apartament
TelefonCl Telefonul firmei client
CodPr Codul produsului / sortimentului comercializat
DenPr Denumirea produsului
UM Unitatea de măsură a produsului/sortimentului
Grupa Grupa sortimentală (Bere, Ciocolată, Cafea etc.) în care se încadrează produsul curent
ProcTVA Procentul TVA aplicat în mod obişnuit produsului
NrFact Numărul facturii emise
DataFact Data întocmirii facturii
Obs Observaţii privitoare la factură
Linie Linia curentă (corespunzătoare unui produs) dintr-o factură
Cantitate Cantitatea vândută din produs în factură (pe linia curentă)
PreţUnit Preţul unitar de vânzare (fără TVA) al produsului în factură (pe linia curentă)
CodÎnc Codul încasării
DataÎnc Data încasării (în care banii intră în casierie sau în contul bancar)
CodDoc Codul documentului de încasare
NrDoc Numărul documentului de încasare
DataDoc Data la care a fost întocmit documentul de încasare
Tranşă Tranşa primită din factură prin încasarea (documentul) curentă

Una dintre cele mai importante observaţii ce se cuvine a fi făcute în acest moment ţine de
faptul că o serie de informaţii foarte importante, precum:
valoarea fără TVA a unui produs facturat (de pe o linie a fiecărei facturi);
TVA calculată pe linia unei facturi (pentru un produs vândut);
valoarea cu TVA a unei linii (produs) dintr-o factură;
valoarea fără TVA a unei facturi;
TVA colectată aferentă unei facturi;
valoarea totală (cu TVA) a unei facturi;
valoarea totală a încasărilor pentru o factură;
valoarea totală a vânzărilor către fiecare client;
valoarea totală a încasărilor de la un client;
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 31

restul de plată pentru fiecare client (diferenţa vânzări - încasări);


valoarea vânzărilor pentru fiecare localitate;
valoarea vânzărilor pentru fiecare judeţ;
valoarea vânzărilor pentru fiecare regiune (Moldova, Banat etc.);
şi multe altele au fost eliminate din dicţionarul de date, deoarece pot fi calculate pornind de la cele
prezente în dicţionar. Vom vedea ceva mai târziu că atunci când este vorba de date accesate în mod
frecvent, pentru calcularea cărora este necesar un consum de resurse important, se poate recurge la
introducerea controlată a unor atribute redundante.
O factură conţine mai multe linii şi poate fi încasată în una, două sau mai multe tranşe.
Deoarece există un interval între momentul întocmirii de către client a documentului de plată
(DataDoc) şi momentul intrării efective a banilor în contul (sau casieria) firmei ( DataÎnc), se
folosesc ambele atribute.

Baza de date FILMOGRAFIE


Pentru un centru de închirieri video se pune problema constituirii unei baze de date cu
informaţii despre filmele aflate pe casetele sau DVD-urile oferite (legal) spre închieriere. Fiecare film
are un cod unic (identificator), iar atributele principale sunt cele din tabelul 3.2.
Tabel 3.2. FILMOGRAFIE - dicţionarul datelor (simplificat)

Atribut Descriere
IdFilm Codul unic al filmului
TitluOriginal Titlul în engleză, franceză etc., aşa cum apare la lansarea filmului
TitluRO Traducerea românească a titlului original
AnLans Anul lansării
Producători Producătorul sau producătorii filmului
Regizori Regizorul sau regizorii filmului
Distribuţie Actorii şi rolurile interpretate în film
Genuri Genul/genurile la care se încadrează filmul (horror, comedie etc.)
Premii Numele premiului - tipul (Oscar, Leul de argint, Ursul de aur etc.), categoria (cel mai bun film,
cel mai bun actor în rol principal) anul premiului şi, eventual, numele actorului.

Un film poate fi produs şi regizat de una, două sau mai multe persoane. Pentru fiecare actor
distribuit în film interesează şi personajul sau personajele (dacă actorul e în dublu rol) interpretate. O
peliculă se poate încadra la mai multe categorii (dramă, comedie) simultan. Dacă un premiu se referă
la regie, scenariu etc., interesează numele, tipul şi anul decernării, iar dacă se referă la o performanţă
actoricească, la informaţiile anterioare se adaugă şi numele actorului laureat. Practic, două tupluri din
această relaţie s-ar prezenta că în figura 3.3.

IdFilm TitluOriginal TitluRO AnLans Producători Regizori


11899 As Good As It Gets Mai bine nu se poate 1997 James Brooks, Bridget Johnson, James Brooks
Kristi Zea
12345 Bicentennial Man Omul bicentenar 1999 Michael Barnathan, Chris Chris Columbus
Columbus, Gail Katz

Distribuţie Genuri Premii


Jack Nicholson (Levin Udall) comedie Oscar - cel mai bun actor în rol principal (Jack Nicholson) - 1998
Helen Hunt (Carol Connelly) dramă Oscar - cea mai bună actriţă în rol principal (Helen Hunt) - 1998
Greg Kinnear (Simon Bishop) romantic Globul de aur - cea mai bună imagine - 1998
Cuba Gooding Jr. (Frank Sachs) Globul de aur - cel mai bun actor într-o comedie/musical (Jack
Nicholson) - 1998
Globul de aur - cea mai bună actriţă într-o comedie/musical
(Helen Hunt) - 1998
Robin Williams (Andrew Martin) SF
Embeth Davidtz (Little Miss dramă
32 Baze de date I

Amanda Martia / Portia romantic


Charney)
Sam Neil (Richard Martin)
Oliver Platt (Rupert Burns)
Kiersten Warren (Galatea)
Figura 3.3. O descompunere a relaţiei universale FILMOGRAFIE

Aceste două relaţii universale, ca şi altele, vor fi supuse, în capitolele următoare, normalizării
şi tuturor discuţiilor şi problemelor legate de acest demers.

3.2 PRIMA FORMĂ NORMALIZATĂ


Prima formă normalizată (1NF) este, în general, tratată cu superficialitate de majoritatea
practicienilor în materie de proiectare a bazelor de date. Aceasta deoarece principala cerinţă –
atomicitatea valorilor – este un deziderat uşor de îndeplinit, cel puţin la prima vedere. Cele mai
sensibile chestiuni în aducerea relaţiilor în 1NF sunt legate de atomicitate, eliminarea grupurilor
repetitive, precum şi de stabilirea cheii primare, cheie primară care devine o problemă delicată atunci
când se porneşte de la o relaţie cu zeci sau sute de atribute.
Pe de altă parte, noţiunea de atomicitate este una destul de alunecoasă. Deşi în primele lucrări
Codd nu formulează o definiţie riguroasă a atomicităţii, timp de aproape trei decenii majoritatea
autorilor din "zona" relaţionalului au considerat ca pe o primă "poruncă" a unei relaţii (tabele)
necesitatea ca valoarea oricărui atribut pe orice linie (tuplu) să fie una scalară (atomică,
nedecompozabilă). De câtva timp, însă, Chris Date şi alţi relaţionişti încearcă că depăşească limitele
atomicităţii, înlocuind domeniile cu tipuri care pot fi atât scalare, cât şi compozite (definite de
utilizator).
Teoria normalizării nu face parte, propriu-zis, din modelul relaţional. Dintre toate formele
normalizate, doar prima are caracter de obligativitate. O bază de date este normalizată dacă toate
relaţiile (tabelele) care o alcătuiesc se află măcar în 1NF. Celelalte forme normale sunt dezirabile
pentru diminuarea redundanţelor, spaţiului ocupat pe disc sau anomaliilor manifestate la actualizare,
însă nu sunt obligatorii.
După Codd, o relaţie este în prima formă normală dacă… nici unul dintre domeniile sale nu
conţine elemente care sunt, la rândul lor, seturi (ansambluri)10. Se cuvine de adăugat remarca lui Date
care crede ca ar fi fost mai nimerit ca, în locul termenului domenii, Codd să-l fi folosit pe cel de
atribute. Actualmente, mulţi "relaţionişti" preferă termenul tip celui de domeniu11.
Unele lucrări12 definesc o relaţie aflată în 1NF ca acea relaţie în care fiecare atribut prezintă
numai valori atomice, adică toate atributele sunt ne-decompozabile; după R.Riordan, o relaţie este în
1NF dacă domeniile pe care sunt definite atributele relaţiei sunt scalare13. În ediţia a treia a cărţii
dedicate sistemelor de baze de date, Connoly şi Begg sunt mai tranşanţi: o relaţie în 1NF este o relaţie
în care intersecţia oricărei linii cu oricare coloană conţine o valoare şi numai una14. Astfel, 1NF
respinge ideea de set de valori, tuplu de valori sau combinaţia acestora ca valoare a unui singur atribut
pe un oricare tuplu (linie) a relaţiei.

10
[Codd72]
11
[Date04]
12
[Date86], [Miranda&Busta90], [Connoly s.a.96], [Dollinger98]
13
[Riordan99]
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 33

Alţi autori formează explicit pentru 1NF şi restricţia: toate atributele ce compun relaţia să fie
în dependenţă funcţională faţă de cheia relaţiei, ceea ce este, oarecum, o tautologie, întrucât una
dintre "poruncile" valabile pentru orice tabelă stipulează negru pe alb că într-o relaţie nu pot exista
două tupluri identice sau, altfel spus, într-o relaţie trebuie să existe măcar un atribut sau combinaţie de
atribute ale căror valori să deosebească orice tuplu de toate celelalte sau orice relaţie posedă o cheie
primară.
Formularea care a generat cele mai multe confuzii este legată de noţiunea de grupuri
repetitive15: O relaţie în 1NF nu trebuie să conţină grupuri repetitive.
De obicei, noţiunea de grup repetitiv este raportată direct la cea de atomicitate. Diferenţierea
atomicitate – grup repetitiv apare în [Lungu s.a.95], pentru care un atribut atomic este unul simplu (ne-
compus): „O relaţie este în 1NF dacă domeniile pe care sunt definite atributele relaţiei sunt constituite
numai din valori atomice (elementare). În plus, un tuplu nu trebuie să conţină atribute sau grupuri de
atribute repetitive”16. Din păcate, autorii se opresc aici cu definiţia, păstrând o remarcabilă discreţie
vis-a-vis de ceea ce consideră grup repetitiv. La fel stau lucrurile şi la dna. Ileana Popescu17.
Un plus de claritate aduce definiţia lui Toby Teorey, pentru care o relaţie este în 1NF dacă şi
numai dacă toate coloanele conţin numai valori atomice; aceasta înseamnă că nu există grupuri
(coloane) repetitive în cadrul vreunei linii a tabelei. Imediat autoarea precizează că un grup repetitiv
apare într-o tabelă atunci când unui atribut multi-valoare îi sunt permise două sau mai multe valori
reprezentate în cadrul unei aceleaşi linii18. Sau, după cum spunea Chris Date până în ediţia a şasea a
celei mai cunoscute cărţi a sa, un grup repetitiv reprezintă o coloană care conţine câteva valori în
fiecare linie, sau, altfel spus, un număr diferit de valori pe linii diferite19.
În concluzie, aducerea unei relaţii în 1NF presupune discutarea următoarelor elemente:
raportul atribut simplu/atribut compus;
grupuri repetitive de atribute (pe orizontală);
grupuri repetitive de valori în cadrul fiecărei celule a tabelei.

Atribute simple şi atribute compuse

Primele vizate de “stigmatul” neatomicităţii sunt atributele compuse. Cu ani buni în urmă, în
cursurile de profil, exemplul clasic al unui atribut compus era DataCalendaristică, alcătuit, cum
altfel, din câmpurile: Ziuă, Lună, An. Era vremea înfloritoare a COBOLului şi FORTRANului, când
nu existau mecanisme de declarare şi gestionare a variabilelor şi atributelor de tip DATE; verificarea
corectitudinii datei calendaristice cădea în sarcina programatorului ce trebuia să scrie o rutină specială.
Astăzi, nu există SGBD sau mediu de programare care să nu prezinte tipul de dată DATE prin
care se gestionează simultan toate cele trei elemente; prin funcţii de conversie se pot obţine: ziua
(DAY), luna (MONTH), anul (YEAR), ba chiar calcula numărul de zile, luni, ani dintre două date

14
[Connoly & Begg 02], p.388
15
[Date86], [Pratt&Adamski91], [Oprea99]), [Lungu s.a.95]
16
[Lungu s.a. 95], p.169
17
[Popescu 01], p.78
18
[Teorey99], p.99
19
Preluare din ediţia a opta a cărţii ([Date 04], p.153), unde Date se autocitează pentru a demonstra că
înţelesese greşit noţiunea de domeniu (tip) şi, în consecinţă greşise atunci când a formulat definiţia 1NF.
34 Baze de date I

calendaristice şi alte minunăţii. Ca să nu mai vorbim de tipul DATETIME şi acesta omniprezent în


produsele software ale zilelor noastre.
Un alt exemplu celebru privind atribute compuse (non-atomice) este Adresa, discutat în
capitolul 2. Fiind alcătuită din Stradă, Număr, Bloc, Scară, Etaj, Apartament, plus CodPoştal
(după noul sistem de codificare) discuţia despre atomicitatea adresei pare de prisos, iar descompunerea
sa imperativă. Din nou însă trebuie să ne raportăm la obiectivele bazei. Fără îndoială că, pentru BD a
unei filiale CONEL sau ROMTELECOM, sau pentru poliţie, preluarea separată a fiecărui element
constituent al adresei este vitală. Interesează, spre exemplu, ce abonaţi sunt pe strada Tranziţiei sau
câte cereri sunt depuse de cetăţeni de pe străzile Lascăr Catargi, Titu Maiorescu şi Marina Scupra.
Sau, un cioranian ce lucrează în cadrul Poliţiei, ar putea fi interesat să afle dacă gradul de sinucidere al
persoanelor ce locuiesc la etajul trei este mai mare decât cel al persoanelor de la parter.
Cu totul altfel stau lucrurile pentru un importator direct, pentru un mare en-grossist sau pentru
o firmă de producţie. Partenerii de afaceri ai acestora sunt persoane juridice, iar adresa interesează
numai la nivel general. Este drept că imediat după Revoluţie deveniseră mari furnizori naţionali de
cupru şi aluminiu şi persoane fizice (vezi cazul bulibaşei din Ciurea, dar asta-i altă poveste…).
În relaţia universală FILMOGRAFIE, al cărei dicţionar de date simplificat este cel din tabelul
3.2, cel puţin două atribute sunt compuse şi trebuie "sparte", Distribuţie şi Premii. Astfel, în
Distribuţie pot fi delimitate măcar două informaţii, Actor şi Rol. În cazul premiilor, ar trebui
detaliată discuţia prin introducerea atributelor:
denumire premiu (Oscar, Globul de aur etc.);
locul atribuirii (Hollywod, Cannes, Berlin, Veneţia, Bucureşti etc.);
categoria (regie, scenariu, actor în rol principal etc.)
anul decernării;
numele actorului, dacă categoria este pentru interpretare.
Astfel, noua formă a dicţionarului de date este cea din tabelul 3.3.
Tabel 3.3. FILMOGRAFIE - dicţionarul datelor (simplificat) - versiunea 2
Atribut Descriere
IdFilm Codul unic al filmului
TitluOriginal Titlul în engleză, franceză etc, aşa cum apare la lansare filmului
TitluRO Traducerea românească a titlului original
AnLans Anul lansării
Producători Producătorul sau producătorii filmului
Regizori Regizorul sau regizorii filmului
Rol Rolul din film
Actor Actorul care interpretează rolul/filmul curente
Genuri Genul/genurile la care se încadrează filmul (horror, comedie etc.)
DenPremiu Numele premiului - tipul (Oscar, Leul de argint, Ursul de aur etc.)
LocDecernare Locul în care se organizează festivitatea sau festivalul
Categorie Categoria premiului (pentru ce anume se acordă premiul)
AnPremiu Anul decernării

Concluzionând în termeni rabinici, ambele variante, descompunerea sau nedescompunerea


atributelor compuse, sunt valabile de la caz la caz, alegerea soluţiei fiind în concordanţă cu specificul
bazei de date (şi inspiraţia proiectantului).
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 35

Despre grupuri repetitive şi urmările lor

O relaţie (tabelă) în 1NF nu trebuie să conţină atribute care se repetă ca grupuri. Într-o altă
formulare, toate liniile unei tabele trebuie să conţină acelaşi număr de atribute. Fiecare celulă a tabelei
(intersecţia unei coloane cu o linie), altfel spus, valoarea unui atribut pe o linie (înregistrare), trebuie
să fie atomică. După [Connoly s.a.96], un grup repetitiv este un atribut sau grup de atribute dintr-o
tabelă care apare cu valori multiple pentru o singură apariţie a cheii primare a tabelei ne-normalizate.
Să luăm exemplul tabelei BIBLIOTECĂ din figura 3.4. Tabela gestionează informaţii despre
cărţile existente în depozitul bibliotecii Facultăţii de Economie şi Administrarea Afacerilor (FEAA).
De remarcat că în blibliotecă există două exemplare ale cărţii cu ISBN-ul 973-683-709-2 şi trei
exemplare din cea dedicată Visual FoxPro. Prima carte a fost scrisă de patru autori şi îi sunt asociate
opt cuvinte-cheie, iar a doua are un singur autor. Relaţia nenormalizată conţine trei grupuri repetitive:
Cote, Autori şi CuvinteCheie.
BIBLIOTECĂ
ISBN Titlu Cote Autori
973-683-889-7 Visual FoxPro. Ghidul dezvoltării III-13421, III-13422, Marin Fotache, Ioan Brava, Cătălin
aplicaţiilor profesionale III-13423 Strâmbei, Liviu Creţu
973-683-709-2 SQL. Dialecte DB2, Oracle şi III-10678, III-10679 Marin Fotache
Visual FoxPro

Editura LocSediuEd AnApariţie CuvinteCheie


Polirom Iaşi 2002 baze de date, SQL, proceduri stocate, FoxPro,
formulare, orientare pe obiecte, client-server, web
Polirom Iaşi 2001 baze de date, algebră relaţională, SQL
Figura 3.4. Relaţia universală nenormalizată BIBLIOTECĂ
În paragrafele 3.3.1 (pp.55-56), 3.3.2 (pp.57-61) şi 3.3.3 (pp.61-67) din [Fotache 2005] sunt
prezentate câteva soluţii de eliminare a grupurilor repetitive:
adăugarea de atribute suplimentare (grupuri repetitive pe orizontală);
adăugarea unui număr minim de linii – o valoare atomică pentru fiecare atribut compozit;
adăugarea unui nr. de linii egal cu suma valorilor elementare ale atributelor de tip grup repetitiv
adăugarea unui număr maxim de tupluri egal cu produsul valorilor elementare ale atributelor de tip
grup repetitiv (vezi figura 3.5).
BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 (fragment)
ISBN Titlu Cotă Autor
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Marin Fotache
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Ioan Brava
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Cătălin Stâmbei
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Liviu Creţu
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Marin Fotache
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Ioan Brava
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Cătălin Stâmbei
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale III-13421 Liviu Creţu
... (încă 88 de linii pentru cartea de Visual FoxPro, plus 6 pentru cea de SQL )

Editura LocSediuEd AnApariţie CuvântCheie


Polirom Iaşi 2002 baze de date
Polirom Iaşi 2002 baze de date
Polirom Iaşi 2002 baze de date
Polirom Iaşi 2002 baze de date
Polirom Iaşi 2002 SQL
Polirom Iaşi 2002 SQL
Polirom Iaşi 2002 SQL
Polirom Iaşi 2002 SQL
...
Figura 3.5. Completarea cu tupluri - soluţia 3
36 Baze de date I

eliminarea grupurilor de repetitive prin contruirea de noi relaţii.

Trebuie precizat, însă, că nu întotdeauna grupurile repetitive reprezintă o problemă majoră.


Spre exemplu, într-o relaţie gen PERSOANE_CONTACT atributul Telefoane poate avea valori ne-
atomice, deoarece pot exista mai multe numere atât la birou, cât şi mobile (mulţi manageri au câte un
aparat pe fiecare reţea - Orange, Vodafone, Zapp)...

3.3 DEPENDENŢE FUNCŢIONALE


Problema stabilirii dependenţelor dintre atribute este fundamentală în procesul elaborării
bazelor de date relaţionale. Aducerea structurii bazei într-o variantă care minimizează redundanţele şi
anomaliile manifestate la adăugarea/modificarea/ştergerea unor linii din tabele presupune parcurgerea
unui proces de normalizare a relaţiilor, normalizare care este centrată tocmai pe conceptul de
dependenţă dintre atributele bazei.

3.3.1 Definţii
Dependenţa funcţională reprezintă o generalizare a conceptului de cheie primară20. Desemnăm
prin Data2 un atribut sau combinaţie de atribute dintr-o relaţie oarecare şi prin Data1 un alt atribut
sau grup de atribute din aceeaşi relaţie. Se spune că Data2 este în dependenţă funcţională (sau
depinde funcţional) de Data1 atunci când cunoaşterea unei valori pentru Data1 permite
determinarea (cunoaşterea) a maximum o valoare pentru Data2. Data1 se numeşte sursa sau
determinantul dependenţei funcţionale, iar Data2 reprezintă destinaţia (scopul sau determinatul)
dependenţei funcţionale.
Formal, dacă notăm relaţia R {A1, A2,...., An} - unde R este numele relaţiei, Ai sunt atributele
relaţiei R - şi prin X şi Y două subansambluri din {A1, A2,...., An}, se spune că există o dependenţă
funcţională între X şi Y (simbolizată X Y) dacă şi numai dacă :
fiecare apariţie (valoare) a lui X poate fi asociată unei singure apariţii a lui Y,
două apariţii identice ale lui X nu pot fi asociate decât aceleiaşi apariţii a lui Y.
Într-o prezentare şi mai explicită, Date21 defineşte dependenţa funcţională după cum urmează: dată
fiind o relaţie R, subansamblul de atribute Y din R depinde funcţional de subansamblul X (tot din R),
dacă şi numai dacă, ori de câte ori două tupluri din R prezintă aceeaşi valoare pentru X, obligatoriu
valoarea celor două tupluri este identică şi pentru Y.
Se spune despre o dependenţă că este trivială dacă şi numai dacă destinaţia este un subset al
sursei: dacă Y X, atunci X Y.

Revenim asupra relaţiei universale VÎNZĂRI prezentată în tabelul 3.1. Cunoaşterea numărului
facturii permite determinarea cu exactitate a codului clientului, deoarece o factură este (conform legii
şi metodologiei în vigoare) întocmită pentru un singur client. Se poate scrie:
NrFact DataFact; NrFact CodCl; NrFact Obs

20
Vezi şi [Garcia-Molina s.a.02], [Date86]
21
[Date86], p.365
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 37

dar şi
NrFact (DataFact, CodCl); NrFact (DataFact, CodCl, Obs)

Situaţia de mai sus se încadrează în cazurile în care sursa (determinantul) este cheia primară a
relaţiei. Implicit, în orice relaţie R există dependenţele funcţionale: cheia lui R celelalte atribute
ale lui R.
Luăm un alt exemplu: un cod poştal nu poate desemna adrese din oraşe şi comune diferite. Se
poate scrie, deci: CodPoştal Localitate şi CodPoştal Judeţ.

Observaţii
1. Uneori, începătorii au probleme în ceea ce priveşte "sensul" dependenţelor funcţionale. Dependenţa
funcţională CodPoştal Localitate nu înseamnă că o localitate are un singur cod poştal, ci,
invers, că unui cod poştal îi corespunde o singură localitate.
2. dependenţă funcţională în care scopul (destinaţia) este alcătuit dintr-un singur atribut se
numeşte dependenţă funcţională canonică sau dependenţă funcţională în formă canonică22.
Două atribute nu sunt în dependenţă funcţională atunci când cunoaşterea unei valori oarecare
a primului atribut:
fie nu permite cunoaşterea nici uneia dintre valorile celui de-al doilea (nu există nici un
"raport" între cele două atribute); în acest caz dependenţa ne-funcţională echivalează cu
inexistenţa vreunei dependenţe între cele două atribute;
fie permite cunoaşterea mai multor valori ale celui de-al doilea atribut.

În relaţia VÎNZĂRI între atributele DataFact şi CodCl nu există dependenţă funcţională. O


valoare a atributului DataFact nu furnizează nici o informaţie cu privire la valoarea de pe aceeaşi
linie a atributului CodCl. Se scrie: DataFact / CodCl. De asemenea, deoarece pentru un client
se pot întocmi mai multe facturi (aferente fiecărei vânzări): CodCl / NrFact. Într-o factură se
poate consemna vânzarea mai multor produse/servicii; prin urmare, unei valori a NrFact, îi corespund
mai multe valori ale atributului CodPr. Se notează: NrFact / CodPr.

Să examinăm în continuare tabela de mai jos.


NotaContabilă Data Operaţiune ContDebitor ContCreditor Suma
150 30.11.2006 1 300 401 10000000
150 30.11.2006 1 4426 401 1800000
150 30.11.2006 2 401 5311 7000000
150 30.11.2006 2 401 5121 4800000
151 01.12.2006 1 5121 700 5250000
Figura 3.6. Tabela LINII_ARTICOLE_CONTABILE
- o notă contabilă este alcătuită din mai multe articole contabile:
(NotaContabilă, Data) / Operaţiune
- un articol contabil (înregistrare) poate fi compus, adică alcătuit din mai multe corespondenţe
ContDebitor = ContCreditor:
(NotaContabilă, Data, Operaţiune) / ContDebitor
(NotaContabilă, Data, Operaţiune) / ContCreditor
(NotaContabilă, Data, Operaţiune, ContDebitor) / ContCreditor

22
[Saleh94], p.76. Vezi şi [Garcia-Molina s.a 01], p.89
38 Baze de date I

(NotaContabilă, Data, Operaţiune, ContCreditor) / ContDebitor


Atunci când firmele îşi centralizează operaţiunile, utilizând de la o lună la alta aceleaşi numere pentru
notele contabile: NotaContabilă / Data.
Pe lângă aceste două exemple, am mai putea aminti faptul că localităţi cu aceeaşi denumire
există în mai multe judeţe. Spre exemplu, localitatea Vînători există în judeţele Vrancea, Galaţi, Iaşi,
Neamţ etc. Aceasta înseamnă că: Localitate / Judeţ
Dependenţele funcţionale reprezintă legături semantice între atribute ale bazei de date. Au fost
propuşi algoritmi de identificare şi gestionare a dependenţelor funcţionale într-o relaţie. Cea mai mare
parte a acestora sunt total inutili, pentru că, efectiv, nimeni nu poate stabili o dependenţă fără a
cunoaşte regula sau restricţia care se ascunde în spatele acesteia, altfel spus, fără a cunoaşte temeinic
realitatea modelată. Cu atât mai mult calculatorul care are mari probleme în a lucra la nivel semantic,
ca să nu mai vorbim de cel pragmatic.

3.3.2 Dependenţe funcţionale cu sursa compusă


Există dependenţe funcţionale care prezintă în partea stângă (sursa dependenţei funcţionale
sau determinantul) două sau mai multe atribute. Acestea sunt dependenţele funcţionale cu sursa
compusă. Astfel, în relaţia VÎNZĂRI:
- o factură conţine mai multe linii: NrFact / Linie
- pe o factură pot apărea mai multe produse: NrFact / CodPr
- un produs apare, de obicei, pe mai multe facturi: CodPr / NrFact
- pentru fiecare factură, o linie este unică: (NrFact, Linie) CodPr; (NrFact, Linie)
Cantitate; (NrFact, Linie) PreţUnit
- dacă stabilim că un produs nu poate apărea pe o factură decât o singură dată, atunci sunt valabile DF:
(NrFact, CodPr) Linie; (NrFact, CodPr) Cantitate; (NrFact, CodPr)
PreţUnit

În tabela LINII_ARTICOLE_CONTABILE din figura 3.6 stabilirea dependenţelor funcţionale


depinde de modul în care firma îşi organizează evidenţa contabilă. Astfel, marea majoritate a firmelor
îşi centralizează operaţiunile, pe tipuri (intrări de materiale, consumuri de materiale, operaţiuni legate
de casierie, conturile din bancă etc.), utilizând, de la o lună la altă, aceleaşi numere pentru notele
contabile (ex: nota 1 - încasări prin casierie, nota 10 - pentru înregistrarea amortizării etc.). În acest
caz, există dependenţa funcţională: (1) (NotaContabilă, Data, Operaţiune, ContDebitor,
ContCreditor) Suma. Dacă unele firme, cu un volum de activitate mai redus, ar conta fiecare
document primit/întocmit, generând astfel o nouă notă contabilă, dependenţele funcţionale s-ar putea
scrie sub forma: (2) NotaContabilă Data; (3) (NotaContabilă,Operaţiune, ContDe-
bitor, ContCreditor) Suma.
Fireşte, este valabilă şi dependenţa (4) (NotaContabilă, Operaţiune, ContDebitor,
ContCreditor) Data

Tabela BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 din figura 3.5, aflată în prima formă


normală, prezintă ca şi cheie primară combinaţia (Cota, Autor, CuvântCheie). De aceea, se pot
inventaria următoarele cinci dependenţe funcţionale, în care destinaţii sunt atributele necheie.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 39

(1) (Cota, Autor, CuvântCheie) ISBN


(2) (Cota, Autor, CuvântCheie) Titlu
(3) (Cota, Autor, CuvântCheie) Editura
(4) (Cota, Autor, CuvântCheie) LocSediuEd
(5) (Cota, Autor, CuvântCheie) AnApariţie
Alte elemente luate în discuţie pentru stabilirea DF:
- o carte poate fi scrisă de mai mulţi autori: ISBN / Autor
- un autor scrie mai multe cărţi: Autor / ISBN şi despre mai multe subiecte: Autor /
CuvântCheie
- într-o carte sunt tratate mai multe subiecte: ISBN / CuvântCheie
- pentru o carte sunt în depozit mai multe exemplare: ISBN / Cota
Cota este codul care identifică în mod unic o carte aflată în depozit, aşadar:
(6) Cota ISBN (7) Cota Titlu (8) Cota Editura
(9) Cota LocSediuEd (10) Cota AnApariţie
Fiecare titlu de carte este identificat fără ambiguitate de ISBN:
(11) ISBN Titlu (12) ISBN Editura (13) ISBN LocSediuEd
(14) ISBN AnApariţie
O editură are un singur sediu central: (15) Editura LocSediuEd.
Prin urmare, dintre cele 15 DF inventariate, primele cinci au sursa compusă, celelalte sursa
simplă.

În baza de date VÎNZĂRI, dat fiind numărul de atribute şi specificul aplicaţiei, în acest caz
discuţiile sunt ceva mai laborioase:
- fiecare judeţ are un nume şi un indicativ unice:
(1) Jud Regiune (2) Judeţ Regiune (3) Jud Judeţ
(4) Judeţ Jud
- pe baza discuţiei de la exemplul anterior (CODURI_NOI_V2):
(5) CodPost Localitate (6) CodPost Comuna (7) CodPost Jud
(8) CodPost Judeţ
- fiecare firmă-client este identificată în mod unic atât prin cod (CodCl):
(9) CodCl DenCl (10) CodCl CodFiscal (11)CodCl StradaCl
(12) CodCl NrStradaCl (13) CodCl BlocScApCl
(14) CodCl TelefonCl (15) CodCl CodPost (16) CodCl Localitate
(17) CodCl Comuna (18) CodCl Jud (19) CodCl Judet
(20) CodCl Regiune
- cât şi prin cod fiscal (CodFiscal):
(21) CodFiscal DenCl (22) CodFiscal CodCl
(23) CodFiscal StradaCl (24) CodFiscal NrStradaCl
(25) CodFiscal BlocScApCl (26) CodFiscal TelefonCl
(27) CodFiscal CodPost (28) CodFiscal Localitate
40 Baze de date I

(29) CodFiscal Comuna (30) CodFiscal Jud


(31) CodFiscal Judet (32) CodFiscal Regiune
- elementul care identifică un produs este codul acestuia:
(33) CodPr DenPr (34) CodPr UM (35) CodPr Grupa
(36) CodPr ProcTVA
- o factură se întocmeşte pentru un singur client:
(37) NrFact CodCl (38) NrFact CodFiscal
- o factură se întocmeşte o singură dată: (39) NrFact DataFact (40)NrFact Obs
- o factură conţine mai multe linii: NrFact / Linie
- fiecare linie dintr-o factură se referă la vânzarea unui produs (CodPr), indicând cantitatea şi preţul
unitar la care s-a făcut vânzarea:
(41) (NrFact, Linie) Cantitate (42) (NrFact, Linie) PreţUnit
(43) (NrFact, Linie) CodPr (44) (NrFact, Linie) DenPr
(45)(NrFact, Linie) UM (46) (NrFact, Linie) ProcTVA
- într-o factură, un produs apare pe o singură linie:
(47) (NrFact, CodPr) Cantitate (48) (NrFact, CodPr) PreţUnit
(49) (NrFact, CodPr) Linie
- o încasare are la bază un document primar (Ordin de plată, Chitanţă etc.):
(50) CodÎnc DataÎnc (51) CodÎnc CodDoc
(52) CodÎnc NrDoc (53) CodÎnc DataDoc
- documentele de încasare pot prezenta numere similare de la un client la altul; spre exemplu, Ordinul
de plată cu nr. 101 poate fi emis de mai mulţi clienţi (bineînţeles, este vorba de trei documente diferite
care au însă acelaşi număr, poate chiar şi dată): (CodDoc, NrDoc) / DataDoc; (CodDoc,
NrDoc) / NrFact; (CodDoc, NrDoc) / CodCl
- o factură este încasată în mai multe tranşe: NrFact / CodÎnc
- o încasare poate achita mai multe facturi: CodÎnc / NrFact
- într-o încasare, o tranşă se referă la o singură factură: (54) (CodÎnc, NrFact) Tranşă,
nu însă şi (CodÎnc, Tranşă) NrFact, deoarece, într-o aceeaşi încasare pot exista două tranşe
egale (ex. 5 000 000 lei) referitoare la două facturi diferite.

3.3.3 Dependenţe funcţionale simetrice şi redundante


Deseori, o bază de date sau relaţie prezintă mai multe chei candidat. Alegerea dintre acestea a
cheii primare are în vedere, pe lângă unicitate, compoziţie minimală şi valori nenule ale oricărui
atribut component, şi elemente ce ţin de facilitatea utilizării: uşurinţa reţinerii (de către utilizator),
lungime cât mai mică, constanţă în timp etc.
În procesul normalizării, problemele ridicate de aceste dependenţe reciproce dintre cheile
candidat trebuie tratate cu prudenţă. De cele mai multe ori, este recomandat ca dintre dependenţele ale
unei relaţii să fie eliminate toate cele în care sursa este o cheie candidat şi păstrarea numai celor în care
determinantul este atributul cheie-primară.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 41

Revenim la cele 54 de dependenţe din relaţia universală VÎNZĂRI tratate în urmă cu câteva
pagini. Este evident că dependenţele (3), Jud Judeţ, şi (4), Judeţ Jud, sunt simetrice.
Deşi corecte, şi dependenţele (1) - Jud Regiune - şi (2) - Judeţ Regiune - "se calcă
pe picior”. Pentru comoditate, putem alege atributul Jud ca identificator "de bază" al unui judeţ,
eliminând (pe nedrept), pe lângă (4), toate DF în care sursa simplă este atributul Judeţ iar destinaţiile
identice cu cele ale dependenţelor în care sursă este Jud. Ce-i drept, efortul nu este chiar
supraomenesc, deoarece este o singură dependenţă de acest tip, (2).
La fel se pune problema şi pentru perechile de atribute CodCl şi DenCl şi CodCl şi
CodFiscal. Toate identifică un acelaşi tip de entitate - o firmă client. Investim CodCl drept
"reprezentant unic" al unui client. DenCl nefiind luat prea mult în seamă în setul de DF, avem de
eliminat doar DF în care CodFiscal este sursă simplă: (21) - (32). În privinţa perechii CodPr şi
DenPr nu avem a ne epuiza, deoarece, aproape intenţionat, au fost omise din setul DF cele în care
DenPr apare ca sursă.

Atenţie ! Nu facem acelaşi lucru în relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 cu atributele Cota şi


ISBN, deoarece primul se referă la un exemplar "fizic" al unei cărţi din bibliotecă, în timp ce al doilea se referă
la un titlu (carte publicată la o editură).

Tipul acesta de redundanţă e uşor de identificat şi eliminat, chiar dacă nu se leagă vizibil de
nici o teoremă sau lemă ce constituie fundamentul normalizării. Setul DF conţine însă şi un alt tip de
redundanţă/circularitate. Astfel, deoarece fiecare linie dintr-o factură se referă la un produs vândut la
un preţ unitar şi într-o anumită cantitate, sursele DF în care destinaţiile sunt Cantitate, PreţUnit
(plus DenPr, UM şi ProcTVA) pot fi atât (NrFact, Linie), cât şi (NrFact, CodPr). Procedând
analog situaţiei de mai sus (dar cu mai multă strângere de inimă), decidem ca fiecare linie/produs
dintr-o factură să fie identificat de sursa (NrFact, Linie). Aşa că eliminăm dependenţele în care
sursă este "concurentul", adică (NrFact, CodPr) şi destinaţiile sunt identice: (47), (48) şi (49).

3.3.4 Graful dependenţelor funcţionale


Într-un graf al dependenţelor funcţionale, acestea sunt reprezentate prin săgeţi ce leagă sursa şi
destinaţia. La dependenţele funcţionale simple săgeata uneşte cele două atribute. Când sursa este
compusă, se foloseşte un conector care leagă, în prima instanţă, atributele-determinant, iar săgeata
uneşte acest conector cu atributul destinaţie.
Dependenţele discutate în paragraful anterior pentru relaţia BIBLIOTECA_TU-
PLURI_NOI_SOLUŢIA_3 (figura 3.5) pot fi reprezentate sub formă de graf ca în figura 3.7.

Figura 3.7. Reprezentarea sub formă de graf a DF din relaţia


BIBLIOTECA_TUPLURI_NOI_SOLUŢIA_3
42 Baze de date I

Din baza de date VÎNZĂRI, pentru simplificare, în figura 3.8 sunt reprezentate numai DF (1),
(3), (5)-(7), (9)-(15), (33)-(46), (50)-(54).

Figura 3.8. Graful simplificat al DF din BD VÎNZĂRI

3.4 A DOUA FORMĂ NORMALIZATĂ


Cea de-a doua formă normală (2NF) se bazează pe dependenţele funcţionale totale sau
elementare. Drept care acest paragraf începe cu prezentarea acestui tip de dependenţe funcţionale eşi
se continuă cu discutarea modalităţile de “rupere” a bazei de date în mai multe relaţii aflate în 2NF.

3.4.1 Dependenţe funcţionale totale


Noţiunea de dependenţă funcţională totală a fost introdusă de Melkanoff. O dependenţă
funcţională Data1 Data2 este totală dacă nu există nici un atribut/combinaţie Dată3 ca
subansamblu al Data1, care să verifice dependenţa funcţională Dată3 Dată2. Formal, dacă X
= { Ai, Aj,...., Ak } reprezintă un grup de atribute ale relaţiei R, iar Am un atribut al aceleiaşi relaţii,
unde Am X. Se spune că există o dependenţă funcţională totală între X şi Am dacă:

1. X Am şi 2. nu există nici un subansamblu S de atribute, S X, pentru care S Am .


total
Se notează: X Am
Dependenţa funcţională totală mai este denumită şi elementară, plină, deplină sau completă.
Implicit, toate dependenţele în care sursa este simplă (alcătuită dintr-un singur atribut) sunt dependenţe
funcţionale complete. Problema prezintă importanţă în cazul dependenţei funcţionale cu sursa
compusă. Astfel, în BD VÎNZĂRI, dependenţele : (NrFact, Linie) CodPr; (NrFact, Linie)
Cantitate şi (NrFact, Linie) PretUnit sunt totale, deoarece: NrFact / CodPr;
NrFact / Cantitate; NrFact / PretUnit; Linie / CodPr; Linie / Cantitate;
Linie / PretUnit.
Dacă se utilizează cel de-al doilea sistem de contare, caz în care sunt valabile DF (2)-(4),
atunci pentru LINII_ARTICOLE_CONTABILE din figura 3.6 dependenţa (4) este una parţială
datorită (2).
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 43

În relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 din figura 3.5 primele cinci


dependenţe funcţionale: (1) (Cota, Autor, CuvântCheie) ISBN; (2) (Cota, Autor,
CuvântCheie) Titlu; (3) (Cota, Autor, CuvântCheie) Editura; (4) (Cota,
Autor, CuvântCheie) LocSediuEd; (5) (Cota, Autor, CuvântCheie) AnApariţie
sunt parţiale, deoarece: (6) Cota ISBN; (7) Cota Titlu; (8) Cota
Editura; (9) Cota LocSediuEd; (10) Cota AnApariţie
Cu alte cuvinte, un atribut component al determinatului dependenţelor (1)-(5) este, el singur,
sursă într- serie de DF în care destinaţiile sunt identice.

3.4.2 Trecerea relaţiilor în a doua formă normală (2NF)


Începând cu a doua formă normală, relaţiile pot fi decupate (decompuse, sparte) în sub-relaţii,
în scopul diminuării problemelor legate de stocare şi actualizare. În general, atunci când într-o relaţie
R există o DF de tip X Y şi X nu este cheie candidată, atunci R conţine o anumită doză de
redundanţă .
23

Jan Heath a demonstrat24 că orice relaţie alcătuită din trei atribute, notată R(X, Y, Z), în care
există dependenţa funcţională X Y poate fi descompusă în două relaţii R1(X, Y) şi R2(X, Z);
R1 şi R2 reprezentă proiecţiile relaţiile R pe atributele X şi Y, respectiv X şi Z. Esenţial este faptul că
descompunerea să se facă fără pierdere de informaţii, adică R să poată fi "recompusă" prin joncţiunea
tabelelor R1 şi R2. În alţi termeni, la spargerea oricărei relaţii trebuie ca toate dependenţele din
acoperirea minimală să se regăsească în structura noilor relaţii25. Ca o consecinţă indirectă, se poate
astfel rezolva şi problema cheii primare a relaţiei universale, în cazul încălcării restricţiei de entitate
(un atribut din cheia primară prezintă valori nule).
O relaţie se află în 2NF dacă: 1. Se află în 1NF; 2. Toate DF ce leagă cheia primară la celelalte
atribute sunt DF elementare (totale).
O bază de date este în 2NF când toate relaţiile care o alcătuiesc sunt în 2NF. Problema trecerii
unei relaţii din prima în a doua formă normală se pune numai când cheia primară a relaţiei este
compusă din mai multe atribute. Într-o formulare mai lejeră, se poate spune că o relaţie în 2NF nu
conţine DF parţiale între atributele-cheie şi celelalte atribute.
În general, trecerea de la 1NF la 2NF se poate realiza după următoarea succesiune de paşi:
a) Se identifică posibila cheie primară a relaţiei universale, care conferă unicitate oricărui tuplu din
relaţie, chiar în condiţiile violării restricţiei de entitate;
b) Se inventariază toate dependenţele dintre atributele relaţiei (închiderea DF), cu excepţia celor în
care destinaţia este un atribut component al cheii primare.
c) Se trec în revistă toate dependenţele care au ca sursă (determinant) un atribut sau sub-ansamblu de
atribute din cheia primară.
d) Pentru fiecare atribut (sau sub-ansamblu) al cheii de la pasul c), se creează o relaţie care va avea
drept identificator primar atributul (subansamblul) respectiv, iar celelalte atribute vor fi cele care apar
ca destinaţii în dependenţele de la pasul c).

23
[Date00], p.333
24
Heath, I.J. - Unacceptable File Operations in a Relational Database, Proc.1971 ACM SIGFIDET Workshop on
Data Description, Access and Control, nov. 1971
25
Vezi şi [O'Neil & O'Neil 01], p.387
44 Baze de date I

e) Din relaţia iniţială sunt eliminate toate atributele destinaţie (noncheie) din relaţiile "proaspăt"
obţinute, păstrându-se numai atributele cheie ale noilor relaţii (se asigură, astfel, cheile străine care vor
face legăturile către noile relaţii).
f) În „rămăşiţele” relaţiei universale (iniţiale) se verifică dacă cheia primară iniţială respectă restricţia
de entitate, iar dacă nu, se mai elimină dintre atribute, iar, la limită, chiar toată relaţia-„rămăşiţe”.

Important !
Etapele sugerate sunt aplicabile mai ales în situaţiile în care cheia primară a relaţiei universale
încalcă restricţia de entitate, adică atunci când combinaţia ar conferi unicitate, însă unul sau mai multe
dintre atributele cheie ar risca valori nule (vezi discuţia din paragraful 3.4). Este vorba de violare
ruşinoasă a "poruncilor" relaţionale, deoarece, după cum am promis, începând cu 2NF orice relaţie
obţinută va respecta scupulos (şi) restricţia de entitate.
La limită, prin descompuneri, din relaţia universală (iniţială) este posibil să nu rămână decât
atributele cheie. Acum, însă, orice pericol privind nulitatea vreunui atribut cheie va atrage după sine
eliminarea atributului respectiv, sau, la limită a relaţiei respective, după cum vom vedea chiar în acest
paragraf, pentru relaţia VÎNZĂRI.

Relaţia LINII_ARTICOLE_CONTABILE
Pentru relaţia din figura 3.6, dacă se utilizează primul sistem de contare, în care singura
dependenţă dintre cele discutate în paragraful 3.3 este (1), atunci nu se mai pune problema trecerii în
2NF. În al doilea caz, în care DF sunt valabile (2), (3) şi (4), ultima este parţială. Urmăm cei şase paşi:
a) Cheia primară este combinaţia: (NotaContabilă, Operaţiune, ContDebitor, ContCreditor)
b) Inventarierea tuturor DF: (2), (3), (4)
c) Singura DF în care determinantul e parte din cheia primară este (2): NotaContabilă Data
d) Pentru atributul NotaContabilă se constituie relaţie care îl va avea identificator primar, iar cel
de-al doilea atribut Data. Din tabela iniţială se elimină atributul necheie preluat în noua relaţie.
e) Din relaţia iniţială rămâne: R‟ {NotaContabilă, Operaţiune, ContDebitor, ContCreditor,
Suma}
f) În R‟ cheia primară nu periclitează restricţia entităţii, deci R‟ îşi poate păstra structura.

În consecinţă, în 2NF relaţia LINII_ARTICOLE_CONTABILE se descompune în două relaţii,


NOTE_CTB {NotaContabilă, Data} şi ARTICOLE_CTB {NotaContabilă, Operaţiune,
ContDebitor, ContCreditor, Suma} prezentate în figura 3.9.

NOTE_CTB ARTICOLE_CTB
NotaContabilă Data NotaContabilă Operaţiune ContDebitor ContCreditor Suma
150 30.11.2006 150 1 300 401 10000000
151 01.12.2006 150 1 4426 401 1800000
150 2 401 5311 7000000
150 2 401 5121 4800000
151 1 5121 700 5250000
Figura 3.9. Tabela LINII_ARTICOLE_CONTABILE adusă în 2NF
La drept vorbind, nu putem spune că economia de spaţiu şi diminuarea anomaliilor la actualizări sunt
spectaculoase din cale-afară prin aducerea relaţiei în 2NF. Datorită simplităţii, exemplul este însă util.
Relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3
Relaţia ilustrată în figura 3.5 prezintă, dintre cele discutate în paragraful 3.3, dependenţe
parţiale şi, astfel, constituie un bun "obiect" de normalizare în 2NF. Dealtminteri, relaţia abundă în
redundanţe şi anomalii de inserare şi modificare:
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 45

înregistrarea recepţiei unui titlu, introducerea datelor despre o carte (titlu) sunt imposibile până
în momentul în care măcar o carte primeşte cotă;
dacă un titlu a fost introdus iniţial eronat, corecţia trebuie operată pe destule linii ale tabelei;
la fiecare achiziţie ulterioară a unei aceleaşi cărţi (ISBN) trebuie introduse din nou ISBN-ul,
titlul, editura etc.
Parcurgem cei şase paşi, după cum urmează:
a) Cheia primară a relaţie iniţiale este: (Cota, Autor, CuvântCheie)
b) Ansamblul DF: (1)-(15)
c) În dependenţele (6)-(10), atributul Cota, component al cheii relaţiei, este sursă simplă
d) Se constituie o relaţie în care Cota este cheie primară, iar celelalte atribute sunt destinaţiile DF (6),
(7), (8), (9) şi (10).
e) Din relaţia iniţială se elimină toate atributele-destinaţie preluate în proaspătă tabelă, rămânând doar
atributele cheie: {Cota, Autor, CuvântCheie}.
f) Nici unul dintre cele trei atribute de la punctul e) nu periclitează restricţia de entitate, aşa încât
relaţia-„rămăşiţă” se păstrează în această formă.
Prin urmare, în a doua formă normalizată, relaţia iniţială (universala) s-ar putea descompune
în: CĂRŢI {Cota, ISBN, Titlu, Editura, LocSediuEd, AnApariţie} şi TITLURI_AU-
TORI_CUVINTECHEIE {Cota, Autor, CuvântCheie} - vezi figura 3.10.
CĂRŢI
Cotă ISBN Titlu Editura LocSediuEd AnApariţie
III-13421 973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale Polirom Iaşi 2002
III-13422 973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale Polirom Iaşi 2002
III-13423 973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor profesionale Polirom Iaşi 2002
III-10678 973-683-709-2 SQL. Dialecte DB2, Oracle şi Visual FoxPro Polirom Iaşi 2001
III-10679 973-683-709-2 SQL. Dialecte DB2, Oracle şi Visual FoxPro Polirom Iaşi 2001

TITLURI_AUTORI_CUVINTECHEIE (fragment)
Cota Autor CuvântCheie
III-13421 Marin Fotache baze de date
III-13421 Marin Fotache SQL
III-13421 Marin Fotache proceduri stocate
III-13421 Marin Fotache FoxPro
III-13421 Marin Fotache formulare
III-13421 Marin Fotache orientare pe obiecte
III-13421 Marin Fotache client-server
III-13421 Marin Fotache web
...
Figura 3.10. Relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 în 2NF
Chiar dacă o parte din redundanţe au fost eliminate, mai rămân destule, astfel încât structura de mai
sus va constitui subiect atât pentru a trei formă normalizată (3NF), cât şi pentru cea de a patra (4NF).
Relaţia STUDENŢI_EXAMENE
Senzaţia de deja-vu care vă încearcă este îndreptăţită. Acesta este relaţia din paragraful 3.1.1
(figura 3.1) cu care am început să închinăm osanale normalizării. Este o relaţie simplă care se pretează
la normalizare, manifestând redundanţe şi anomalii la actualizare. Cheia primară fiind combinaţia
(Matricol, CodDisc, DataExamen), automat sunt valabile următoarele DF:
(1) (Matricol, CodDisc, DataExamen) NumePrenume
(2) (Matricol, CodDisc, DataExamen) An
(3) (Matricol, CodDisc, DataExamen) Specializare
(4) (Matricol, CodDisc, DataExamen) DenumireDisc
(5) (Matricol, CodDisc, DataExamen) NrCredite
46 Baze de date I

(6) (Matricol, CodDisc, DataExamen) Nota


STUDENŢI_EXAMENE ar fi în 2NF dacă nici una din aceste şase dependenţe nu ar fi parţială. Fără a
ne întrebuinţa prea serios, e suficient să ne reamintim că matricolul este desemnat să identifice în mod
unic fiecare student. Dintr-un foc avem trei dependenţe: (7) Matricol NumePrenume;
(8)Matricol An; (9)Matricol Specializare.
Şi codul disciplinei are pretenţii de identificare fără ambiguitare a fiecărei "materii" parcurse
de studenţi: (10) CodDisc DenumireDisc; (11) CodDisc NrCredite.
Din dependenţele (7)-(9) construim relaţia STUDENŢI { Matricol, NumePrenume, An,
Specializare}, din (10) şi (11) DISCIPLINE { CodDisc, DenumireDisc, NrCredite} iar din
relaţia iniţială rămâne: EXAMENE { Matricol, CodDisc, DataExamen, Nota} care nu are
probleme cu valorile nule ale vreunui atribut-cheie.
Avem toate motivele să fim mulţumiţi. Redundanţele au fost eliminate. Numele, specializarea
şi anul de studiu al fiecărui student apar acum o singură dată (în tuplul corespunzător studentului din
relaţia STUDENŢI). Tot o singură dată apar şi denumirea unei discipline şi numărul de credite alocat
acesteia (relaţia DISCIPLINE). Lucrurile stau la fel de bine şi în ceea ce priveşte diminuarea
anomaliilor manifestate la editarea datelor. Astfel, privitor la anomaliile de inserare, din momentul
înscrierii în anul I, până la cel al primului examen, studentul poate fi inserat în tabela STUDENŢI,
urmând ca, odată cu prima sa sesiune de examene, să-i fie introduse înregistrări în EXAMENE.
Modificarea denumirii unei discipline sau numărul de credite, sau corectarea unei eventuale erori
strecurate în numele studentului sau al specializării/anului, toate se fac într-un singur loc, pe tuplul
corespunzător din STUDENŢI sau DISCIPLINE, ceea ce înseamnă că am scăpat şi de anomaliile de
modificare. În fine, tot în paragraful 3.1.1 se atrăgea atenţia asupra riscului pierderii unor informaţii
valoroase la ştergerea unei linii din STUDENŢI_EXAMENE (un soi de aruncatul copilului odată cu
albia). Nici acest pericol nu mai este de actualitate în noua structură.
Relaţia VÎNZĂRI
Şi relaţia universală VÎNZĂRI încalcă preceptele relaţionalului (în fapt, am putea să-i spunem
tabelă, pentru a respecta adevărul, tabela fiind asociată cu SQL-ul care este mult prea tolerant cu
elemente ce fac dintr-o tabelă să nu fie o relaţie - unicitate, valori nule, lipsa cheii primare etc.),
întrucât unul din atributele care asigură unicitate fiecărui tuplu din relaţia iniţială, CodÎnc (codul
încasării) are valori nule pentru facturile “proaspăt” emise, de fapt, pentru toate facturile din care nu s-
a încasat nici o leţcaie. Dacă nu s-ar fi pus problema restricţiei de entitate, combinaţia de atribute care
diferenţiază cu siguranţă orice tuplu de celelalte este (NrFact, Linie, CodÎnc). Lucrurile nu sunt
atât de grave, deoarece în setul de DF apar destule surse care sunt subansambluri ale pseudo-cheii
primare. Aşadar, să parcurgem cele şase etape ale deja celebrului „îndrumar” de aducere a unei baze
de date în 2FN.
a) Cheia primară a relaţiei este: (NrFact, Linie, CodÎnc).
b) Dependenţele manifestate în relaţie sunt cele 54 din paragraful 3.3.
c) Dependenţele (50), (51), (52) şi (53) au drept sursă CodÎnc. Cu totul altfel stau lucrurile pentru
NrFact. Dacă iniţial identificasem dependenţele (37), (38), (39) şi (40), datorită tranzitivităţii acestora
li se mai pot adăuga încă 12, adică: (55) NrFact DenCl; (56) NrFact CodFiscal; (57)
NrFact StradaCl; (58) NrFact NrStradaCl; (59) NrFact BlocScApCl; (60)
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 47

NrFact TelefonCl; (61)NrFact CodPost; (62)NrFact Localitate; (63) Nr-


Fact Comuna; (64) NrFact Jud; (65) NrFact Judet; (66)NrFact Regiune.
Am încheiat inventarierea DF în care sursele sunt simple şi atribute-cheie şi continuăm cu surse
compuse, alcătuite din două dintre cele trei atribute, la acest „capitol” având dependenţele (54) şi cele
de la (41) la (46).
d) Creăm noile relaţii. Din (50), (51), (52) şi (53) rezultă: ÎNCASĂRI { CodÎnc, DataÎnc, CodDoc,
NrDoc, DataDoc}. Din (37), (38), (39) şi (40), plus (55)-(66) se obţine: FACTURI {NrFact, CodCl,
DataFact, Obs, DenCl, CodFiscal, StradaCl, NrStradaCl, BlocScApCl, TelefonCl,
CodPost, Localitate, Comuna, Jud, Judeţ, Regiune}. Din dependenţa (54) se poate “izola”
relaţia: ÎNCAS_FACT {CodÎnc, NrFact, Tranşa} Pe baza DF (41)-(46) se construieşte: LI-
NII_FACTURI {NrFact, Linie, Cantitate, PreţUnit, CodPr, DenPr, UM, ProcTVA, Grupa}.
e) Din relaţia iniţială rămâne: FACTURI_INCASĂRI {NrFact, Linie, CodÎnc}.
f) În afara celor trei atribute cheie din relaţia iniţială - nimic. Dacă în 1NF eludam problema restricţiei
de entitate, pe motiv că în 2NF lucrurile se rezolvă, acum ne propunem să tranşăm discuţia. Pentru ca
să respecte restricţia de entitate, relaţia FACTURI_ÎNCASĂRI nu poate "primi" tupluri, decât în
momentul unei prime încasări a unei facturi. Dar, de fapt, ceea ce se încasează într-o tranşă nu este o
linie (un produs) dintr-o factură, ci o parte sau întreaga factură, lucrul pe care-l reflectă pe deplin
dependenţa (54) şi relaţia ÎNCAS_FACT. Prin urmare, această ultimă relaţie nu ne este de nici un
folos, şi, pur şi simplu, renunţăm la ea. Senzaţia este oarecum ciudată, mai ales că un asemenea
demers nu este fundamentat prea riguros în teoria atât de matematizată a normalizării.
În concluzie, în 2NF, relaţia universală VÎNZĂRI va avea structura relaţiilor: ÎNCASĂRI,
FACTURI, ÎNCAS_FACT şi LINII_FACTURI.

3.5. A TREIA FORMĂ NORMALIZATĂ ŞI FORMA NORMALĂ


BOYCE-CODD
A treia formă normalizată (3NF) prezintă un interes aparte, deoarece este considerată de mulţi
specialişti drept un minimum acceptabil pentru structura unei baze de date relaţionale. Bazată pe a
doua formă normală şi dependenţele funcţionale tranzitive (de fapt, pe eliminarea lor), 3NF a cunoscut
două definiţii majore, una “originală”, formulată de Codd însuşi (cu variantele sale “folclorice”) şi a
doua, ce acoperă câteva lipsuri ale primeia, numită, după autorii săi, Boyce-Codd Normal Form, în
traducere oarecum liberă forma normală Boyce-Codd, mai pe româneşte, BCNF.

3.5.1 Dependenţe funcţionale directe/tranzitive


O dependenţă funcţională Dată1 Dată2 este directă atunci când nu există o Dată3
care angajează o dependenţă funcţională tranzitivă de genul: Dată1 Dată3 Dată2.
Pentru relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3, DF (7), (8), (9) şi (10) sunt
tranzitive deoarece: Cota ISBN Titlu; Cota ISBN Editura; Cota ISBN
LocSediuEd; Cota ISBN AnApariţie. În plus şi DF (13) este tranzitivă, întrucât
ISBN Editura LocSediuEd.
48 Baze de date I

Pentru relaţia VÎNZĂRI dependenţele tranzitive simple sunt mai numeroase: CodPost
Jud Judeţ; CodPost Jud Regiune; CodCl CodPost Localitate;
CodCl CodPost Comuna; CodCl CodPost Jud Judeţ; CodCl Cod-
Post Jud Regiune; NrFact CodCl DenCl; NrFact CodCl Cod-
Fiscal; NrFact CodCl StradaCl; NrFact CodCl NrStradaCl; NrFact
CodCl BlocScApCl; NrFact CodCl TelefonCl; NrFact CodCl
CodPost, NrFact CodCl CodPost Localitate, …, dar şi (NrFact,Linie)
CodPr DenPr; (NrFact,Linie) CodPr UM; (NrFact,Linie) CodPr
ProcTVA; (NrFact, Linie) CodPr Grupa.
Pe baza noţiunilor prezentate se poate defini închiderea tranzitivă a unui ansamblu de
dependenţe funcţionale. Închidere tranzitivă reprezintă ansamblul dependenţelor totale directe +
dependenţele totale tranzitive. Acoperirea minimală, definită în capitolul anterior, este subansamblul
minimal de dependenţe funcţionale elementare care permit determinarea tuturor celorlalte dependenţe
funcţionale. Cu alte cuvinte: (1) nici o dependenţă funcţională nu este redundantă şi (2) orice
dependenţă funcţională totală face parte din închiderea tranzitivă. Spre deosebire de ceea ce am
discutat în capitolul anterior, acum suntem în măsură să eliminăm din ansamblul DF şi pe cele
tranzitive.
Astfel, pentru relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3, acoperirea minimală este
alcătuită din patru DF: Cota ISBN; ISBN Titlu; ISBN Editura; ISBN An-
Apariţie; Editura LocSediuEd, iar închiderea tranzitivă este următorul ansamblu de
dependenţe: Cota ISBN; Cota Titlu; Cota Editura; Cota LocSediuEd;
Cota AnApariţie; ISBN Titlu; ISBN Editura; ISBN LocSediuEd;
ISBN AnApariţie; Editura LocSediuEd.
Acoperirea minimală poate fi uşor reprezentată prin graful DF. Dependenţele tranzitive sunt
foarte uşor de identificat şi eliminat. Spre exemplu, dacă din figura 3.7 eliminăm săgeţile ce reprezintă
drumul direct dintre două atribute/grupe şi un atribut destinaţie, atâta timp cât între respectivele
atribute/conectori există un “traseu ocolitor”, se obţine graful acoperirii minimale – vezi figura 3.11.

Figura 3.11. Graful DF din acoperirea minimală pentru relaţia


BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3
Este drept, atributele Autor şi CuvîntCheie atârnă inutil în figură, stricând imaginea
grafului. Le-am păstrat însă pentru a nu omite nici un atribut din relaţia universală şi de a indica cheia
primară a relaţiei.

Pentru baza de date VÎNZĂRI acoperirea minimală, adică setul iniţial de DF din care au fost
eliminate dependenţele simetrice (redundante), parţiale şi tranzitive este următorul set de dependenţe
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 49

funcţionale reprezentat în figura 3.8: Jud Judeţ; Jud Regiune; CodPost Loca-
litate; CodPost Comună; CodPost Jud; CodCl DenCl; CodCl CodFis-
cal; CodCl StradaCl; CodCl NrStradaCl; CodCl BlocScApCl; CodCl
TelefonCl; CodCl CodPost; CodPr DenPr; CodPr UM; CodPr Grupa;
CodPr ProcTVA; NrFact CodCl ; NrFact DataFact; NrFact Obs; (Nr-
Fact,Linie) Cantitate; (NrFact,Linie) PreţUnit; CodÎnc DataÎnc;
CodÎnc CodDoc; CodÎnc NrDoc; CodÎnc DataDoc; (CodÎnc,NrFact)
Tranşă.

3.5.2 Aducerea relaţiilor în a treia formă normală


Prima definiţie a celei de-a treia forme normale a unei relaţii poate fi formulată relativ simplu
şi incremental, prin raportare la 2NF. O relaţie se află în 3NF dacă: 1. Se găseşte în 2NF; 2. Toate
atributele care nu aparţin cheii primare nu depind funcţional de un alt atribut (ansamblu de atribute)
care nu face parte din cheie. A doua condiţie poate fi exprimată şi în maniera: toate dependenţele
funcţionale care leagă cheia primară de celelalte atribute sunt directe (netranzitive).
O altă formulare a 3NF utilizează tot două condiţii: a) toate atributele ne-cheie din R sunt
independente unele de altele, în sensul că nici unul dintre atributele neparticipante în cheia primară nu
apare în vreo DF în care sursa este constituită dintr-o combinaţie de celelalte atribute ne-cheie; b) toate
atributele ne-cheie din R sunt dependente ireductibil de cheia primară.

Important !
Chris Date este poate singurul care, precaut, la începutul discuţiilor legate de primele trei
forme normale, afirmă că situaţiile luate în considerare pornesc de la premisa că nu există chei
candidat. Premisa este foarte importantă, întrucât atunci când nu se elimină dependenţele simetrice şi
redundante pomenite în capitolul anterior, iar o relaţie are două mai multe chei candidat, sunt şanse
mari să apară complicaţii. Vom reveni la sfârşitul acestui paragraf cu analiza bazei de date VÎNZĂRI
şi vom vedea că şi precauţia lui Date este insuficientă, deoarece, în fapt, problema nu este numai a
cheilor candadidat ale unei relaţii, ci a dependenţelor simetrice şi redundante.

Trecerea din 2NF în 3NF presupune eliminarea DF tranzitive şi se poate face, pentru o relaţie,
în următoarea manieră:
a) Se identifică toate atributele ce nu fac parte din cheie şi sunt surse ale unor dependenţe funcţionale.
b) Pentru toate atributele identificate la punctul a), se constituie câte o relaţie în care cheie primară va
fi atributul respectiv, iar celelate atribute destinaţiile din dependenţele considerate.
Operaţiile a) şi b) se repetă şi pentru relaţiile "proaspăt" obţinute prin decompoziţie. De fiecare dată,
din relaţiile supuse descompunerii se elimină atributele destinaţie ale surselor non-cheie (sună bine,
nu-i aşa ?), păstrându-se atributele sursă, în vederea stabilirii legăturilor între tabele (cheile străine) şi,
implicit, declarării restricţiilor referenţiale.

Relaţia LINII_ARTICOLE_CONTABILE a fost adusă în paragraful 3.4.2 în 2NF prin


descompunerea în două relaţii, NOTE_CTB {NotaContabilă, Data} şi ARTICOLE_CTB
{NotaContabilă, Operaţiune, ContDebitor, ContCreditor, Suma} prezentate în figura 3.9.
Întrucât nici una din cele două relaţii nu conţine dependenţe funcţionale tranzitive, în 3NF baza de
date are aceaşi structură ca şi în 2NF.
50 Baze de date I

Pentru a îndeplini cerinţele 2FN, relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 a fost


ruptă în tabelele CĂRŢI {Cota, ISBN, Titlu, Editura, LocSediuEd, AnApariţie} şi
TITLURI_AUTORI_CUVINTECHEIE {Cota, Autor, CuvântCheie} - vezi figura 3.10. Cea de-a
doua este alcătuită exclusiv din atribute-cheie, deci nu se pune în discuţie 3NF (adică relaţia este deja
în 3NF). Altfel stau lucrurile în CĂRŢI. Relaţia este în 2NF şi conţine dependenţe tranzitive. Există
două atribute din afara cheii primare, ISBN şi Editura, care sunt surse de DF, astfel încât
dependenţele (7), (8), (9), (10) şi (13) sunt tranzitive. Trecerea în 3NF se realizează prin constituirea a
două relaţii pe care o să le numim EDITURI {Editura, LocSediuEd} şi TITLURI {ISBN, Titlu,
Editura, AnApariţie}, iar din CĂRŢI rămâne EXEMPLARE { Cota, ISBN}. În concluzie, în
3NF baza de date BIBLIOTECĂ este alcătuită din patru tabele: EDITURI { Editura, LocSediuEd},
TITLURI {ISBN, Titlu, Editura, AnApariţie }, EXEMPLARE { Cota, ISBN} şi TITLURI_A-
UTORI_CUVINTECHEIE {Cota, Autor, CuvântCheie} – vezi fig. 3.12.
EDITURI TITLURI
Editura LocSediuEd ISBN Titlu Editura AnApariţie
Polirom Iaşi 973-683-889-7 Visual FoxPro.Ghidul dezvoltării aplicaţiilor profesionale Polirom 2002
973-683-709-2 SQL. Dialecte DB2, Oracle şi Visual FoxPro Polirom 2001

EXEMPLARE TITLURI_AUTORI_CUVINTECHEIE (fragment)


Cotă ISBN Cota Autor CuvântCheie
III-13421 973-683-889-7 III-13421 Marin Fotache baze de date
III-13422 973-683-889-7 III-13421 Marin Fotache SQL
III-13423 973-683-889-7 III-13421 Marin Fotache proceduri stocate
III-10678 973-683-709-2 III-13421 Marin Fotache FoxPro
III-10679 973-683-709-2 III-13421 Marin Fotache formulare
III-13421 Marin Fotache orientare pe obiecte
III-13421 Marin Fotache client-server
III-13421 Marin Fotache web
...
Figura 3.12. Relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 în 3NF

Din păcate, ne rămâne pe conştiinţă ultima tabelă, TITLURI_AUTORI_CUVINTECHEIE


care conţine un grad de ridicat de redundanţă a datelor, problemă rezolvată pe baza unui alt tip de
dependenţe - multivaloare (vezi capitolul viitor).

Ultimul exemplu din pagraful 4.4 a adus relaţia universală VÎNZĂRI în a doua formă
normalizată, prin ruperea acesteia în patru tabele: ÎNCAS_FACT, ÎNCASĂRI, LINII_FACTURI şi
FACTURI. Dar, după cum am văzut în paragraful precedent, există o serie de dependenţe tranzitive.
Să examinăm pe rând cele cinci relaţii.
* ÎNCAS_FACT {CodÎnc, NrFact, Tranşa} are un singur atribut ne-cheie, deci nici vorbă de DF
tranzitive.
* ÎNCASĂRI {CodÎnc, DataÎnc, CodDoc, NrDoc, DataDoc} nu prezintă nici un atribut în afara
CodÎnc care să fie sursă de DF.
* Cu LINII_FACTURI {NrFact, Linie, Cantitate, PreţUnit, CodPr, DenPr, UM, ProcTVA,
Grupa} situaţia este diferită. Atributul ne-cheie CodPr este determinant în o serie de dependenţe,
(33)-(36), pe baza acestora construindu-se relaţia PRODUSE {CodPr, DenPr, UM, ProcTVA, Grupa};
din LINII_FACTURI se elimină toate destinaţiile celor patru DF, obţinându-se LINII_FACT
{NrFact, Linie, CodPr, Cantitate, PreţUnit}.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 51

* În tabela FACTURI {NrFact, CodCl, DataFact, Obs, DenCl, CodFiscal, StradaCl,


NrStradaCl, BlocScApCl , TelefonCl, CodPost, Localitate, Comuna, Jud, Judeţ,
Regiune} lucrurile sunt şi mai interesante:
- mai întâi, se cuvine de observat că Jud este sursă a DF (1) şi (3), aşa încât FACTURI se
descompune în JUDEŢE{ Jud, Judeţ, Regiune} şi FACTURI_1 {NrFact, CodCl, DataFact,
Obs, DenCl, CodFiscal, StradaCl, NrStradaCl, BlocScApCl, TelefonCl, CodPost,
Localitate, Comună, Jud};
- dar şi FACTURI_1 prezintă DF; în dependenţele (5), (6) şi (7) CodPost este sursă, aşa încât
FACTURI_1 se descompune în CODURI_POŞTALE {CodPost, Localitate, Comună, Jud} şi
FACTURI_2 {NrFact, CodCl, DataFact, Obs, DenCl, CodFiscal, StradaCl, NrStradaCl,
BlocScApCl, TelefonCl, CodPost};
- în FACTURI_2 a mai rămas un singur atribut ne-cheie ce este sursă în DF (8)-(16), CodCl, astfel
încât şi aceasta se descmpune în CLIENŢI {CodCl, DenCl, CodFiscal, StradaCl, NrStradaCl,
BlocScApCl, TelefonCl, CodPost} şi FACT {NrFact, CodCl, DataFact, Obs}.
În concluzie, în 3NF relaţia iniţială VÎNZĂRI este “spartă” în următoarele opt relaţii:
ÎNCAS_FACT {CodÎnc, NrFact, Tranşa}, ÎNCASĂRI {CodÎnc, DataÎnc, CodDoc, NrDoc,
DataDoc}, PRODUSE {CodPr, DenPr, UM, ProcTVA, Grupa}, LINII_FACT {NrFact, Linie,
CodPr, Cantitate, PreţUnit}, JUDEŢE{ Jud, Judeţ, Regiune}, CODURI_POŞTALE
{CodPost, Localitate, Comună, Jud}, CLIENŢI {CodCl, DenCl, CodFiscal, StradaCl,
NrStradaCl, BlocScApCl, TelefonCl, CodPost} şi FACT {NrFact, CodCl, DataFact, Obs}.

3.5.3 Cheile candidat şi influenţa lor asupra 3NF


Promiteam în deschiderea paragrafului câteva detalii despre implicaţiile cheilor alternative
asupra aducerii unei relaţii în 3NF, implicaţii prea puţin evocate în literatura de profil. Astfel, revenim
la relaţia în 2NF LINII_FACTURI {NrFact, Linie, Cantitate, PreţUnit, CodPr, DenPr, UM,
ProcTVA, Grupa} din baza de date VÎNZĂRI. Dacă luăm în consideraţie că şi denumirea produsului
este unică, altfel spus, nu există două sortimente cu nume absolut identic, rezultă o serie de dependenţe
paralele:
CodPr DenPr DenPr CodPr
CodPr UM DenPr UM
CodPr Grupa DenPr Grupa
CodPr ProcTVA DenPr ProcTVA

Dacă urmăm algoritmul prescris de trecere a relaţiei LINII_FACTURI din 2NF în 3NF, ar
trebui să consituim câte o relaţie pentru fiecare sursă de DF care nu face parte din cheia primară a
relaţiei, iar în relaţia ce rămâne din LINII_FACTURI (LINII_FACT_2) să păstrăm pe post de chei
străine atât CodPr, cât şi DenPr: PRODUSE_1 {CodPr, DenPr, UM, ProcTVA, Grupa},
PRODUSE_2 { DenPr, CodPr, UM, ProcTVA, Grupa} şi LINII_FACT_2 {NrFact, Linie, CodPr,
DenPr, Cantitate, PreţUnit}.
Gogomănia este evidentă, însă, ca în atâtea alte cazuri, fundamentată ştiinţific ! Iar dacă
demostraţia nu a fost suficient de impresionantă, ce ziceţi de relaţia FACTURI în 2FN, în care orice
client poate fi identificat fără ambiguitate atât de CodCl, cât şi de DenCl şi de CodFiscal.
52 Baze de date I

CodCl DenCl DenCl CodCl CodFiscal CodCl


CodCl CodFiscal DenCl CodFiscal CodFiscal DenCl
CodCl StradaCl DenCl StradaCl CodFiscal StradaCl
CodCl NrStradaCl DenCl NrStradaCl CodFiscal NrStradaCl
CodCl BlocScApCl DenCl BlocScApCl CodFiscal BlocScApCl
CodCl TelefonCl DenCl TelefonCl CodFiscal TelefonCl
CodCl CodPost DenCl CodPost CodFiscal CodPost
CodCl Localitate DenCl Localitate CodFiscal Localitate
CodCl Comuna DenCl Comuna CodFiscal Comuna
CodCl Jud DenCl Jud CodFiscal Jud
CodCl Judet DenCl Judet CodFiscal Judet
CodCl Regiune DenCl Regiune CodFiscal Regiune

Aşa că din LINIIFACTURI obţinem în prima fază a trecerii la 3NF nu mai puţin de patru
relaţii, apoi să continuăm cu descompunem celor trei constituite pe baza seriilor de dependenţe de mai
sus s.a.m.d.
Soluţia este cea pe care am sugerat-o în capitolul 4. Pentru identificarea oricărei entităţi
(produs, client, factură) păstrăm doar cheia primară (practic, renunţăm la celelate chei candidat), iar
toate dependenţele ce decurg din calitatea de cheie alternativă nu sunt luate în considerare.

3.5.4 Aducerea relaţiilor în 3NF prin grafuri ale DF


Modul în care a fost prezentată transformarea sucesivă a relaţiei universale iniţiale în 1NF,
2NF şi 3NF prin descompuneri succesive are certe valenţe pedagogice şi aplicabilitate practică.
Descompunerea nu este însă singura manieră de normalizare a unei baze de date relaţionale.
Philip A. Berstein este autorul unui model de normalizare prin sinteza unor relaţii binare
(construite pe baza DF) în relaţii mai mari şi, astfel, aducerea bazei în 3NF 26. În plus, autorul a
demonstrat că schema obţinută conţine un număr minim de relaţii. Deşi s-a demonstrat că algoritmul
este corect, aplicarea sa efectivă ridică destule dificultăţi, deoarece, după cum am mai discutat,
închiderea şi acoperirea minimală ale seturilor de DF chiar şi pentru relaţii nu foarte voluminoase sunt
greu de manevrat. Chris Date reproşează modelului (şi Bernstein acceptă observaţia) că operaţiunile
executate în cadrul algoritmului de sinteză sunt exclusiv de natură sintactică şi nu iau în considerare
latura semantică27. Însă exemplul luat de Date pentru a pune în dificultate algoritmul de sinteză
constituie o capcană şi pentru varianta descompunerii.
O variantă mult mai directă şi relevantă pentru practicieni aparţine lui Henry Smith care a
publicat în Communications of the ACM un foarte interesant articol în care arată că o structură deplin
normalizată poate fi obţinută pe baza unei riguroase diagrame de DF28. De fapt, diagramele sale
seamănă mai degrabă cu grafuri, decât forma lor consacrată29. Smith afirmă că descompunerea clasică,
pornind de la relaţia universală este greoaie, iar construirea relaţiei universale reclamă, de multe ori,
un efort important. Lucrarea lui Smith este cu atât mai tentantă cu cât nu operează cu algoritmi pentru
determinarea închiderilor şi acoperirilor de seturi de DF. Ţinând seama că, în 3NF, relaţiile nu trebuie
să conţină dependenţe parţiale şi tranzitive, se va reprezenta grafic acoperirea minimală.

26
[Berstein76]
27
[Date00], pp. 377-378
28
[Smith85]
29
vezi, spre exemplu, [Date86] sau [Pratt & Adamski91]
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 53

De fapt, un avantaj decisiv al grafurilor DF este posibilitatea identificării imediate şi eliminării


atât a dependenţelor parţiale, cât şi a celor tranzitive. În plus, există situaţii în care normalizarea
„clasică” generează probleme, în timp ce, folosind graful, schema obţinută este net superioară.

Astfel, pentru BD BIBLIOTECĂ, graful acoperirii minimale este reprezentat în figura 3.11.
Totuşi, pentru ilustrarea modului de identificare şi eliminare dependenţelor parţiale şi tranzitive
pornim de la graful din stânga figurii 3.13. Spre deosebire de figura „sursă”, săgeţile ce reprezintă
dependenţe parţiale sau tranzitive sunt punctate. Eliminându-le, obţinem graful din dreapta figurii pe
care delimităm câte o relaţie pentru fiecare sursă, simplă sau compusă. Relaţia corespondentă va
conţine sursa, drept cheie primară, plus toate destinaţiile sale directe (şi totale).

Figura 3.13. Cele patru tabele în 3NF pentru relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3

În final, nu ne mai rămâne decât să dăm câte un nume fiecăreia dintre cele patru relaţii:
EDITURI, TITLURI, EXEMPLARE, TITLURI_AUTORI_CUVINTECHEIE.

Cele 11 dependenţe ale relaţiei STUDENŢI_EXAMENE identificate în paragraful 3.4.2 pot fi


lesne reprezentate ca un graf - vezi figura 3.14. Dependenţele punctate sunt parţiale, deoarece
destinaţiile lor sunt legate de surse simple, aşa că în final sunt eliminate din calcul.

Figura 3.14. Aducerea relaţiei STUDENŢI_EXAMENE în 3NF cu ajutorul grafului DF

Pe graf sunt trei surse, aşa încât vom obţine trei relaţii, cu următoarele atribute: { Matricol,
NumePrenume, An, Specializare}, {CodDisc, DenumireDisc, NrCredite} şi {Matricol,
CodDisc, DataExamen, Nota}. Schema seamănă leit cu cea din paragraful 3.4.2.

Pentru BD VÎNZĂRI, prin reprezentarea sub formă de graf, economia de timp pentru aducerea
în 3NF este şi mai spectaculoasă, după cum reiese şi din figura 3.15. Singura problemă ţine de atenţie
în delimitarea tuturor surselor şi dependenţelor lor, ceea ce poate creea oarecare confuzie.
54 Baze de date I

Figura 3.15. Decuparea relaţiilor în 3NF pentru baza de date VÎNZĂRI

Pentru majoritatea cazurilor întâlnite în practică, construirea grafului dependenţelor


funcţionale ce alcătuiesc acoperirea minimală, este una din cele mai simple şi la îndemână modalităţi
de aducere a bazei de date în 3NF. De o manieră asemănătoare se pot determina relaţiile celei de-a
treia forme normale utilizând matricea dependenţelor incluse în acoperirea minimală.

3.5.5 Forma normală Boyce-Codd


Definirea celei de-a treia forme normale, aşa cum a fost formulată iniţial de Codd, ridica o
serie de probleme în anumite circumstanţe, probleme pe care formularea din acest patagraf le rezolvă.
Chris Date este de părere că situaţiile în care definiţia originală a 3NF este insuficientă se traduce prin
trei condiţii simultane30: a) relaţia are mai multe chei candidate; b) o parte din cheile candidate sunt
compuse; c) cheile compuse au atribute în comun.
Întrucât îndeplinirea simultană a celor trei condiţii se întâlneşte destul de rar în practică, a treia
formă normală, aşa cum a fost formulată în paragraful precedent, este suficientă. Se spune despre o
relaţie că este în forma normală Boyce-Codd (BCNF) dacă: 1. se află deja în 3NF; 2. nu există nici o
DF a cărei sursă să fie un atribut ne-component al cheii, iar destinaţia un atribut din cheie.
Într-o altă formulare, o relaţie R este în BCNF dacă şi numai dacă orice determinant (sursă de
dependenţă funcţională sau supercheie) este o cheie-candidat. Această exprimare are şi avantajul de a
nu lega BCNF de 3NF, unii autori preferând să discute problema normalizării direct în termenii
BCNF, fără a trece prin formele intermediare31. Numele formei normalizate este discutabil, deoarece
primul care elaborează o definiţie echivalentă a ceea ce reprezintă astăzi BCNF este Ian Heath. În
penultima ediţie a cărţii sale ce a împlinit recent 25 de ani, Chris Date afirmă că ar fi fost mai indicată
titulatura forma normală a lui Heath (Heath Normal Form)32.

30
[Date86], p.374
31
Vezi, spre exemplu, [Garcia-Molina s.a.]
32
[Date00], p.366
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 55

Relaţia PROIECTE
Candace Fleming şi Barbara von Halle exemplifică utilitatea BCNF prin relaţia PROIECTE
din figura 3.1633 în care se manifestă urmăroarele restricţii:
la fiecare proiect există mai mulţi supervizori şi mai mulţi membri;
un membru poate participa la oricâte proiecte;
persoană poate superviza un singur proiect;
un supervizor poate coordona (în cadrul aceluiaşi proiect) mai mulţi membri;
într-un proiect, un membru este coordonat de un singur supervizor.

Figura 3.16. Relaţie aflată în 3NF, nu însă şi în BCNF

În aceste condiţii, se verifică următoarele DF: (1) (ProiectNr, Membru) Supervizor


şi (2) (Supervizor, Membru) ProiectNr.Prin urmare, relaţia are două chei candidat.
Totodată, însă, există şi DF Supervizor ProiectNr, ceea ce face din dependenţa (2) una
parţială. Atributul Supervizor este determinant fără a fi cheie candidat. În aceste condiţii, relaţia nu
este în BCNF. Pentru a o aduce în această formă normalizată este necesară transformarea grafului DF,
aşa cum arată figura 3.17.

Figura 3.17. Transformarea grafului DF pentru relaţia PROIECTE

Cele două relaţii obţinute sunt: SUPERVIZORI {Supervizor, ProiectNr} şi MEMBRI { Super-
vizor, Membru } reprezentate în figura 3.18.

Figura 3.18. Aducerea relaţiei PROIECTE în BCNF

33
Exemplul este preluat şi în alte lucrări, precum [Teorey99]
56 Baze de date I

O prima problemă semnalată de cele două autoare se referă la pierderea unei restricţii de
integritate: la un proiect, un membru are un singur supervizor. Cu alte cuvinte, noua structură a BD
permite ca unui membru să-i fie asociaţi doi supervizori în cadrul aceluiaşi proiect, ceea ce este
incorect în condiţiile date. În plus, exemplului prezentat i se poate reproşa şi rigiditatea, deoarece în
decursul anilor un supervizor poate răspunde de mai multe proiecte, dar acest din urmă neajuns este
mai degrabă unul mărunt.

Profesori, cursuri şi specializări


Cred că, de la C.J. Date încoace, cel mai frecvent întâlnit exemplu pentru ilustrarea formei
normale Boyce-Codd este cel privitor la cursuri-profesori-studenţi. Nu vom face rabat de la “regulă” şi
definim tabela SCP, reprezentată în figura 3.19, în condiţiile în care un profesor poate preda la oricâte
specializări, dar numai un singur curs!

Figura 3.19. Relaţia SCP

Să analizăm dependenţele funcţionale.


- un profesor predă un singur curs: Profesor Curs
- un profesor predă la mai multe specializări: Profesor / Specializare
- un acelaşi curs poate fi ţinut de mai mulţi profesori: Curs / Profesor
- un acelaşi curs poate fi predat la mai multe specializări: Curs / Specializare
- la o specializare, se predau mai multe cursuri (din păcate): Specializare / Curs
- la o specializare predau cursuri mai mulţi profesori: Specializare / Profesor
- la o specializare, un curs este predat de un singur profesor: (Specializare, Curs) Profesor
- un profesor poate ţine cursul la mai multe specializări: (Profesor, Curs) / Specializare
Cuplul de atribute (Specializare, Curs) este cheie primară. Pe baza dependenţei
funcţionale Profesor Curs în BCNF tabela SCP va fi descompusă în două tabele, SP şi PC,
prezentate în figura 3.20.

Figura 3.20. Tabelele SP şi PC aflate în BCNF


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 57

3.5.6 Dependenţe tranzitive necanonice


În exemplele de până acum am avut de lucrat cu dependenţe funcţionale în formă canonică –
cele în care destinaţia este alcătuită dintr-un singur atribut. Lucrurile nu sunt grozav de complicate,
identificarea dependenţelor fiind o operaţiune lesnicioasă. Din păcate, în unele situaţii este posibil ca
tranzitivitatea să se stabilească prin dependenţe ale căror destinaţii sunt compuse. La normalizarea
relaţiilor este recomandabil ca, în situaţia unor dependenţe funcţionale de genul: A B, A
C, A D, A E, dependenţe ce derivă din rolul de cheie primară pe care îl îndeplineşte
atributul A, să se verifice dacă nu cumva există fie o dependenţă funcţională simplă, de genul B
D, fie o dependenţă funcţională cu sursa compusă, de genul (B, C) D. Dacă una din cele două
afirmaţii de mai sus este adevărată, atunci dependenţa funcţională A D nu este directă.
Pentru un plus de credibilitate, să reluăm cazul bazei de date destinată evidenţierii rezultatelor
obţinute de studenţi la examene programate în sesiunile de pe parcursul unui an universitar
(STUDENŢI_EXAMENE) căreia îi extindem structura: R {Matricol, NumePren, An, Modul, Spec,
Grupa, CodDisc, DenDisc, NrCredDisc, CodProf, NumeProf, GradProf, DataEx, SalaEx,
Nota}, al cărei dicţionar simplicat este prezentat în tabelul 3.4
Tabel 3.4. Dicţionarul datelor pentru baza de date EXAMENE
Atribut Descriere Tip
Matricol O combinaţie de litere şi cifre ce identifică în mod unic un student CHAR
NumePren Numele şi prenumele studentului CHAR
An Anul de studiu NUMERIC
Modul Modulul de studiu: este o literă ce desemnează un grup de specializări CHAR
Spec Specializarea („C‟ de la contabilitate, „I‟ de la Informatică economică etc.) CHAR
Grupa Grupa de studiu NUMERIC
CodDisc Codul disciplinei din planul de învăţământ al specializării CHAR
DenDisc Denumirea disciplinei CHAR
NrCredDisc Numărul de credite alocat disciplinei NUMERIC
CodProf Codul profesorului CHAR
NumeProf Numele profesorului CHAR
GradProf P - profesor, C - conferenţiar, L - lector, A - asistent, R -preparator, A - CHAR
din afară (colaborator din afara universităţii)
DataEx Data examenului DATE
SalaEx Sala de desfăşurare a examenului CHAR
Nota Nota obţinută NUMERIC

Pentru simplificare, am luat în calcul numai studenţii de la profilul Economic, cursuri de zi. O
linie din R reprezintă rezultatul obţinut de un student la o disciplină într-o sesiune de examinare. Spre
exemplu, un student (matricol EL001103) poate obţine la disciplina Microeconomie (cod AE1001)
nota 4 (scuze) în prima sesiune de examinare – ianuarie 2005 (data examenului: 19 ianuarie 2005) şi
nota 9 în a doua sesiune de examinare – februarie 2005 (data ex: 14/02/2005). Cei mai puţin dispuşi la
efort se pot califica în faze superioare ale competiţiei (sesiunea mai 2005 s.a.m.d.). Acestea fiind
spuse, cheia primară a relaţiei R este, ca şi în cazul STUDENŢI_EXAMENE, ( Matricol, CodDisc,
DataEx), pe baza căreia pot fi scrise următoarele DF:
(1) (Matricol, CodDisc, DataEx) NumePren
(2) (Matricol, CodDisc, DataEx) An
(3) (Matricol, CodDisc, DataEx) Modul
(4) (Matricol, CodDisc, DataEx) Spec
(5) (Matricol, CodDisc, DataEx) Grupa
(6) (Matricol, CodDisc, DataEx) DenDisc
58 Baze de date I

(7) (Matricol, CodDisc, DataEx) NrCredDisc


(8) (Matricol, CodDisc, DataEx) CodProf
(9) (Matricol, CodDisc, DataEx) NumeProf
(10) (Matricol, CodDisc, DataEx) GradProf
(11) (Matricol, CodDisc, DataEx) SalaEx
(12) (Matricol, CodDisc, DataEx) Nota
Relaţia R este în prima formă normalizată. După cum am văzut în capitolul precedent, pentru a
demonstra că nu este în 2NF trebuie să găsim în ansamblul DF (1)-(12) măcar o DF parţială. Lucru
destul de simplu, dacă ţinem seamă de următoarele DF în care sursa o constituie unul dintre atributele
componente ale cheii.
Matricolul identifică în mod unic un student înscris la o specializare într-un an de studii: (13)
Matricol NumePren; (14) Matricol An; (15) Matricol Modul;
(16) Matricol Spec; (17) Matricol Grupa.
CodDisc identifică în mod unic fiecare discplină din planul de învăţământ al specializării: (18)
CodDisc DenDisc; (19) CodDisc NrCredDisc.
Pe baza ultimelor şapte DF, relaţia universală R se poate descompune astfel: R1 {Matricol,
NumePren, An, Modul, Spec, Grupa}, R2 { CodDisc, DenDisc, NrCredDisc}, R3 {Matricol,
CodDisc, CodProf, NumeProf, GradProf, DataEx, SalaEx, Nota}.
Cele trei relaţii sunt în 2NF, iar până aici similitudinea cu discuţia pricinuită de relaţia
STUDENŢI_EXAMENE este tulburătoare. Aducerea BD în 3NF echivalează cu eliminarea
dependenţelor tranzitive din R1, R2 şi R3. În R1 singurele DF existente sunt (13)-(17), deci această
relaţie este în 3NF. Nici R2 nu conţine dependenţe tranzitive; în schimb R3 da:
CodProf identifică în mod unic un profesor: (20) CodProf NumeProf;
(21)CodProf GradProf. Pe baza DF (20) şi (21), putem spune că (9) şi (10) sunt tranzitive,
aşa încât rupem R3 în R31 {CodProf, NumeProf, GradProf } şi R32 {Matricol, CodDisc,
CodProf, DataEx, SalaEx, Nota}. Astfel, în 3NF, relaţia universală R are o schemă alcătuită din
patru relaţii, după cum urmează: STUDENTI {Matricol, NumePren, An, Modul, Spec, Grupa},
DISCIPLINE { CodDisc, DenDisc, NrCredDisc }, PROFESORI {CodProf, NumeProf,
GradProf }, NOTE1 {Matricol, CodDisc, CodProf, DataEx, SalaEx, Nota}.
Nici una din cele patru relaţii nu conţine DF tranzitive. Cu toate acestea, schema este departe
de a fi optimă. Faţă de DF discutate, mai există două restricţii neluate în calcul în normalizare:
a. La o serie de curs (seriile de curs se constituie, pentru fiecare disciplină, la nivel de an, modul,
specializare) titular este un singur profesor: (22)(CodDisc, An, Modul, Spec) CodProf
b. Studenţii unei specializări susţin examenul la o disciplină în aceeaşi sală (şi aceeaşi zi): (23) ( An,
Modul, Spec, DataEx) SalaEx
Să recapitulăm: în relaţia NOTE există DF: (8) (Matricol, CodDisc, DataEx)
CodProf, dar şi (22) (CodDisc, An, Modul, Spec) CodProf. Deoarece există DF (24):
Matricol An, Modul, Spec rezultă ca (8) este o DF necanonică, aşa încât relaţia NOTE1 s-ar
descompune în: DISC_SPEC_PROFI { CodDisc, An, Modul, Spec, CodProf} şi NOTE2
{Matricol, CodDisc, DataEx, SalaEx, Nota}. Câştigul este considerabil.
Analog se poate proceda cu DF (23), discuţia transferându-se în relaţia NOTE2, în care, din
calitatatea de cheie primară a celor trei atribute există dependenţa: (11) (Matricol, CodDisc,
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 59

DataEx) SalaEx dar şi DF (23) (An, Modul, Spec, CodDisc, DataEx) SalaEx. Pe
baza dependenţei (24), relaţia NOTE2 se sparge în SALI_EX {An, Modul, Spec, CodDisc, DataEx,
SalaEx} şi NOTE {Matricol, CodDisc, DataEx, Nota}.
Ajungem, astfel, la o cu totul altă structură a bazei de date, mult mai bună decât precedenta:
STUDENTI {Matricol, NumePren, An, Modul, Spec, Grupa}, DISCIPLINE {CodDisc, DenDisc,
NrCredDisc}, PROFESORI {CodProf, NumeProf, GradProf }, DISC_SPEC_PROFI { CodDisc,
An, Modul, Spec, CodProf}, SALI_EX {An, Modul, Spec, CodDisc, DataEx, SalaEx}, NOTE
{Matricol, CodDisc, DataEx, Nota}
Ca de obicei, dependenţele tranzitive, canonice sau necanonice se observă mult mai lejer pe
graful DF (vezi fig. 3.21). Este suficient să decupăm câte o relaţie pentru fiecare sursă simplă şi
compusă din graf, sursă ce devine automat cheie a relaţiei respective, şi vom obţine structura de mai
sus.

Figura 3.21. Graful DF pentru baza de date EXAMENE

Structura finală îndeplineşte şi cerinţele formei normale Boyce-Codd ca fiecare sursă de DF să


fie cheie candidată. Putem institui şi o regulă: atunci când descompunerea se face urmând filiera
clasică:
stabilirea cheii primare a relaţiei iniţiale (universale), apoi
eliminarea, dintre dependenţele funcţionale ce decurg din calitatea de cheie primară a relaţiei
iniţiale, a celor parţiale, apoi
eliminarea, din toate relaţiile obţinute în pasul anterior, a tuturor dependenţelor tranzitive (în
care surse sunt cheile primare ale respectivelor relaţii),
trebuie verificat dacă în relaţia iniţială mai existau dependenţe cu sursa compusă ce nu apar drept cheie
primară sau candidat în nici una dintre relaţiile finale obţinute.

3.5.7 Adăugarea de atribute în schema unei baze de date


Revenim la un caz “clasat” pentru 3NF, cel al relaţiei BIBLIOTECĂ_TUPLURI_NOI_SO-
LUŢIA_3. Combinaţia care diferenţiază orice linie de toate celelalte este ( Cota, Autor,
CuvîntCheie). Intuim însă un aspect pe care dependenţele şi, implicit, normalizarea îl scapă, şi
anume că autorii şi cuvintele cheie se referă la o carte (adică ISBN) şi nu la un exemplar fizic din
depozit (Cota). Deci graful DF nu ar trebui să arate ca în figura 3.13, ci precum în figura 3.22. Din
60 Baze de date I

păcate, acesta este un aspect pe care îl intuim, dar e mai greu de demonstrat “ştiinţific” prin
normalizare. Un argument în plus pentru a nu considera normalizarea drept un proces infailibil, ci doar
un ghid în construirea unei scheme a BDR cât mai bune.

Figura 3.22. Autorii şi cuvintele cheie corespund titlurilor, nu exemplarelor din cărţi

Pe baza grafului de mai sus, din schema bazei de date în 3NF se schimbă ultima tabelă,
TITLURI_AUTORI_CUVINTECHEIE2 {ISBN, Autor, CuvântCheie}. Conţinutul celor patru
relaţii este acum cel din figura 3.23.
EDITURI
Editura LocSediuEd
Polirom Iaşi

TITLURI
ISBN Titlu Editura AnApariţie
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor Polirom 2002
profesionale
973-683-709-2 SQL. Dialecte DB2, Oracle şi Visual FoxPro Polirom 2001

EXEMPLARE
Cotă ISBN
III-13421 973-683-889-7
III-13422 973-683-889-7
III-13423 973-683-889-7
III-10678 973-683-709-2
III-10679 973-683-709-2

TITLURI_AUTORI_CUVINTECHEIE2
ISBN Autor CuvântCheie
973-683-889-7 Marin Fotache baze de date
973-683-889-7 Marin Fotache SQL
973-683-889-7 Marin Fotache proceduri stocate
973-683-889-7 Marin Fotache FoxPro
973-683-889-7 Marin Fotache formulare
973-683-889-7 Marin Fotache orientare pe obiecte
973-683-889-7 Marin Fotache client-server
973-683-889-7 Marin Fotache web
973-683-889-7 Ioan Brava baze de date
973-683-889-7 Ioan Brava SQL
973-683-889-7 Ioan Brava proceduri stocate
973-683-889-7 Ioan Brava FoxPro
973-683-889-7 Ioan Brava formulare
973-683-889-7 Ioan Brava orientare pe obiecte
973-683-889-7 Ioan Brava client-server
973-683-889-7 Ioan Brava web
973-683-889-7 Cătălin Strâmbei baze de date
973-683-889-7 Cătălin Strâmbei SQL
973-683-889-7 Cătălin Strâmbei proceduri stocate
973-683-889-7 Cătălin Strâmbei FoxPro
973-683-889-7 Cătălin Strâmbei formulare
973-683-889-7 Cătălin Strâmbei orientare pe obiecte
973-683-889-7 Cătălin Strâmbei client-server
973-683-889-7 Cătălin Strâmbei web
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 61

973-683-889-7 Liviu Creţu baze de date


973-683-889-7 Liviu Creţu SQL
973-683-889-7 Liviu Creţu proceduri stocate
973-683-889-7 Liviu Creţu FoxPro
973-683-889-7 Liviu Creţu formulare
973-683-889-7 Liviu Creţu orientare pe obiecte
973-683-889-7 Liviu Creţu client-server
973-683-889-7 Liviu Creţu web
973-683-709-2 Marin Fotache baze de date
973-683-709-2 Marin Fotache algebră relaţională
973-683-709-2 Marin Fotache SQL
Figura 3.23. O 3NF mai bună pentru relaţia BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3

Se observă cu ochiul liber o diminuare a volumului ultimei tabele, astfel încât ne-am
încumetat să reprezentăm toate liniile. Rămâne, în schimb, un grad de redundanţă chiar şi în forma
“comprimată” a tabelei.
Ca element de improvizaţie, putem încerca să adăugăm în BIBLIOTECĂ_TU-
PLURI_NOI_SOLUŢIA_3 câteva atribute care să „atragă” în dependenţe atributele Autor şi
CuvîntCheie. Astfel, pornim de la faptul că, pe coperta unei cărţi, autorii sunt trecuţi într-o anumită
ordine, deloc întâmplătoare, iar la extragerea informaţiilor despre cărţi această ordine trebuie
respectată. Prin urmare, introducem în schemă atributul OrdineCopertă, împreună cu DF: (ISBN,
OrdineCopertă) Autor. De asemenea, pentru a putea extrage cărţile legate de unul sau mai
multe cuvinte cheie în ordinea relevanţei, introducem atributul Revelanţă ce indică nivelul la care
este tratată într-o carte o anumită noţiune/sintagmă (cuvînt cheie): (ISBN, CuvîntCheie)
Relevanţă. Pe baza acestor două noi dependenţe, graful din figura 3.22 se modifică după cum este
sugerat în figura 3.24.
Din noul graf, relaţiile construite sunt aproape impecabile: EDITURI {Editura, LocSediuEd},
CĂRŢI {ISBN, Titlu, AnApariţie, Editură}, AUTORI_CĂRŢI {ISBN, OrdineCopertă, Autor},
CĂRŢI_CUVINTE_CHEIE {ISBN, CuvîntCheie, Relevanţă}, EXEMPLARE_CĂRŢI {Cota, ISBN}.

Figura 3.24. Noul graf pentru relaţia BIBLIOTECĂ (cu două atribute noi)

3.6 DEPENDENŢE MULTIVALOARE ŞI A PATRA FORMĂ NORMALĂ


Formelele normalizate 4 şi 5 constituie cea mai dificilă parte din procesul de normalizare a
unei baze de date. Aceasta este vestea rea. Vestea bună este că rareori proiectanţii bazei trebuie să
ajungă în acest stadiu al discuţiilor, mai ales în ceea ce priveşte 5NF, deoarece majoritatea cazurilor
practice pot fi rezolvate onorabil prin aducerea bazei în 3NF/BCNF sau, uneori, în 4NF.
62 Baze de date I

Dependenţele funcţionale discutate până în acest paragraf sunt relativ uşor de identificat şi
explicat. În prezentul capitol vor fi prezentate un tip de dependenţe ceva mai dificil – depedenţele
multi-valoare. Întrucât incidenţa practică a dependenţelor de joncţiune şi celei de-a cincea forme
normale este una nesemnificatică, în acest curs acestea vor fi trecute sub tăcere.

3.6.1 Dependenţe multivaloare


Dependenţa multivaloare sau multivalorică şi, implicit, a patra formă normală au fost
introduse la câţiva ani după dependenţele funcţionale şi primele trei forme normale, prin 1977 de către
Ronald Fagin34. Fie relaţia R {A1, A2,...., An} şi X, Y şi Z trei subansambluri de atribute ale
ansamblului {A1, A2,...., An}. Există o dependenţă multi-valoare (DMV) între X şi Y dacă şi numai
dacă: (a) la fiecare apariţie (valoare) a lui X poate fi asociată una sau mai multe apariţii (valori) ale lui
Y; (b) această asociaţie nu depinde de apariţiile lui Z. Se notează X Y sau X Y | Z şi
se spune că atributul Y este multidependent faţă de atributul X sau că atributul X multidetermină pe
Y.
Cea mai rezonabilă definiţie a DMV o furnizează C.J. Date. Fie relaţia R {A1, A2,...., An} şi
X, Y şi Z trei subansambluri ale ansamblului {A1, A2,...., An}. Există o dependenţă multi-valoare
între X şi Y în următoarea situaţie: dacă (x,y,z) şi (x,y',z') sunt două tuplete ale relaţiei R, atunci
(x,y',z) şi (x,y,z') aparţin, de asemenea, lui R. Într-o formulare uşor schimbată, Elmasri şi Navathe
definesc DMV de maniera următoare: dacă în R există două tupluri t1 şi t2 pentru care t1[X] = t2[X],
atunci există în R alte două tupluri, t3 şi t4, care satisfac următoarele condiţii (notăm prin Z toate
celelalte (decât X şi Y) atribute din R, adică (R - (X U Y))):
t3[X] = t4[X] = t1[X] = t2[X]
t3[Y] = t1[Y] şi t4[Y] = t2[Y]
t3[Z] = t2[Z] şi t4[Z] = t1[Z]35.
După cum afirmam mai sus, aspectul contra-intuitiv al definiţiei DMV ţine de faptul că X este
independent de Y, ceea ce s-ar traduce prin “X oarecare, Y oarecare”. O bună explicaţie în acest sens
o găsim în [Dollinger98]36: o valoare dată a lui X se găseşte în R în combinaţie (formează tupluri) cu
fiecare pereche de valori (y,z) din produsul cartezian al mulţimilor Yx (valorile lui y care apar în
combinaţie cu un x dat) şi Zx. Aceasta înseamnă ca mulţimile Yx şi Zx sunt independente între ele.
Cele mai tentante exemple de DMV sunt cele teoretice, aşa cum este cel din figura 3.25. În
această relaţie există nu există nici una din dependenţele funcţionale: X / Y; X / Z ; Y
/ X; Y / Z; Z / X; Z / Y.

34
[Fagin77]
35
[Elmasri & Navathe 00], p. 514
36
[Dollinger98], p.163
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 63

Figura 3.25. Exemplu teoretic de relaţie în care există DMV

La o examinare mai atentă se observă dependenţele multi-valoare X Y (sau X


Y | Z) şi X Z (sau X Z | Y); aceasta deoarece în condiţiile în care există
tuplurile (x1,y1,z1) şi (x1,y2,z2), există şi tuplurile (x1,y1,z2) şi (x1,y2,z1).
Cel mai dureros aspect al dependenţelor multivaloare ţine de faptul că, în realitate, acestea
trebuie identificate de către cei care fac analiza şi proiectarea bazei de date, pe baza legăturilor
semantice dintre atributele bazei, şi nu pe baza unui caz simplist (în cazul nostru, o tabelă cu şapte
linii).

Profesori, cursuri & capitole


Cazul următor este aproape identic celui prezentat de C.J. Date în ediţiile sale ale lucrării An
Introduction to Database Systems37. Relaţia are trei atribute: CPC {Curs, Profesor, Capitol}38. La
o facultate "oarecare", pot exista mai mulţi profesori titulari ai unei aceleaşi discipline. Dacă
presupunem că toţi profesorii cad de acord asupra unei programe unice pentru disciplina respectivă,
diferenţiat fiind numai modul de "tratare" şi ponderea fiecărui capitol, putem imagina o situaţie ca în
figura 3.26.
Analizăm dependenţele dintre atribute:
- Un acelaşi curs poate fi "ţinut" de mai mulţi profesori: Curs / Profesor
- Un curs este alcătuit din mai multe capitole: Curs / Capitol
- Fiecare profesor poate preda mai multe cursuri: Profesor / Curs şi, implicit, Profesor
/ Capitol

Figura 3.26. Relaţia CPC în care există DMV

37
Spre exemplu, [Date86], pp. 381-382
38
La C.J.Date relaţia era CTX {Curs, Profesor, LucrareBibliografică}
64 Baze de date I

- Un acelaşi capitol poate apărea în cursuri diferite. De exemplu, un capitol dedicat Poştei Electronice
poate apărea şi în cursul Bazele informaticii Economice şi în cursul de Birotică. Capitolul Modelul
Entitate-Asociaţie este în planul cursului de Baze de date, dar şi al cursului Analiza sistemelor
informatice (pentru modelul conceptual al datelor). Prin urmare, Capitol / Curs iar Capitol
/ Profesor.
Numai în condiţiile unei programe unice, la nivelul facultăţii, pentru fiecare curs există
dependenţele multi-valoare: Curs Profesor; Curs Capitol.
C.J. Date face o observaţie foarte bine venită: într-o asemenea relaţie, pentru a introduce trei
profesori şi patru capitole ar fi suficiente patru linii, şi nu 12 (3*4). Aceasta naşte însă câteva
probleme: cum se realizează corespondenţa profesor-capitol, adică ce capitole a elaborat fiecare
profesor ? Numărul capitolelor fiind mai mare ca al autorilor, pentru acele linii, ce profesori apar ?
Cum este interpretată fiecare linie în acest caz ? Cum se fac actualizările ?

Relaţia COMENZI_PRODUSE_MATERIALE
O întreprindere manufacturieră foloseşte pentru confecţionarea unei unităţi de produs finit o
serie de materii prime şi materiale, în conformitate cu fişa tehnologică. Producţia este pe comenzi. Se
poate imagina o tabelă care să conţină informaţiile cu privire la ce materiale sunt necesare onorării
unei anumite comenzi, COMENZI_PRODUSE_MATERIALE – vezi figura 3.27.

Figura 3.27. Relaţia COMENZI_PRODUSE_MATERIALE

În această tabelă, este imperios necesar ca, atunci când un produs apare într-o comandă, să se
introducă câte o linie pentru fiecare material ce intră în fabricaţia produsului respectiv. Prin urmare,
există dependenţele multi-valoare: Produs Comandă | Material.

Din nou despre bibliotecă


Cel mai convingător exemplu de dependenţă multi-valoare este cel din relaţia
BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 pe care o preluăm aşa cum am lăsat-o în figurile 3.22
(graful) şi 3.23 (conţinutul în 3NF-BCNF). În relaţia TITLURI_AUTORI_CUVINTECHEIE2 fiecare
linie se referă la un autor şi un subiect (cuvânt cheie) pentru o carte (ISBN). Practic, pe baza acestei
tabele suntem în măsură să obţinem informaţii de genul:
ce cărţi a scris un anume autor,
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 65

despre ce subiecte a scris un anume autor,


care sunt autorii unei cărţi,
ce subiecte sunt tratate într-o carte,
care sunt cărţile în care apare tratat un anume subiect,
care sunt autorii care au scris despre un subiect dat.
Pentru ca nici una dintre aceste informaţii să nu se piardă, există un preţ adecvat. Dacă, pentru
o carte, apare o nouă sintagmă (cuvânt cheie), în relaţie trebuie introdus nu un tuplu, ci n, unde n este
egal cu numărul autorilor acelei cărţi. Pentru o carte cu 5 autori şi 10 cuvinte cheie, în relaţia TI-
TLURI_AUTORI_CUVINTECHEIE2 trebuie introduse nu mai puţin de 50 de linii.
Pe baza faptului că pentru o carte, un autor trebuie să apară în combinaţie cu toate
sintagmenele tratate şi, vice-versa, orice sintagmă apare în combinaţie cu toţi autorii cărţii, există
DMV: ISBN Autor | CuvântCheie şi ISBN CuvântCheie | Autor.

3.6.2 A patra formă normală


Deşi mai exotică decât primele trei şi BCNF, a patra formă normală (4NF) işi are importanţa
sa. Margaret Wu39 afirma că, în timp ce majoritatea universitarilor şi autorilor din zona normalizării
tinde să neglijeze 4NF şi 5NF, un studiu întreprins într-o serie de firme ce foloseau (la începutul anilor
`90) aplicaţii cu baze de date a scos la iveală că mai mult de 20% dintre schemele bazelelor de date
analizate violau 4FN. Prin comparaţie, Wu nu a identificat nici un caz de violare a 5NF.
În lucrarea menţionată în paragraful anterior, Ronald Fagin formulează o teoremă conform
căreia o relaţie R cu trei atribute A, B şi C poate fi descompusă fără pierdere de informaţii în două
proiecţii ale sale: R1{A, B} şi R2 {A, C} dacă şi numai dacă există dependenţă multivaloare A
B | C. Aceasta este teorema care ne va permite aducerea unei relaţii în 4NF.
O relaţie este în 4NF dacă şi numai dacă: (a) Este în BCNF; (b) Toate dependenţele care se
manifestă în cadrul său sunt dependenţe funcţionale, altfel spus, eventualele dependenţe multi-valoare
conţinute sunt, de fapt, dependenţe funcţionale.
Vom lua pe rând exemplele prezentate în paragraful anterior şi vom îndeplini formalitatea
aducerii lor în 4NF. Începem cu cel mai îndrăgit exemplu, primul. Relaţia XYZ din figura 3.25
prezintă, aşa cum am discutat, dependenţa multi-valoare X Y | Z, în virtutea căreia o putem
descompune în două: XY {X, Y} şi XZ {X, Z} ca în figura 3.28.

Figura 3.28. Aducerea relaţiei XYZ în 4NF

Pe baza dependenţelor multivaloare prezentate în paragraful precedent, relaţia CO-


MENZI_PRODUSE_MATERIALE (figura 3.27) se descompune ca în figura 3.29.

39
[Wu92]
66 Baze de date I

Figura 3.29. Aducerea relaţiei COMENZI_PRODUSE_MATERIALE în 4NF

Ceva mai interesantă este situaţia relaţiei BIBLIOTECĂ_TUPLURI_NOI_SOLUŢIA_3 care,


după cum am văzut în paragraful precedent, conţine o dependenţă multi-valoare. Prin eliminarea
acesteia (dependenţei) vom aduce relaţia în 4NF. Deşi rareori întâlnită în literatura de specialitate,
reprezentarea grafică a DMV în graful dependenţelor nu ridică probleme deosebite, mai ales atunci
când cele trei atribute (X, Y şi Z din definiţiile noastre) sunt simple. Iată, în figura 3.30, care ar fi o
posibilă reprezentare şi modul de decupare al relaţiilor în 4NF din graf.

Figura 3.30. Graf ce conţine deopotrivă DF şi DMV

Conform obiceiului, se va constitui câte o relaţie pentru fiecare sursă de dependenţă


funcţională (Cota, ISBN şi Editura). La aceste relaţii se va adăugă câte o relaţie pentru fiecare
destinaţie de dependenţă multi-valoare (Autor şi CuvîntCheie). Schema finală a bazei de date,
precum şi conţinutul relaţiilor sunt ilustrate în figura 3.31.
EDITURI
Editura LocSediuEd
Polirom Iaşi

TITLURI
ISBN Titlu Editura AnApariţie
973-683-889-7 Visual FoxPro. Ghidul dezvoltării aplicaţiilor Polirom 2002
profesionale
973-683-709-2 SQL. Dialecte DB2, Oracle şi Visual FoxPro Polirom 2001

EXEMPLARE
Cotă ISBN
III-13421 973-683-889-7
III-13422 973-683-889-7
III-13423 973-683-889-7
III-10678 973-683-709-2
III-10679 973-683-709-2
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 67

TITLURI_AUTORI
ISBN Autor
973-683-889-7 Marin Fotache
973-683-889-7 Ioan Brava
973-683-889-7 Cătălin Strâmbei
973-683-889-7 Liviu Creţu
973-683-709-2 Marin Fotache

TITLURI_CUVINTECHEIE
ISBN CuvântCheie
973-683-889-7 baze de date
973-683-889-7 SQL
973-683-889-7 proceduri stocate
973-683-889-7 FoxPro
973-683-889-7 formulare
973-683-889-7 orientare pe obiecte
973-683-889-7 client-server
973-683-889-7 web
973-683-709-2 baze de date
973-683-709-2 algebră relaţională
973-683-709-2 SQL
Figura 3.31. 4NF pentru relaţia BIBLIOTECĂ

Iată că, după atâta amar de capitole şi discuţii, am reuşit să rezolvăm de o manieră acceptabilă
problema schemei bazei de date destinate gestionării informaţiilor revelante despre cărţile aflate în
biblioteca unei facultăţi/universităţi. Este, cred, inutil să mai intrăm în delalii vis-a-vis de reducerea
volumului bazei, ca şi cvasi-dispariţia oricărei forme de anomalie la inserarea, modificarea sau
ştergerea unei linii în oricare dintre cele cinci tabele.

3.6.3 Un mini-caz practic - baza de date FILMOGRAFIE


Discuţiile despre această bază de date au rămas suspendate încă de pe la începutul capitolului.
În paragraful 3.2 semnalam necazurile pricinuite de valorile nule ale cheii primare în condiţiile în care
o parte dintre premii era acordată la nivel de film (Oscar pentru regie, Oscar pentru cel mai bun film
etc.), iar altă parte la nivel de actor pentru prestaţia dintr-un film (Oscar pentru cel mai bun
actor/actriţă în rol principal/secundar). La acel moment singura soluţia era constituirea a două tabele
dedicate premiilor.
Pentru ceea ce urmează considerăm atributele din tabelul 3.3. (paragraful 3.2) între care
încercăm să determinăm dependenţele funcţionale şi multivaloare. Astfel, atributul IdFilm identifică
fără ambiguitate orice film, aşa că avem următoarea serie de DF: (1) IdFilm TitluOriginal;
(2) IdFilm TitluRO; (3)IdFilm AnLans.
Deoarece un film poate fi încadrat la mai multe genuri (vezi figura 2.5) şi avea mai mulţi
producători, regizori, actori şi premii, IdFilm / Gen; IdFilm / Producător; IdFilm
/ Regizor; IdFilm / Actor; IdFilm / DenPremiu.
Un premiu se decernează într-o localitate (ex. Oscarul la Hollywood, Leul de argint la
Veneţia, Ursul de aur la Berlin etc.): (4)DenPremiu LocDecernare
Premiile au, de obicei, mai multe categorii (ex. Oscarul are categoriile: Cel mai bun film, Cel
mai bun film străin, Efecte speciale, Regie etc.) şi se acordă de mulţi ani: DenPremiu /
Categorie; DenPremiu / AnPremiu.
Ne punem problema reflectării prin dependenţe a distribuţiei într-un film. După cum am văzut
mai sus (de fapt, ştiam de mult), într-un film sunt distribuiţi mai mulţi actori; însă şi un actor poate
68 Baze de date I

juca în multe filme - Actor / IdFilm. Am avea de ales între dependenţele (IdFilm, Actor)
Rol (poate era mai nimerit ca atributul să se numească Personaj) sau (IdFilm, Rol)
Actor. Dumneavoastră pe care aţi alege-o ? Ei bine, prima n-ar fi chiar indicată, deoarece există filme
în care un actor joacă un dublu rol (vă mai amintiţi Zmeura de aur pentru dublul rol al lui Leonardo di
Caprio din filmul Masca de fier, în care îl interpretează pe regele Ludovic al XIV-lea şi pe fratele
regelui, unul "original", iar celălalt "mascatul" ?). În acest situaţii valorile grupului ( IdFilm, Actor)
sunt identice, în timp ce valorile atributul Rol sunt diferite, ceea ce infirmă dependenţa. Privitor la cea
de-a doua dependenţă, pericolul invalidării ei ar apărea în situaţia când într-un film acelaşi rol este
jucat de mai mulţi actori. Din fericire rolurile nu sunt tocmai aşa. Chiar şi în filmele biografice, sau
marile saga de familie, diferitele etape din viaţa unui personaj (copil, adolescent etc.) sunt marcate de
roluri diferite. Aşa că numărăm a cincea dependenţă a relaţiei, (5) (IdFilm, Rol) Actor.
Cel puţin la fel de interesant stau lucrurile şi cu premiile. Faptul că Globul de aur
(DenPremiu) pentru Cea mai bună imagine (Categorie) a fost decernat în 1988 (AnPremiu)
filmului Mai bine nu se poate (titlul original - As Good As It Gets), film ce are codul (IdFilm) 11899
în baza noastră de date, ne-ar determina să scriem dependenţa în forma: (DenPremiu, Categorie,
AnPremiu) IdFilm.
Ce ne facem, însă, când un premiu la aceeaşi categorie este acordat simultan la două filme ?
Nu e cazul Oscarurilor, dar, spre exemplu, câte o menţiune specială din partea juriului (Categorie)
la ediţia din 2007 (AnPremiu) a festivalului Ursul de aur (Denpremiu) ar pute fi acordată filmelor cu
identificatorii 7898843, 8967883 şi 9312299. Dependenţa este aruncată în aer. Soluţia este însă pe-
aproape: un film nu poate lua un acelaşi premiu, la aceeaşi categorie, în ani diferiţi, aşa că schimbăm
poziţia a două atribute: (6) (DenPremiu, Categorie, IdFilm) AnPremiu.
Dependenţa de mai sus nu rezolvă problema premiilor acordate pentru prestaţiile actoriceşti, în
care laureat este un actor pentru prestaţia dintr-un film. Am putea alege între varianta (DenPremiu,
Categorie, AnPremiu, IdFilm) Actor şi (DenPremiu, Categorie, AnPremiu, Actor)
IdFilm. Prima variantă este minată dacă într-un an un premiu (la aceeaşi categorie) este
acordat simultan pentru doi actori care joacă în acelaşi film. Situaţia nu pare a fi prea frecventă, dar
nici imposibilă, dacă ne gândim la festivalurile mai puţin poleite decât Oscar, Palme D'Or etc. A doua
variantă are şi ea un competitor, croit după analogia cu dependenţa (6): ( DenPremiu, Categorie,
IdFilm, Actor) AnPremiu. Iniţial eram tentat de această ultimă formă, deoarece era similară
celei care reflecta celelalte tipuri de premii. Există, însă, o categorie de premii ce crează o serie de
dureri de cap: premiile pentru întreaga carieră. Acestea nu sunt legate de un film anume, ci răsplătesc
o carieră, mai lungă sau mai scurtă, de cineast. Nefiind legată de un film, sursa dependenţei, pe baza
căreia vor constitui o relaţie, ar conţine în aceste situaţii o valoare NULL (pentru IdFilm), ceea ce e
inadmisibil pentru o componentă a cheii primare. Cu toate acestea, vom păstra această dependenţă,
întrucât premiul pentru întreaga carieră nu se acordă numai actorilor, ci şi regizorilor (teoretic şi altor
profesiuni legate de industria/arta cinematografică), iar dacă optam pentru a doua variantă tot nu
scăpam de valori nule, e drept, pentru un atribut necheie. Ca să tranşăm discuţia, întrucât baza de date
se referă la informaţii despre filme, nu ne propunem să preluăm premiile individuale, aşa că ne
declarăm mulţumiţi de dependenţa: (7) (DenPremiu, Categorie, IdFilm, Actor) AnPremiu
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 69

Au rămas neluate în seamă trei atribute, Producător, Regizor şi Gen. Intuiţia ne spune că
este imposibil de stabilit vreo dependenţă între ele, chiar dacă se referă la acelaşi film. Pe de altă parte,
cele trei atribute par cât se poate de independente unele de altele. Verificăm, mai întâi, cerinţa DMV
pentru primele două. Dacă filmul X cu id-ul 234556 are, printre regizori, pe Reg1 şi Reg2 şi cel puţin
doi producători, Prod1 şi Prod2, atunci trebuie să verificăm că, dacă atributele ar apărea într-o relaţie
de tip R {IdFilm, Producător, Regizor}, oricare dintre tuplurile următoare sunt posibile:

RELx
IdFilm Producător Regizor
... ... ...
234556 Prod1 Reg1
234556 Prod2 Reg2
234556 Prod1 Reg2
234556 Prod2 Reg1
... ... ...
Figura 3.32. Raportul dintre atributele Producător şi Regizor

Prezenţa ultimelor două tupluri în relaţia din figura 3.32 pare a fi agreată, nu însă şi necesară.
Schema de mai sus, însă, trebuie să furnizeze răspunsul corect la întrebarea Care sunt filmele în care
producătorul Prod1 a lucrat cu regizorul Reg1 ? Faptul că, pentru a-şi păstra viabilitatea
informaţională, relaţia trebuie să conţină toate cele patru tupluri constituie motiv suficient pentru a
"trâmbiţa" dependenţa multivalorică: IdFilm Producător | Regizor.
Pe acelaşi calapod, pornind de la răspunsul la întrebarea Ce genuri de filme a produs/regizat
un anumit cineast ?, putem demonstra că: IdFilm Producător | Gen sau IdFilm
Regizor | Gen. Forma finală a grafului dependenţelor funcţionale şi multivalorice ar putea fi cea
din figura 3.33.

Figura 3.33. Graful dependenţelor pentru BD FILMOGRAFIE

Urmează partea cea mai frumoasă - decuparea relaţiilor din graf. Sunt două surse simple de
DF - DenPremiu şi IdFilm şi trei surse compuse - (IdFilm, Rol), (IdFilm, DenPremiu,
Categorie) şi (IdFilm, Actor, DenPremiu, Categorie), aşa că cinci relaţii vor fi obţinute din
dependenţele funcţionale. La acestea se mai adaugă trei, corespunzătoare dependenţelor multi-valoare.
Iată, deci, schema finală: PREMII_DENUMIRI {DenPremiu, LocDecernare}, FILME {IdFilm,
TitluOriginal, TitluRO, AnLans}, DISTRIBUŢIE {IdFilm, Rol, Actor}, PREMII_FILME
{IdFilm, DenPremiu, Categorie,AnPremiu}, PREMII_INTERPRETARE {IdFilm, Actor ,
DenPremiu, Categorie, AnPremiu}, PRODUCĂTORI {IdFilm, Producător}, REGIZORI
{IdFilm, Regizor}, FILME_GENURI {IdFilm, Gen}.
70 Baze de date I

3.7 CHEI SUROGAT


Folosind limbajul de lemn al tehnologiei informaţionale, putem spune că bazele de date
stochează persistent şi gestionează varii informaţii privitoare la fenomene, procese, tranzacţii,
persoane etc. din mai toate domeniile de activitate, în funcţie de aplicaţiile pentru care sunt proiectate
şi implementate. Una din problemele de căpătâi în lucrul cu aceste entităţi, procese, tranzacţii etc. ţine
de posibilitatea identificării lor fără ambiguitate.

3.7.1 Nevoia de surogate (în bazele de date)


Închipuiţi-vă ce-ar însemna ca firma noastră să vireze câteva sute de milioane de lei în contul
unui client, plătind astfel o factură, iar contul respectiv să fie confundat cu un altul, al altui client. Sau
ce s-ar întâmpla dacă la plata "pe card" a salariilor plătite s-ar produce niscaiva confuzii: cei mai
oropsiţi s-ar oripila văzând cât câştigă şefii (confirmând luminoasa teză "impozita-i-ar naiba !" a unor
preşedinţi retardaţi istoric şi economic), iar şefii ar fi cel puţin la fel de oripilaţi, dacă nu chiar îngroziţi
de mânia populară/proletară.
Aşa că vedem în jurul nostru tot soiul de "ingrediente" menite a face diferenţieri sau, altfel
spus, a identifica fiecare persoană, carte, factură, plată etc. Privind retrospectiv, în relaţia
STUDENŢI_EXAMENE - figura 3.1, paragraful 3.1.1 - fiecare student este identificat de matricolul
său, iar disciplinele au un cod (CodDisc) unic prin care evităm confuzia ce s-ar putea genera prin
folosirea exclusivă a denumirii disciplinei (respectiv, numelui studentului). De asemenea, valoarea
atributului NrFact este unică pentru orice factură emisă de firma noastră şi trimisă unui client (baza
de date VÎNZĂRI). ISBN-ul este un cod unic asociat, la nivel mondial, unei cărţi tipărite, în timp ce
cota identifică într-o bibliotecă un exemplar al unei cărţi. Codul numeric personal ( CNP-ul) este cel
mai bun mod de a identifica o persoană, în timp ce codul fiscal este un element de încredere când ne
propunem să identificăm un client.
Din contră, în paragraful 3.1.4, în dicţionarul bazei de date VÎNZĂRI (tabelul 3.1), am apelat
atribute cu oarecare doză de artificialitate – CodCl (codul fiecărui client), CodPr (codul fircărui
produs), CodDoc (codul documentului) ca să nu mai vorbim de CodÎnc (codul fiecărei încasari).
Aceste atribute sunt exemple de cheie surogat. Dealtminteri, noţiunea de surogat desemnează un
produs artificial sau sintetic care este utilizat drept substitut pentru un produs natural. O cheie surogat
este o cheie artificială sau sintetică utilizată ca substitut al unei chei "naturale".
De cele mai multe ori, cheile surogat simplifică graful dependenţelor funcţionale şi, implicit,
sarcina demarcării relaţiilor. Astfel, revenind la relaţia LINII_ARTICOLE_CONTABILE din figura
3.6 (paragraful 3.3.1), ştiind că o notă contabilă are mai multe operaţiuni care, la rândul lor, conţin mai
mai multe corespondenţe ContDebitor - ContCreditor, introducem două atribute care să preia
explicaţiile/comentariile despre note, respectiv, operaţiuni - ExplicaţiiNotă şi ExplicaţiiOp.
Din paragraful 3.3.1 ştim că, în funcţie de modul de contare, configuraţia dependenţelor poate fi
sensibil diferită. Pentru uniformizarea celor două variante de numerotare a notelor contabile, putem
recurge la două atribute de tip cheie surogat. IdNotăContabilă va fi un număr unic asociat fiecărei
note contabile, iar IdOperaţiune va identifica orice operaţiune, indiferent de nota contabilă din care
face parte. Graful ia configuraţia din figura 3.34.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 71

Figura 3.34. Două chei surogat pentru LINII_ARTICOLE_CONTABILE

Schema obţinută prin decuparea dependenţelor din graf este: NOTE_CONTABILE


{IdNotăContabilă, NrNotă, Data, ExplicaţiiNotă}, OPERAŢIUNI {IdOperaţiune, NrOp,
IdNotăContabilă, ExplicaţiiOp} şi DETALII_OPERAŢIUNI {IdOperaţiune, ContDebi-
tor, ContCreditor, Suma}.

3.7.2 Puncte de vedere privind cheile surogat


Părerile despre cheile surogat sunt, fireşte, împărţite. Există autori care fac distincţia între chei
surogat definite/controlate de utilizatori şi chei surogat gestionate exclusiv de sistem (SGBD). Pentru
Codd, cheile primare definite şi controlate de utilizator folosite ca surogate permanente pentru entităţi,
ar prezenta trei dificultăţi la întrebuinţare40:
Valorile cheilor surogat trebuie uneori schimbate. De exemplu, atunci când două
companii fuzionează, angajaţii trebuie reuniţi într-o sigură tabelă, şi nu este exclus ca,
datorită suprapunerilor, unele mărci să fie modificate.
Două relaţii ar putea avea chei surogat utilizator definite pe domenii diferite, chiar
dacă entităţile pe care le identifică sunt aceleaşi; de exemplu, o firmă poate fi
deopotrivă client şi furnizor al companiei noastre. În tabela FURNIZORI firma ar
putea fi identificată prin CodFurnizor, iar în tabela CLIENŢI prin CodClient. Deşi
firma este aceeaşi, ea este identificată prin două chei surogat diferite.
Uneori este necesară preluarea informaţiei despre o entitate înaintea atribuirii unei
chei surogat utilizator pentru aceasta sau, pe de altă parte, păstrarea valorii cheii
surogat chiar şi după ce entitatea nu mai constituie subiect al aplicaţiei. Cazurile tipice
sunt cele ale candidaţilor pentru angajarea pe un post (li se va atribui o marcă doar
dacă vor fi angajaţi, atribuirea petrecându-se după angajare), sau cele ale angajaţilor
pensionaţi.
Cele trei dificultăţi sunt suficiente lui Codd pentru a justifica folosirea cheilor surogat sistem,
pe care utilizatorul nu le poate nici modifica, iar uneori nici măcar vizualiza. Astfel, fiecare entitate ar
avea propria cheie surogat, unică la nivelul întregii baze de date. Două chei surogat sunt identice dacă
şi numai dacă desemnează aceeaşi entitate. Codd se gândea chiar şi la o comandă specială de fuzionare
(COALESCE) a două chei surogat care desemnează, în fapt, aceeaşi entitate. Mai mult, în articolul său
din 1979 publicat în ACM Transactions on Database Systems, autorul pledează pentru un model
relaţional extins în care, printre multe altele, unul dintre domenii să servească drept sursă pentru toate
cheile surogat din bază.

40
[Codd79]
72 Baze de date I

La întrebarea "Ce e mai preferabil, să folosim cheile primare "naturale" sau cele surogat ?" nu
există un răspuns universal sau, chiar dacă ar exista, am evita să-l dăm. Atunci când atributul/atributele
ce identifică entitatea sunt disponibile şi stabile în timp, soluţia "naturală" este mai la îndemână: CNP-
ul pentru persoane, ISBN-ul pentru cărţi, CodFiscal pentru firme. Când apar probleme de
identificare, iar unicitatea este asigurată de un mare grup de atribute, sau când atributul/atributele
potenţiale chei nu prezintă stabilitate pe termen lung, atunci soluţia "surogat" este cea mai indicată. La
exemplele din acest paragraf, mai putem adăuga:
Seria şi numărul de buletin/carte de identitate - deşi identifică fiecare persoană, peste
un număr de ani, datorită schimbării buletinului/cărţii de identitate, combinaţia îşi
schimbă valoarea, iar dacă s-au făcut deja arhivări, vor apărea complicaţii.
Matricol - în multe şcoli, licee şi universităţi s-a practicat un sistem simplu de
atribuire a numărului matricol, astfel încât, după câţiva ani, un matricol era "reciclat".
Căutarea în baza de date arhivă a liceului ar ridica probleme mari, dacă intervalul care
interesează este de ordinul deceniilor. Necazuri similare ar apărea dacă unele firme ar
recicla, în timp, mărcile angajaţilor, numerele de inventar ale mijloacelor fixe etc.
Asemenea gen de discuţii este valabil însă şi la reciclarea cheilor surogat, aşadar ideea folosirii
unor surogate-sistem se poate dovedi benefică.
Dintre materialele dedicate cheilor surogat, v-am recomanda pe cele scrise de Mike Lonigro41,
Ian Harrington42, Graeme C. Simsion43 şi Fabian Pascal. În ceea ce mă priveşte, nu împărtăşesc nici
lehamitea unor autori faţă de cheile surogat, mergându-se, în acest sens, până la a le "demasca" drept
identificatoare deghizate de obiecte (celebrele OID-uri din orientarea pe obiecte), dar nici frenezia
"surogării" manifestată de mulţi proiectanţi de baze de date, deşi, recunosc, frenezia cu pricina nu are
nimic toxic în ea, ci, mai degrabă, ţine de un soi de lene (subscriu cu toată inima la ideea că lenea nu e
toxică, dacă nu e însoţită de "aditivi").
O altă temere legată de cheile surogat priveşte îngreunarea accesului la informaţiile din baza
de date, întrucât acesta nu se mai realizează prin atribute explicite, ci prin numere destul de irelevante
pentru obiectul/entitatea în cauză. Ori, utilizatorii obişnuiţi ar fi obligaţi să reţină kilograme întregi de
cifre nesuferite, doar pentru a obţine datele de care au nevoie. Nici acest neajuns nu trebuie să
descurajeze, deoarece trebuie să vedem o aplicaţie, indiferent de tipologia sa, pe cel puţin două
straturi, date şi interfaţă. În majoritatea copleşitoare a cazurilor, utizatorii interacţionează cu baza doar
prin meniuri, rapoarte şi mai ales formulare, iar cheile surogat pot fi chiar ascunse.
În orice caz, dacă nu am reuşit să tranşăm discuţia chei naturale-chei surogat, măcar să arătăm
cu degetul pe cele mai toxice: cheile inteligente. Astfel, au existat firme în care angajaţii
compartimentelor personal-salarizare audiaseră sau au citiseră în cursurile de baze de date sau
analiză/proiectare despre teoria codurilor şi, la angajarea unei persoane la compartimentul
Contabilitate, îi atribuiau o marcă de tip CTB85CC101, în care primele trei litere semnalizau că este
încadrat la compartimentul Contabilitate, iar cele două C-uri semnalizau biroul Calculaţia costurilor,
în cadrul compartimentului Contabilitate. Partizanii acestui sistem argumentau că marca ar conţine, în
acest fel, informaţii preţioase (compartimentul şi biroul) despre fiecare angajat, cheia fiind chiar, după
unii autori, inteligentă.

41
[Lonigro98]
42
[Harrington02], pp.77-82
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 73

După cinci ani, însă, persoana respectivă se transferă la compartimentul Financiar. Asta
înseamnă că marca, fiind inteligentă, trebuie schimbată. Costul schimbării depinde de întrebarea: unde
se află cele 5 (ani) * 12 (luni) înregistrări despre calcululul lunar al salariului, plus 5(ani) * 12 (luni) *
20 (zile lucrătoare) înregistrări, dacă pontajul se preia zilnic ? Răspunsul poate fi însoţit de apăsătoare
dureri de cap.
Nu întotdeauna atributele implicate în schema bazei pot fi identificate cu uşurinţă. Mai mult,
deseori nu strică o doză rezonabilă de artificial, de improvizaţie, doză care poate salva pe termen lung
o schemă sau măcar să o scutească de multe necazuri. Deşi mai puţin intuitiv decât modelul obiectual,
relaţionalul are certe atuuri în ceea ce priveşte mecanismul de declarare a restricţiilor şi mai ales în
ceea ce priveşte opţiunile de extragere a informaţiilor din bază (mecanismul de manipulare a datelor).
Chiar şi aşa, rămân în discuţie o mare parte din restricţii care ţin de integritatea bazei de date, dar sunt
legate nu atât de reguli intrinseci modelului (cheie primară, restricţie de entitate, integritate
referenţială), cât de regulile aplicaţiei, sau, altfel spus, reguli ale afacerii. Din păcate, nici un model de
date nu oferă un mecanism infailibil de declarare a tuturor restricţiilor semantice dintr-o bază de date.

3.8 DEPENDENŢE DE INCLUZIUNE


Este greu de explicat motiv pentru care toate cărţile majore care tratează problema
normalizării şi, în general, proiectarea bazelor de date, sunt atât de discrete în materie de dependenţe
de incluziune. Nu-i vorbă că noţiunea ar fi din cale-afară de dificilă, ci mai deagrabă că în aplicaţiile
economice, chiar de calibru mediu, este aproape imposibil să nu se manifeste un asemenea gen de
dependenţă.
Într-o exprimare lejeră, între două atribute X şi Y există o dependenţă de incluziune (DI) dacă
şi numai dacă orice valoare a lui X este obligatoriu şi valoare a lui Y şi se notează simplu X Y. Mai
general, o DI semnifică faptul că proiecţia pe m atribute date ale relaţiei R este un subset al proiecţiei
pe m atribute date din relaţia S. De aici şi caracterizarea DI ca dependenţe interrelaţii44. Definiţia însă
nu obligă ca R şi S să fie neapărat distincte, deci DI se poate institui între atribute şi grupe de atribute
ale aceleaşi relaţii. După Casanova s.a., DI semnalizează relaţiile de tip este-un/o45, iar Sciore este şi
mai limpede când scrie că folosim DI pentru a arăta că două atribute din relaţii diferite se referă la
acelaşi lucru46. Am putea amenda afirmaţia lui Sciore prin precizarea faptului că atributele nu trebuie
neapărat să fie din relaţii diferite.
Cel mai frecvent exemplu de DI este Manager Angajat47, care înseamnă că orice manager
este un angajat al companiei. Graful din figura 3.35 ilustrează o situaţie comună în firmele de
pretutindeni: fiecare angajat al unei întreprinderi este arondat unui compartiment (Producţie,
Marketing, Personal, Financiar, Contabilitate etc.); fiecare compartiment are un singur şef; fiecare şef
este, şi el, angajat, chiar dacă mai uită uneori de lucrul acesta.

43
[Simsion01], pp.282-285
44
[Fagin 81]
45
[Casanova s.a.82]
46
[Sciore83]
47
[Fagin81], [Casanova s.a.82], [Johnson & Klug82], [Sciore83]
74 Baze de date I

Figura 3.35. O dependenţă de incluziune

Dependenţa de joncţiune reprezentată prin săgeata punctată este, deci, MarcăŞef Marcă.
Decuparea relaţiilor din graf nu ridică mari probleme. Avem două surse de DF, prin urmare cele două
relaţii vor fi: PERSONAL {Marcă, NumePren, Adresă, Compartiment} şi COMPARTIMENTE
{Compartiment, MarcăŞef}. Dependenţa de incluziune va fi încorporată în schemă sub forma
restricţiei referenţiale dintre COMPARTIMENTE.MarcăŞef şi PERSONAL.Marcă.

Frunze şi conturi
Ne îndepărtăm de exemplele consacrate, apelând la baza de date CONTABILITATE, pe care,
după discuţia din paragrafele precedente, am adus-o la schema: NOTE_CONTABILE
{IdNotăContabilă, Data, ExplicaţiiNotă}, OPERAŢIUNI {IdOperaţiune,
IdNotăContabilă, ExplicaţiiOp, NrConturiDebitoare, NrConturiCreditoare,
UnContDebitor, UnContCreditor} şi DETALII_OPERAŢIUNI {IdOperaţiune,
ContDebitor, ContCreditor, Suma}. Ei bine, a sosit momentul pentru a introduce una din cele
mai importante restricţii ale unei aplicaţii de contabilitate generală: conturile care apar în operaţiunile
contabile nu trebuie să aibă conturi sintetice/analitice subordonate !
Să începem explicaţiile mai din "amonte". Pentru reflectarea elementelor patrimoniale, a
capitalului, datoriilor şi obligaţiilor, contabilitatea foloseşte conturi organizate în serii, clase şi grupe48.
De exemplu, în seria 1 sunt incluse conturile organice de bilanţ, în seria 2 conturile de procese, în seria
3 conturile de rezultate, în seria 4 conturile în afara bilanţului (extrapatrimoniale), iar în seria 5
conturile privind circuitul contabilităţii manageriale. Seria 1 conţine 7 clase, pentru: capitaluri, active
imobilizate, stocuri, terţi, trezorerie, regularizare şi conturi rectificative. Fiecare clasă are una sau mai
multe serii de conturi. Astfel, clasa Stocuri are şapte grupe: materii şi materiale, obiecte de inventar;
producţie în curs; produse; stocuri la terţi; animale; mărfuri; ambalaje. În fiecare grupă sunt conturile
propriu-zise care pot fi sintetice de ordinul 1 (alcătuite din trei cifre) sau de ordinul 2 (patru cifre).
Fiecare sintetic de ordinul 2 aparţine unui sintetic de ordinul 1. Tabelul 3.5 indică o porţiune din
planul de conturi al firmei X. Orice tip cont sintetic poate fi descompus pe conturi analitice, în funcţie
de specificul şi interesele întreprinderii.
Tabel 3.5. Extras din planul de conturi al unei firme

Simbol cont TipCont Denumire cont

Clasa: Conturi de CAPITALURI


...
101 pasiv Capital social
1011 pasiv Capital social subscris ne-vărsat
1012 pasiv Capital social subscris vărsat
...

48
Vezi, spre exemplu, Horomnea, E. - Bazele contabilităţii. Concepte, aplicaţii, lexicon, Editura Sedcom Libris,
Iaşi, 2004, pp. 178-180
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 75

211 activ Terenuri şi amenajări


2111 activ Terenuri
2112 activ Amenajări
....
301 activ Materii prime
301.01 activ Nisip
301.02 activ Ciment
301.03 activ Var
301.09 activ Alte materii prime
...
49
308 bifuncţional Diferenţe de preţ la materii prime
...
401 pasiv Furnizori
401.01 pasiv Furnizor A
401.02 pasiv Furnizor B
401.99 pasiv Alţi furnizori
...
428 bifuncţional Alte creanţe şi datorii faţă de salariaţi
4281 activ Alte creanţe faţă de salariaţi
4282 pasiv Alte datorii faţă de salariaţi
...
411 activ Clienţi
...
4426 activ TVA deductibilă
4427 pasiv TVA colectată
...
601 activ Cheltuieli cu materii prime
601.01 activ Cheltuieli cu nisipul
601.02 activ Cheltuieli cu cimentul
601.03 activ Cheltuieli cu varul
601.09 activ Cheltuieli cu alte materii prime
...

Astfel, contul 301 - Materii prime este decompus pe patru analitice. Primele trei, 301.01,
301.02 şi 301.03, reprezintă cele mai importante materii prime pentru firmă, iar ultimul, 301.09 le
grupează pe toate celelalte. Mai sunt descompuse pe analitice conturile de furnizori (401) şi cheltuieli
cu materiile prime (600). Interesant că un cont sintetic de gradul I bifuncţional (428) poate avea un
"subordonat" de activ (4281), şi un altul de pasiv (4282) !
Din moment ce am devenit mai riguroşi, este evident că, practic, putem introduce în schemă
atributele SimbolCont, TipCont, DenumireCont, iar între ContDebitor şi SimbolCont, pe de o
parte, şi ContCreditor şi SimbolCont, pe de altă parte, avem de-a face cu o dependenţă de
incluziune. Graful dependenţelor ia forma din figura 3.36.

Figura 3.36. Dependenţele funcţionale şi de incluziune pentru BD CONTABILITATE


76 Baze de date I

Relaţia nouă ce rezultă din graf este evidentă: PLAN_CONTURI {SimbolCont, TipCont,
DenumireCont}, iar cele două dependenţe de incluziune se vor materializa la implementarea bazei în
două restricţii referenţiale (vezi listingul de pe pagina web).

Proiecte vechi şi noi


Am recurs la un exemplu privind proiectele derulate într-o firmă cu ocazia paragrafului 5.4,
pentru a ilustra forma normalizată Boyce-Codd. Acum, însă, ne apropiem de realitatea dintr-o
companie, luând în calcul următoarele elemente:
un angajat, identificat prin Marcă este inclus în schema unui compartiment funcţional
(Contabilitate, Marketing etc.);
orice compartiment are un singur şef (MarcăŞef);
firma, datorită specificului activităţii sale, lucrează pe bază de proiecte; fiecare proiect are un
identificator (IdProiect), demarează la o anumită dată (DatăProiect) şi are un termen de
realizare (Termen);
un proiect este alcătuit din una sau mai multe activităţi ( IdActivitate), o activitate având o
titulatură (DenActivit) şi o scurtă descriere (DescActivit);
orice angajat este capabil să desfăşoare una sau mai multe activităţi;
un angajat poate lucra la mai multe proiecte;
la un proiect sunt angajate una sau mai multe persoane;
activitate poate fi inclusă în unul sau mai multe proiecte;
în cadrul unui proiect, fiecare activitate începe la o anumită dată ( DataÎnceput), se
finalizează la o altă dată (DataFinal) şi constă într-o serie de operaţii, responsabilităţi şi
rezultate (Detalii);
aceeaşi persoană poate desfăşura, în acelaşi proiect, una, două sau mai multe activităţi;
activitate într-un proiect poate fi desfăşurată de mai multe persoane.
Dependenţele corepunzătoare acestor cerinţe pot fi reprezentate sub formă de graf ca în figura 3.37.
În graful de mai sus există o serie de informaţii care nu pot fi furnizate de schema construită
pe baza decupării relaţiilor:
- care sunt activităţile pe care le poate desfăşura o persoană ?
- în ce proiecte a fost implicat un angajat ?
- care sunt persoanele implicate într-un anumit proiect ?
- care este activitatea, sau activităţile, desfăşurate de o anumită persoană într-un proiect dat ?
- care sunt persoanele care au desfăşurat o anumită activitate într-un anumit proiect ?

49
A fost o vreme când la această facultate picai sigur la examele de Contabilitate dacă afirmai că există conturi
bifuncţionale. Acum puteţi pica şi pentru alte motive !
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 77

Figura 3.37. Dependenţele bazei de date PROIECTE

O prim artificiu ar fi introducerea în schema bazei şi alte atribute care să "fixeze" toate
legăturile neprinse în dependenţe. Astfel, un angajat poate fi "legat" de o anumită activitate pe care o
poate desfăşura printr-un atribut nou, Competenţe, care să sugereze abilităţile şi experienţa
angajatului în privinţa activităţii cu pricina: (Marcă, IdActivitate) Competenţe. Pe de altă
parte, participarea unui angajat într-o activitate din cadrul unui proiect poate fi descrisă cu ajutorul a
trei atribute - Atribuţii, Probleme şi Rezultate - care indică ce sarcini a avut angajatul legat de
acea activitate din cadrul proiectului, ce probleme a întâmpinat şi care au fost rezultatele muncii sale.
Dependenţele sunt evidente: (Marcă, IdProiect, IdActivitate) Atribuţii, (Marcă,
IdProiect, IdActivitate) Probleme şi (Marcă, IdProiect, IdActivitate)
Rezultate. Noul graf s-ar prezenta ca în figura 3.38, noile atribute fiind scrise "aplecat" (italic).

Figura 3.38. Noul graf al dependenţelor BD PROIECTE

Relaţiile decupate din graf sunt următoarele: PERSONAL {Marcă, Numepren, Adresă,
Compartiment}, COMPARTIMENTE {Compartiment, MarcăŞef}, ACTIVITĂŢI {IdActivi-
tate, DenActivit, DescrActivit}, COMPETENŢE {Marcă, IdActivitate, Competenţe},
PROIECTE {IdProiect, TitluProiect, MarcăŞefProiect, DatăLansare, Termen}, ACTIVI-
TĂŢI_PROIECTE {IdProiect, IdActivitate, DataÎnceput, DataFinal, Detalii} şi PER-
SOANE_ACTIVITĂŢI_PROIECTE {IdProiect, IdActivitate, Marcă, Atribuţii, Probleme,
Rezultate}.

Alte frunze, dar aceleaşi conturi


Soluţiei la care am ajuns în acest paragraf pentru schema bazei de date CONTABILITATE i
se poate reproşa, printre altele, faptul că, uneori, la inserarea unei linii în PLAN_CONTURI trebuie
78 Baze de date I

modificate alte linii din aceeaşi tabelă, ceea ce nu este posibil în toate SGBD-urile. Iar dacă la inserare
lucrurile mai pot funcţiona, declanşatorul de modificarea are şanse considerabile să se împotmolească.
Pentru a încerca să ajungem la următoarea soluţie, pornim de la dependenţele conţinute în
graful din figura 5.35. Dependenţele de incluziune dintre ContDebitor, pe de o parte, şi
ContCreditor, pe de altă parte, şi SimbolCont sunt discutabile. De fapt, restricţia este ca toate
conturile debitoare şi creditoare din orice înregistrare contabilă să fie elementare (frunze), adică să nu
fie descompuse pe analitice (sau sintetice de ordinul 2). Putem defini, deci, un atribut denumit ContE-
lementar care identifică (printr-o valoarea booleană TRUE sau şir de caractere "Da") un cont
"frunză". Putem vorbi de o "specializare" a conturilor, cele două dependenţe de incluziune iniţiale
fiind acum ContDebitor ContElementar, respectiv ContCreditor ContElementar, la care
se adaugă ContElementar SimbolCont. Spre deosebire de celelate două, ultima DI indică o
specializare. Noul graf al dependenţelor este cel din figura 3.39.

Figura 3.39. Trei dependenţe de incluziune, dintre care una semnifică o specializare

Decupând relaţiile din graf, obţinem: PLAN_CONTURI {SimbolCont, TipCont,


DenumireCont}, CONTURI_ELEMENTARE {ContElementar }, NOTE_CONTABILE {IdNo-
tăContabilă, Data, ExplicaţiiNotă}, OPERAŢIUNI {IdOperaţiune, IdNotăContabilă,
ExplicaţiiOp, NrConturiDebitoare, NrConturiCreditoare, UnContDebitor,
UnContCreditor} şi DETALII_OPERAŢIUNI {IdOperaţiune, ContDebitor, ContCreditor,
Suma}. Lucrurile sunt chiar interesante, deoarece acum restricţiile referenţiale se stabilesc astfel: (a)
între DETALII_OPERAŢIUNI.ContDebitor (copil) şi CONTURI_ELEMENTARE.ContElementar
(părinte); (b) între DETALII_OPERAŢIUNI.ContCreditor (copil) şi CONTURI_ELEMENTA-
RE.ContElementar (părinte); (c) între CONTURI_ELEMENTARE.ContElementar (copil) şi
PLAN_CONTURI.SimbolCont.

3.9 CAZ PRACTIC - CENTRU DE ÎNCHIRIERE CASETE VIDEO


Deşi nu întotdeauna la zi cu legislaţia pentru protecţia drepturilor de autor, centrele de
închiriere de casete video au constituit o afacere tentantă, mai ales în anii '90 şi, am putea spune, îşi are
încă clientela sa, deşi HBO-ul, în general, televiziunea prin cablu constituie un concurent foarte
puternic. Pentru a-l putea diferenţia de concurenţă, vrem ca baza de date a centrului pentru care lucrăm
să ofere şi date care să ajute la fidelizarea clienţilor:
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 79

- informaţii despre filme: genul de film, distribuţie, realizare (scenariu, regie) etc.;
- premiile importante luate de filme: premiul, categoria, anul decernării;
- date despre împrumuturi, restituiri şi întârzieri;
- informaţii statistice, de genul: filmele cele mai cerute, clienţii cei mai fideli, clienţii cu cele mai mari
întârzieri la returnarea casetelor;
- date despre clienţi: zi de naştere/onomastică, genuri preferate şi, implicit, structura pe vârste şi
ocupaţii ale clienţilor;
- un sistem de evaluare (rating, în termeni elevaţi) a filmelor de către clienţi.
Ceea ce am discutat despre baza de date FILMOGRAFIE în capitolele anterioare ne este deci
de mare folos pentru cele ce urmează. În privinţa atributelor surogat, de la început este evident că am
avea nevoie de cel puţin trei:
IdFilm - pentru identificarea fără ambiguitate a unui film;
IdCasetă - un număr unic pentru diferenţierea între ele a casetelor;
IdÎmprumut - număr atribuit fiecărui împrumut de una sau mai multe casete, la un moment
oarecare.
Pentru identificarea clienţilor am putea să considerăm codul numeric personal (CNP-ul) ca
fiind mulţumitor. Din paragraful 3.6.3 ne-au rămas câteva dependenţe memorabile, unele funcţionale:
(1) IdFilm TitluOriginal
(2) IdFilm TitluRO
(3) IdFilm AnLans
(4) DenPremiu LocDecernare
(5) (IdFilm, Rol) Actor
(6) (DenPremiu, Categorie, IdFilm) AnPremiu.
(7) (DenPremiu, Categorie, IdFilm, Actor) AnPremiu
iar altele multi-valorice: IdFilm Producător | Regizor; IdFilm Producător |
Gen sau IdFilm Regizor | Gen. Adăugăm şi scenaristul, aşa că: IdFilm
Producător | Scenarist sau IdFilm Regizor | Gen.
Caseta este identificată de atributul IdCasetă, astfel că putem scrie:
(8) IdCasetă DataCumpărării
(9) IdCasetă ProducătorCasetă
(10) IdCasetă AnProdCasetă
(11) IdCasetă PreţCumpărare
Anticipând o cerere mai mare, un film poate fi compărat în mai multe exemplare, deci pe mai
multe casete: IdFilm / IdCasetă. Nici reciproca nu e prea valabilă, întrucât pe o casetă pot fi
adunate filme de mai mică dimensiune, scurt metraje (doar trei exemple: filmuleţele cu Stan şi Bran,
Chaplin şi Tom şi Jerry): IdCasetă / IdFilm.
Pentru a putea gestiona coerent toate fimele (scurt metraje sau nu) de pe o casetă, apelăm la un
atribut de genul FilmNr, adică numărul de ordine al filmului de pe o casetă (vă amintiţi de atributul
Linie pentru facturi ?). Aşa că:
(12) (IdCasetă, FilmNr) IdFilm
80 Baze de date I

Am zice că problema raportului filme-casete este tranşată. Da' de unde ! Persoanele mai
simţitoare poate-şi amintesc de febra pre-telenovelistă declanşată de un film epopee - Pasărea Spin. Ei
bine, ca şi alte filme-maraton, acesta se întinde pe mai multe casete. Astfel încât, spre exemplu, pe o
casetă poate fi partea a patra a filmului XYZ. Din fericire, dependenţa de mai sus nu este compromisă,
însă am avea nevoie de un atribut adiţional - ParteFilm, iar dependenţa s-ar putea scrie:
(13) (IdCasetă, FilmNr) ParteFilm
Ba chiar putem fi siguri că, pe o casetă, casa producătoare poate să introducă ultima parte a
unui film plus un scurt-metraj de acelaşi regizor sau un medalion actoricesc etc.
Despre clienţi, numai de bine:
(14) CNPClient NumeClient
(15) CNPClient AdresaClient
(16) CNPClient TelefonClient
(17) CNPClient DataNaştereClient
(18) CNPClient NivelStudiiClient
Un client poate împrumuta una sau mai multe casete. Fiecare împrumut este identificat printr-
o cheie surogat - IdÎmprumut, aşa că:
(19) IdÎmprumut DataOraImprumut
(20) IdÎmprumut CNPClient
Deoarece simultan se pot împrumuta mai multe casete, IdÎmprumut / IdCasetă, ceea
ce e destul de neliniştitor, atâta vreme cât obiectivul central al aplicaţiei este de a gestiona
împrumuturile de casete. Fireşte, prima tentanţie este de a recurge la un atribut suplimentar, ca în cazul
liniilor din facturi, atribut căreia îi putem spune chiar NrCrtÎmpr (adică un soi de număr curent al
împrumutului, număr care indică a câta casetă din împrumut este cea curentă), aşa că, cu un pic de
cârpeală, problema s-ar rezolva:
(IdÎmprumut, NrCrtÎmpr) IdCasetă
Dacă adăugăm că restituirea casetelor nu este întotdeauna simultană, adică un client poate
împrumuta trei casete într-o sâmbătă dimineaţă şi să returneze una sâmbătă seara şi celelalte două
duminică, se poate scrie, fie (IdÎmprumut, NrCrtÎmpr) DataOraRestituirii, fie
(IdÎmprumut, IdCasetă) DataOraRestituirii. Eu, unul, aş alege varianta din urmă.
Ei bine, dacă privim mai atent ultima dependenţă, observăm că, la o adică, aceasta ar face de
prisos atributul NrCrtÎmpr. Diferenţa dintre varianta folosirii atributului NrCrtÎmpr (varianta 1) şi
cea fără (a doua) este ilustrată în figura 3.40.

Figura 3.40. Două variante de dependenţe pentru împrumuturi şi restituiri


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 81

Fiecare îşi are avantajele şi dezavantajele sale. Prima pare mai complicată şi, totodată, are un
uşor aer de artificialitate indus de folosirea NrCrtÎmpr. Cel mai important avantaj al său ţine de
faptul că preia foarte bine succesiunea temporală împrumut (prima DF cu sursa compusă) - restituire
(a doua). Consecinţa esenţială este că în schemă nu apar valori nule. Graful din dreapta figurii este
mult mai simplu. Din momentul împrumutului până în cel al restituirii valoarea atributului DataO-
raRestuirii ar fi NULLă. Astfel, pentru a afla în orice moment casetele aflate la clienţi, condiţia în
SQL ar fi DataOraRestuirii IS NULL. Tocmai adversarii folosirii valorilor nule ar dezaproba această a doua
soluţie. Noi, însă, nefiind duşmani (dar nici prieteni) ai nulităţii în bazele de date relaţionale, vom opta
pentru această a doua soluţie, chiar dacă lenea a cântărit mult în alegere. Aşadar:
(21) (IdÎmprumut, IdCasetă) DataOraRestituirii

Să adunăm tot ce-am discutat până acum într-un graf. Figura 3.41 este reprezentare destul de
bună a ansamblului de dependenţe funcţionale (1)-(21), plus cele multivaloare. Este drept, aglomeraţia
este cam mare, dar arta cere sacrificii.

Figura 3.41. Graful DF pentru BD Centru de închiriere - versiunea 1.0

Decupăm relaţiile din graf: PREMII_DENUMIRI {DenPremiu, LocDecernare}, FILME


{IdFilm, TitluOriginal, TitluRO, AnLans}, DISTRIBUŢIE {IdFilm, Rol, Actor}, PRE-
MII_FILME {IdFilm, DenPremiu, Categorie,AnPremiu}, PREMII_INTERPRETARE
{IdFilm, Actor , DenPremiu, Categorie, AnPremiu}, PRODUCĂTORI {IdFilm, Producă-
tor}, REGIZORI {IdFilm, Regizor}, SCENARIŞTI {IdFilm, Scenarist}, FILME_GENURI
{IdFilm, Gen}, CASETE {IdCasetă, DataCumpărării, ProducătorCasetă, AnProdCasetă,
PreţCumpărare}, CASETE_FILME {IdCasetă, FilmNr, IdFilm, ParteFilm}, CLIENŢI
{CNPClient, NumeClient, AdresaClient, TelefonClient, DataNaşteriiClient, Ni-
velStudiiClient}, ÎMPRUMUTURI {IdÎmpr, DataOraÎmpr, CNPClient}, şi CASETE_ÎM-
PRUMUTATE {IdÎmpr, IdCasetă, DataOraRestituirii}

Schema pare a răspunde necesarului informaţional pe care ni-l propusesem iniţial. Astfel,
putem afla: numărul de casete împrumutate de un client pe o anumită perioadă; ponderea comediilor
82 Baze de date I

între filmele închiriate de un client, sau pe tot centrul; ce genuri de filme preferă tinerii între 30 şi 35
de ani cu studii medii etc. Să ne ocupăm însă de regulile scrise sau nescrise ale centrului:
- un client nu poate împrumuta mai mult de patru casete simultan !
- la fiecare 10 casete împrumutate, un client are dreptul la o casetă împrumutată gratuit;
- împrumutul este pentru 48 de ore; la remiterea casetei se calculează o penalizare de 75% din preţul
de închiriere al casetei pentru fiecare zi de întârziere; iată şi tarifele zilnice la închiriere:
50 000 lei pentru casetele produse în anul calendaristic curent şi cel precedent;
40 000 lei pentru casetele produse în urmă cu doi şi trei ani;
30 000 lei pentru restul.
- pierderea sau distrugerea unei casete antrenează o amendă care reprezintă dublul preţului casetei.

Pentru prima regulă se poate introduce un atribut redundant numit NrCaseteNerestituite,


atribut actualizabil la declanşatoarele de inserare, modificare şi ştergere în
CASETE_ÎMPRUMUTATE. Acest atribut ar depinde de CNPClient: CNPClient
NrCaseteNerestituite. Ba chiar am putea exagera şi mai mult, folosind două atribute depen-
dente de CNPClient, unul NrCaseteÎmprumutate şi altul NrCaseteRestituite. Paradoxal,
deşi ideea pare deplasată, există argumente în favoarea sa. Iar, întrucât a doua regulă spune că la
fiecare 10 casete împrumutate, un client are dreptul la o casetă împrumutată gratuit chiar ne convin
ambele atribute, aşa că, în dezaprobarea publicului, ne precipităm şi scriem:
(22) CNPClient NrCaseteÎmprumutate
(23) CNPClient NrCaseteRestituite

A doua regulă e centrului reprezintă o tentativă de a fideliza clienţii, mai ales pe cei depen-
denţi: la 10 casete închiriate, a 11-a este împrumutată gratuit. Noroc de atributul NrCaseteÎmpru-
mutate de mai sus, pentru că, în condiţiile în care acesta este actualizat corect, putem şti în orice
moment dacă se cuvine să acordăm gratuitatea sau nu. Pentru a cunoaşte, totuşi, în timp, câte casete au
fost împrumutate cu titlu gratuit, am putea recurge la un atribut nou, Gratuită, care să ia valorile
„D” (da) sau „N” şi să depindă funcţional astfel: (24) (IdÎmprumut, IdCasetă) Gratuită.

A treia regulă e ceva mai dură: împrumutul este pentru 48 de ore; la remiterea casetei se
calculează o penalizare de 75% din preţul casetei pentru fiecare zi de întârziere. Implementarea sa ar
presupune că, la "de-nulizarea" valorii unui atribut DataOraRestituirii să verificăm dacă
diferenţa CASETE_ÎMPRUMUTATE.DataOraRestituirii - ÎMPRUMUTURI.DataÎmpr este mai
mare de două zile (întrucât cele două atribute sunt de tip DATETIME, adică dată şi oră, diferenţa lor
furnizează, de obicei, numărul de zile dintre cele două momente). Dacă da, atunci calculăm penaliza-
rea după relaţia: Penalizare := 0.75 * (CASETE_ÎMPRUMUTATE.DataOraRestituirii -
ÎMPRUMUTURI.DataÎmpr -2). Este evidentă nevoia de atributul Penalizare care va depinde
funcţional astfel: (25) (IdÎmprumut, IdCasetă) Penalizare. Cu această ocazie, ne
putem propune să rezolvăm o problemă pe care trebuia s-o avem în vedere mai demult, şi anume Cât
trebuie să achite clientul la fiecare împrumut (închirierea a una, două, trei sau patra casete) ? Pentru
acest scop apelăm la atributul ValoareÎnchir (de la valoare închiriere): (26) IdÎmprumut
ValoareÎnchir.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 83

În fine, ultima regulă, potrivit căreia pierderea sau distrugerea unei casete antrenează o
amendă care reprezintă dublul preţului casetei, are o dublă implicaţie: pe de o parte, regretabilul
eveniment trebuie consemnat la (un soi de) restituire, iar atributul Penalizare va conţine acum
dublul contravalorii casetei; pe de altă parte, trebuie să existe cumva în baza de date o indicaţie
precum că respectiva caseta nu mai poate fi închiriată, adică este pierdută sau distrusă. Soluţia ştergerii
înregistrării corespunzătoare din tabela CASETE nu este una prea ingenioasă, întrucât ar trebui să
pierdem toţi "copiii" acestei înregistrări, deci inclusiv de câte ori şi cui a fost împrumutată, şi, astfel,
informaţiile statistice privind filmele, clienţii şi împrumuturile vor fi serios afectate. Cel mai nimerit
pare să recurgem la un atribut numit StareCasetă care să aibă o valoare implicită OK (sau nulă,
deşi nu suntem fani nullişti) şi, dacă este cazul, Pierdută, Distrusă, ba chiar, dacă tot îl avem, putem
consemna şi situaţiile în care caseta a fost scoasă din uz "de moarte bună" (Casată) sau se apropie de
această stare (Uzată): (27) IdCasetă StareCasetă
Nu este însă suficient să cunoaştem starea casetei, ci şi de la ce împrumut i se trage pierderea
sa distrugerea. Aşă că apelăm la un atribut special, StareLaRestituire, care indică halul în care un
client ne-a restituit caseta (să fim înţeleşi: e vorba de halul casetei, nu al clientului !) şi care depinde
funcţional astfel: (28) (IdÎmprumut, IdCasetă) StareLaRestituire.
Ca un alt artificiu propus, ne imaginăm că, în ton cu vremurile, clienţii vor putea să-şi
rezerve/comande casete pe web, iar centrul să aibă un serviciu de furnizare a casetelor la domiciliu (de
tipul pizza delivery) sau, în cel mai rău caz, datorită dimensiunii impresionante a centrului (dă,
Doamne !), vor fi amplasate trei-patru terminale într-un colţ prin care clientul poate să caute filmele
după actori, regizori, subiect etc. Tocmai pentru a evita execuţia unei interogări care să implice
tabelele CASETE, ÎMPRUMUTURI şi CASETE_ÎMPRUMUTATE prin care să se afle dacă o casetă
este disponibilă sau împrumutată la un moment dat, se poate recurge la un alt atribut redundant numit
Disponibilitate, actualizabil automat prin declanşatoarele tabelei CASETE_ÎMPRUMUTATE:
(29) IdCasetă Disponibilitate.
Apropo, ne propusesem şi un mic mecanism de rating al filmelor de către clienţi, deşi, din câte
am văzut pe site-urile româneşti dedicate vânzărilor (legale) de carte şi CD-uri, sistemul nu prea a
prins la noi. La modul cel mai simplu, putem alege o scală de punctare de la 0 la 5, de genul celei
practicate de Laurenţiu Brătan în revista 22, atributul Punctaj fiind "implicat" în dependenţa: (30)
(IdFilm, CNPClient) Punctaj.

Tot frământându-ne mintea cu mecanismul de căutare a informaţiilor despre filme, realizăm


că, la un moment dat, este posibil ca unii clienţi să dorească a viziona filme în care joacă actori
spanioli, sau filme regizate de cineaşti născuţi în perioada 1930-1940 sau site-ul/pagina Web dedicată
unei anumite personalităţi cinematografice. Putem vorbi de generalizare: actorii, regizorii şi scenariştii
sunt toţi cineaşti, ca să nu amintim numeroşi actori care sunt şi regizori, sau regizori ce sunt şi
producători şi scenarişti. Aşa că am putea introduce o serie de atribute pentru identificarea şi
caracterizarea oricărui actor/scenarist/producător/regizor: IdCineast, NumeCineast,
DataNaşterii, DataMorţii, Naţionalitate, PaginăWeb. Fără a schimba numele celor patru
atribute (ar fi fost mai nimerite titulaturile IdScenarist, IdProducător, IdRegizor şi
IdActor), reprezentăm cele patru dependenţe de incluziune Scenarist IdCineast,
Producător IdCineast, Regizor IdCineast, Actor IdCineast.
84 Baze de date I

Punând cap la cap tot ceea ca am discutat despre legăturile semantice dintre atributele bazei de
date, obţinem reprezentarea sub formă de graf din figura 3.42. Pentru un plus de lizibilitate, am grupat
toate destinaţiile surselor IdFilm, IdCineast, IdCasetă şi CNPClient.

Figura 3.42. Graful DF pentru BD Centru de închiriere - versiunea 2.0

Pe baza grafului, schema finală a bazei de date este următoarea: PREMII_DENUMIRI


{DenPremiu, LocDecernare}, FILME {IdFilm, TitluOriginal, TitluRO, AnLans},
CINEAŞTI {IdCineast, NumeCineast, DataNaşterii, DataMorţii, Naţionalitate, Pagi-
năWeb}, DISTRIBUŢIE {IdFilm, Rol, Actor*}, PREMII_FILME {IdFilm, DenPremiu,
Categorie,AnPremiu}, PREMII_INTERPRETARE {IdFilm, Actor* , DenPremiu, Categorie,
AnPremiu}, PRODUCĂTORI {IdFilm, Producător*}, REGIZORI {IdFilm, Regizor*},
SCENARIŞTI {IdFilm, Scenarist*}, FILME_GENURI {IdFilm, Gen}, CASETE {IdCasetă,
DataCumpărării, ProducătorCasetă, AnProdCasetă, PreţCumpărare, StareCasetă,
Disponibilitate}, CASETE_FILME {IdCasetă, FilmNr, IdFilm, ParteFilm}, CLIENŢI
{CNPClient, NumeClient, AdresaClient, TelefonClient, DataNaşteriiClient,
NivelStudiiClient, NrCaseteÎmprumutate, NrCaseteRestituite}, ÎMPRUMUTURI
{IdÎmpr, DataOraÎmpr, CNPClient, ValoareÎnchir}, CASETE_ÎMPRUMUTATE {IdÎmpr,
IdCasetă, DataOraRestituirii, Gratuită, Penalizare, StareLaRestituire }, APRECI-
ERI_FILME {IdFilm, CNPClient, Punctaj}.
Atributele marcate cu asterisc sunt chei străine, părintele comun fiind câmpul IdCineast din
tabela CINEAŞTI.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 85

Capitolul 4

CREAREA ŞI POPULAREA TABELELOR UNEI UNEI BAZE DE


DATE

Obiective:

I. Prezentarea principalelor module şi funcţionalităţi ale unui sistem de


gestiune a bazelor de date
II. Clarificarea tipologiei opţiunilor pentru Limbajele de Definire a
Datelor şi Limbajele de Manipulare a Datelor
III. Caracterizarea atribuţiilor administratorului bazei de date şi altor
categorii de utilizatori
IV. Conturarea principalor segmente ale pieţei SGBD-urilor
V. Enumerarea principalelor tipuri de date gestionabile prin SQL
VI. Prezentarea principalelor clauze SQL pentru declararea restricţiilor
unei baze de date relaţionale
VII. Exemplificarea comenzilor SQL de editare a conţinutului unei tabele:
INSERT, UPDATE, DELETE

Rezultate aşteptate:

A. Deprinderea principalelor clauze ale comenzii CREATE TABLE


pentru crearea tabelelor şi declararea restricţiilor
B. Cunoaşterea modului de editare a tabelelor dintr-o bază de date
relaţională
86 Baze de date I

4.1 SISTEME DE GESTIUNE A BAZELOR DE DATE


Apărute în anii '60, Sistemele de Gestiune a Bazelor de Date (prescurtat SGBD-uri) reprezintă
un ansamblu de programe ce permit utilizatorilor (profesionişti şi neprofesionişti) să interacţioneze cu
o bază de date, în vederea creării, actualizării şi interogării acesteia. SGBD-ul este software-ul care
asigură şi supervizează: introducerea de informaţii în baza de date; actualizarea şi extragerea datelor
din bază; autorizarea şi controlul accesului la date; păstrarea independenţei dintre structura bazei şi
programe50. Obiectivul esenţial al unui SGBD este furnizarea unui mediu eficient, adaptat utilizatorilor
care doresc să consulte sau să actualizeze informaţiile conţinute în bază. Bazele de date sunt concepute
pentru a prelucra un volum mare de informaţii. Gestiunea acestora impune nu numai o structurare
riguroasă a datelor, dar şi o raţionalizare a procedurilor de acces şi prelucrare.
Principalele funcţiuni ale unui SGBD vizează:
- descrierea ansamblului de date la nivel fizic şi conceptual;
- crearea (iniţializarea) şi exploatarea (consultarea şi actualizarea) bazei de date;
- controlul integrităţii bazei;
- confidenţialitatea informaţiilor conţinute în bază;
- accesul simultan al mai multor utilizatori la informaţii;
- securitatea în funcţionare.
- furnizarea unui set de comenzi şi instrucţiuni, necesare atât utilizatorilor pentru consultarea directă a
bazei, prin intermediul unui limbaj de manipulare, cât şi programatorilor, pentru redactarea
programelor de lucru cu baza de date;
- revizia şi restructurarea bazei;
- monitorizarea performanţelor.
Un SGBD prezintă, în general, următoarele module51:
 Gestionarul fişierelor, care se ocupă cu afectarea spaţiilor de memorie pe disc şi cu structurile fizice
de date care servesc la reprezentarea informaţiilor pe suport.
 Gestionarul bazei de date face legătura datelor "fizice" din bază cu aplicaţiile-program de
consultare şi actualizare.
 Procesorul de consultare "traduce" instrucţiunile limbajului de consultare în instrucţiuni elementare,
"inteligibile" pentru gestionarul bazei. Mai mult, acesta optimizează consultarea, pentru a obţine
rezultatele într-un timp cât mai scurt.
 Modulele DML (Data Manipulation Language) realizează conversia instrucţiunilor limbajului de
manipulare a datelor (DML) inserate într-un program de aplicaţie, în proceduri curente ale limbajului-
gazdă, interacţionând cu procesorul de consultare în vederea producerii secvenţelor de cod adecvate.
 Modulele Limbajului de Definire a Datelor - DDL (Data Definition Language) "traduc" (prin
compilare sau interpretare) şi execută instrucţiunile DDL, obţinându-se ansamblul de tabele ce
reprezintă metadatele stocate în dicţionarul de date.
Aceste cinci module interacţionează cu o serie de componente fizice ale bazei:
Fişierele de date reprezintă suportul propriu-zis al bazei.

50
[G. Dodescu s.a. 87], p. 511
51
[Korth&Silberschatz88], pp.17-18
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 87

Dicţionarul de date înmagazinează informaţii relative la structura bazei, fiind solicitat în toate
operaţiunile de consultare şi actualizare.
Indecşi, într-un număr suficient pentru mărirea vitezei de acces la date.
Sintetic, structura unui sistem de lucru cu o bază de date este prezentată în figura 4.152.

Utilizatori finali Utilizatori finali


Programatori Administratorul bazei
curenţi ocazionali

Consultare
Aplicaţii-program Apeluri-sistem Structura bazei
(interogare)

Precompilatorul Compilatorul limbajului


Procesorul de
limbajului de de definire a datelor
consultare
manipulare a datelor

Coduri-obiect ale Gestionarul bazei de date


aplicaţiilor-program

Sistemul de gestiune a bazei de date

Gestionarul fişierelor

Fişier de date
Dicţionar
de
Baza de date date

Figura 4.1 Structura unui sistem de lucru cu o bază de date

Pentru a-şi îndeplini funcţiunile, un SGBD pune la dispoziţia utilizatorilor săi două tipuri de
comenzi/instrucţiuni. Primul modul este destinat lucrului cu schema bazei de date şi, de aceea, în
literatura de profil se foloseşte titulatura DDL (Data Definition Language) – limbaj de definire a
datelor. Al doilea priveşte conţinutul bazei de date, adică inserarea, modificarea şi ştergerea datelor,
precum şi căutarea datelor în bază (cu sau fără prelucrarea lor), motiv pentru care comenzile/instruc-
ţiunile de acest tip sunt referite ca opţiuni DML (Data Manipulation Language) – limbaj de manipulare
a datelor. Multe lucrări desprind din categoria DDL o a treia categorie denumită DCL (Data Control
Language) – limbaj pentru controlul datelor, care priveşte gestiunea utilizatorilor/grupurilor de
utilizatori şi drepturile de acces ale acestora la anumite obiecte ale bazei.

Limbaje de definire a datelor


Arhitectura unei baze de date este specificată printr-o serie de definiţii redactate sub formă de
instrucţiuni scrise în DDL. Execuţia acestor definiţii se materializează într-un ansamblu de tabele care
sunt memorate într-un fişier special, denumit dicţionar (sau repertoar) de date. Un dicţionar de date
conţine deci metadate, adică date relative la alte date, fiind consultat înaintea oricărei citiri sau
modificări a datelor din bază (dicţionarul de date a fost explicat chiar în primul capitol).
Principale funcţiuni ale DDL sunt:
- Descrierea logică a bazei de date şi sub-schemelor proprii fiecărui grup de utilizatori.

52
Preluare din [Korth&Silberschatz88], p.19
88 Baze de date I

- Identificarea schemei, sub-schemelor şi diverselor agregări de date.


- Specificarea fişierelor de date şi a legăturilor logice dintre acestea. Pe baza acestor specificaţii se
poate realiza accesul la date chiar şi în condiţiile co-existenţei mai multor modele de organizare într-o
aceeaşi BD.
- Definirea restricţiilor semantice la care sunt supuse datele. Acestea reprezintă ansamblul valorilor
permise ale fiecărei date, eventual formula de calcul a unei date pe baza valorilor altor date.
- Respectarea acestor restricţii asigură coerenţa bazei.
- Definirea cheilor de acces rapid şi a cheilor confidenţiale (parolelor).
- Definirea metodelor de exploatare a fişierelor ce vor fi utilizate în aplicaţii pentru selectarea
înregistrărilor.
- Definirea procedurilor speciale de criptare, în vederea generării cheilor de acces.
- Definirea modului de indexare sau de localizare a entităţilor.
- Determinarea tipului unei date, în sensul de dată de bază sau derivată (calculată printr-o expresie, pe
baza valorilor altor date).
Dată fiind complexitatea structurilor de memorare şi metodelor de acces la acestea, la nivel
elementar fişierele de date şi dicţionarul de date sunt accesibile numai specialiştilor. În schimb, pentru
descrierea schemei BD, marea majoritate a limbajelor de interogare prezintă comenzi adecvate, ce pot
fi utilizate şi de ne-informaticieni.

Limbaje de manipulare a datelor


Prin manipularea datelor se înţelege efectuarea uneia dintre următoarele operaţiuni:
extragerea unor date din bază (consultare);
scrierea de noi date în bază (adăugare);
ştergerea datelor perimate sau eronate (uneori chiar şi a celor corecte);
modificarea valorii unor date.
Comenzile DML sunt utilizate pentru a prelucra datele în funcţie de structura lor. La nivel
fizic, prin DML se vizează identificarea şi implementarea unor algoritmi performanţi de acces la date,
în timp ce la nivel extern DML trebuie să faciliteze dialogul utilizatorului cu baza în vederea obţinerii
informaţiilor dorite. În mod curent, termenul consultare sau interogare desemnează acţiunea de
căutare şi identificare a datelor necesare, dintre cele aflate în bază. Ansamblul instrucţiunilor DML
pentru căutare şi identificarea datelor constituie limbajul de consultare.

Gestionarul bazei
O bază de date consumă un volum considerabil de memorie, astfel încât poate fi stocată
numai pe disc, memoria internă (RAM) a calculatorului fiind insuficientă (şi volatilă). Programele de
exploatare fac frecvent transferuri de date între memoria internă şi disc. Deoarece viteza de transfer
este mult mai mică, prin comparaţie cu viteza de lucru a procesorului, găsirea unor soluţii, la nivel
fizic, pentru transferul rapid al datelor prezintă o importanţă deosebită.
Pe de altă parte, obiectivul esenţial al bazelor de date este de a facilita exploatarea datelor, în
vederea obţinerii de către utilizatori a informaţiilor necesare. Acest obiectiv priveşte nivelul extern,
utilizatorul nefiind realmente interesat de modul în care informaţia se găseşte fizic pe suportul de
înregistrare.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 89

Rezolvarea acestor probleme cade sub incidenţa gestionarului bazei de date, care este un
modul de programe ce realizează interfaţa dintre datele interne (de pe suport) conţinute în bază şi
programele (sau comenzile) de consultare şi actualizare; principalele sale sarcini pot fi grupate astfel:
Interacţiunea cu gestionarul de fişiere. Datele brute sunt stocate pe disc prin intermediul sistemului
de gestiune a fişierelor, care este o componentă a sistemului de operare al calculatorului. Gestionarul
BD traduce instrucţiunile diverselor DML în instrucţiuni-sistem, la nivel elementar, fiind "responsabil"
şi de buna desfăşurare a operaţiilor de scriere/citire a datelor în/din bază.
Validitatea (corectitudinea) datelor. Datele stocate trebuie să satisfacă anumite restricţii de
integritate, specificate de administratorul bazei. După implementarea mecanismului de integritate,
gestionarul verifică dacă toate actualizările se derulează cu respectarea restricţiilor.
Securitatea datelor. Accesul la date trebuie să fie selectiv, gestionarul bazei fiind cel care asigură
respectarea drepturilor de acces ale fiecărui utilizator.
Salvare şi restaurare. Şi sistemele informatice sunt supuse accidentelor: distrugerea suprafeţei
magnetice, întreruperi în furnizarea energiei electrice, erori ale programelor etc. sunt inerente chiar şi
în ţări super-dezvoltate. În multe cazuri, datelor ce erau în curs de prelucrare în momentul accidentului
se alterează sau se pierd în totalitate. Gestionarul are dificila misiune de a detecta la timp avariile şi de
a restaura datele pierdute în forma şi conţinutul dinaintea accidentului. Aceasta se realizează prin
intermediul unor programe speciale de salvare-restaurare.
Prelucrări simultane (concurente). Întrucât în acelaşi timp pot lucra cu baza doi sau mulţi utilizatori,
trebuie asigurată coerenţa datelor prin afectarea unor nivele de prioritate la nivel de operaţii şi
utilizatori.

Administratorul bazei de date


Din cele prezentate până acum, reiese clar că un sistem de gestiune a bazei de date reprezintă
un ansamblu de programe ce asigură controlul complet al datelor, dar şi al aplicaţiilor care le
exploatează. Administratorul unei baze de date este persoana responsabilă de sistem în ansamblul său.
Rolul acestuia este determinant în câteva activităţi foarte importante.
Definirea arhitecturii bazei de date se realizează prin scrierea definiţiilor care vor fi transformate de
compilatorul DDL într-un ansamblu de tabele stocate permanent în dicţionarul de date.
Definirea modalităţilor în care va fi structurată memoria externă şi a metodelor de acces la date.
Acestea devin operaţionale prin redactarea unor specificaţii scrise ca instrucţiuni ale limbajului de
definire (descriere) a datelor.
Modificarea arhitecturii şi organizării fizice a bazei de date poate fi realizată prin intermediul
instrucţiunilor DDL, obţinându-se astfel actualizările corespondente ale dicţionarului de date.
Autorizarea accesului la date se acordă fiecărui utilizator al bazei de date, administratorul fiind cel
care decide asupra datelor ce pot fi consultate şi actualizate de fiecare utilizator sau grup de utilizatori.
Specificarea restricţiilor de integritate. Aceste restricţii sunt stocate pe disc şi consultate de
gestionarul bazei la fiecare actualizare.
În plus, administratorul bazei de date este cel care: asigură legătura cu utilizatorii; defineşte
procedurile de verificare a drepturilor de acces şi a procedurilor de validare a integrităţii datelor;
defineşte strategia de salvare (înregistrarea copiilor de siguranţă)/restaurare a bazei; monitorizează
performanţele bazei şi o adaptează la modificările ulterioare ale sistemului informaţional.
90 Baze de date I

Utilizatorii bazelor de date


În ciuda complexităţii unui SGBD, tipologia utilizatorilor ce pot utiliza bazele de date este
extrem de generoasă. Cert este că există categorii profesionale (cum ar fi administratorii de baze de
date) care îşi câştigă existenţa exclusiv din „mânuirea” SGBD-urilor, aşa cum există utilizatori (cum ar
fi personalul de la ghişeele băncilor) care nu au nici cel mai mic habar despre baza de date în care îşi
stochează datele şi îşi extrag informaţiile.
Cu riscul de a simplifica lucrurile excesiv, ne putem opri numai la patru categorii de utilizatori
ce pot interacţiona cu sistemul:
- Administratorii BD. După cum am văzut în paragraful precedent, un administrator este un fel de il
capo di tutti capi pentru o bază de date. Trebuie să fie în egală măsură profesionist şi de caracter,
deoarece acces la cele mai intime informaţii stocate în bază.
- Programatorii (dezvoltatorii) de aplicaţii. Sunt informaticienii care interacţionează cu baza de date
prin intermediul instrucţiunilor DML integrate în programe scrise într-un limbaj-gazdă (Cobol, Pascal,
C, Visual Basic, Java etc.) sau în limbajul SGBD-lui respectiv.
- Utilizatori ocazionali, dar cunoscători. Aceştia sunt persoane care interacţionează cu sistemul, nu
prin intermediul programelor de aplicaţii, ci printr-un limbaj de consultare specific bazei (SQL).
Cunoscând porţiuni din schema bazei (fără a avea drepturi de administrare), ei pot lansa interogări prin
care pot obţine ad-hoc informaţii punctuale necesare.
- Utilizatorii curenţi neprofesionişti (neinformaticieni) sunt persoane care efectuează operaţii de rutină
asupra bazei de date, utilizând aplicaţiile disponibile, fără a cunoaşte schema bazei şi fără a pricepe
prea multe din tehnologia bazelor de date (aşa cum sunteţi dvs. acum !).

4.2 PIAŢA ACTUALĂ A SGBD-urilor


Deşi IBM a fost prima care a iniţial un proiect destinat elaborării unui SGBD relaţional
(System/R, începând cu 1974), prima firmă care a lansat primul SGBDR comercial a fost Relational
Software Inc., astăzi Oracle. Infiinţarea firmei a avut loc în 1977, iar lansarea produsului în 1979.
Impunerea pe piaţă a SGBD-urilor grefate pe modelul relaţional a fost mult mai dificilă decât s-ar
putea înţelege astăzi. Abia în deceniul 9, datorită vitezei ameliorate, securităţii sporite şi mai ales
productivităţii dezvoltatorilor de aplicaţii, modelul relaţionat a câştigat supremaţia. În ţara noastră cel
mai utilizat SGBD al deceniului 9 a fost FoxPro, deşi piaţa sa mondială se diminuase încă de la
mijlocul anilor `90.
Există o largă tipologie a SGBDR-urilor, aşa încât orice categorisire îşi are riscul său. Cele
mai accesibile şi, implicit, cele mai utilizate sunt SGBD-urile dedicate iniţial uzului individual:
Access, Paradox, Visual FoxPro. Astăzi, multe dintre acestea au caracteristici profesionale şi pot fi
folosite la dezvoltarea aplicaţiilor pentru accesul simultan a zeci de utilizatori.
Pentru aplicaţiile complexe din bănci, corporaţii, organizaţii şi instituţii de mari dimensiuni s-
au impus SGBDR-urile de "categoria grea": Oracle, DB2 (IBM), Informix (IBM), Sysbase, SQL
Server (Microsoft). Acestea sunt mult mai robuste, mult mai fiabile, dar şi costisitoare.
În ultimul timp şi-au făcut apariţia, ca alternative la marii coloşi, aşa zisele Free-DBMS-uri,
precum PostgreSQL, MySQL, mSQL etc. Acestea rulează, de obicei, pe sisteme de operare de tip
Linux (foarte ieftine) şi se întrevăd ca adversari serioşi ai marilor producători. Se pare că cel mai mare
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 91

segment de piaţă din categoria SGBD-urilor Open-Source îl are MySQL, deşi în materie de
funcţionalităţi SQL şi proceduri stocate, acest produs a fost, până de curând, foarte slab.

4.3 CREAREA TABELELOR IN LIMBAJUL SQL


Până în acest moment al cursului, discuţiile au fost strict teoretice, urmărind familiarizarea cu
noţiunile fundamentale ale bazelor de date. Din acest paragraf (nu chiar imediat, ci câteva pagini mai
încolo), vom trece şi la chestiuni aplicative, ce pot fi lansate în execuţia de la consola unui SGBD. Şi
tot din acest paragraf, vom începe să gravităm în jurul celui mai important limbaj din lumea bazelor de
date, şi anume SQL.

4.3.1. Prezentare generală a limbajului SQL (partea I)


SQL (de la Structured Query Language) a devenit, de o bună bucată de vreme, limbajul-cheie
în lumea bazelor de date. Nu există astăzi lucrări sau publicaţii în domeniul SGBD-urilor care să nu
prezinte mecanismul de lucru al acestui limbaj sau ultimele produse/tendinţe din lumea SQL. Impactul
său este profund. Devenit un soi de esperando al limbajelor pentru baze de date, SQL are toate şansele
de a nu cădea în desuetitudinea esperando-ului propriu-zis, aceasta deoarece a fost altoit pe toate
tipurile de SGBD-uri, de la cele dedicate microcalculatoarelor (PC), la cele care operează în medii
distribuite/eterogene/client-server. SQL este, pe de o parte, unul dintre "responsabilii" noii aproprieri a
non-informaticianului de datele sale, iar, pe de altă parte, pentru profesionişti, reprezintă nucleul
dezvoltării aplicaţiilor ce utilizează bazele de date.
Deşi este referit, în primul rând, ca un limbaj de interogare, SQL este mult mai mult decât un
instrument de consultare a bazelor de date, deoarece permite, în egală măsură:
Definirea datelor
Consultarea BD
Manipularea datelor din bază
Controlul accesului
Partajarea bazei între mai mulţi utilizatori ai acesteia
Menţinerea integrităţii BD.
Astfel că principale comenzi SQL pot fi rezumate ca în tabelul 4.1.

Tabel 4.1 Cele mai importante comenzi SQL

Comandă Scop
Pentru manipularea datelor
SELECT Extragerea datelor din bază
INSERT Adăugarea de noi linii într-o tabelă
DELETE Ştegerea de linii dintr-o tabelă
UPDATE Modificarea valorilor unor atribute

Pentru definirea bazei de date


CREATE TABLE Adăugarea unei noi tabele în baza de date
DROP TABLE Ştergerea unei tabele din bază
ALTER TABLE Modificarea structurii unei tabele
CREATE VIEW Crearea unei tabele virtuale
DROP VIEW Ştergerea unei tabele virtuale
92 Baze de date I

Pentru controlul accesului la BD


GRANT Acordarea unor drepturi pentru utilizatori
REVOKE Revocarea unor drepturi pentru utilizatori

Pentru controlul tranzacţiilor


COMMIT Marchează sfârşitul unei tranzacţii
ROLLBACK Abandonează tranzacţia în curs

4.3.2. Tipuri de date SQL


La crearea efectivă a oricărei tabele, pentru fiecare atribut, pe lângă nume şi eventuale
restricţii trebuie obligatoriu declarat tipul acestuia – dacă este un număr, un şir de caracterre, o dată
calendaristică etc. Deşi există unele diferenţe semnificative între dialectele SQL, următoarele categorii
de tipuri de date sunt cvasi-prezente.
- SMALLINT: întregi - scurte (4 poziţii, reprezentate pe 16 biţi),
- INTEGER sau INT: întregi (9 poziţii, 32 biţi),
- NUMERIC(p,s) sau DECIMAL(p,s) sau DEC(p,s) - reale, cu un total de p poziţii, din care s la partea
fracţionară ,
- FLOAT: reale, virgulă mobilă (20 poziţii ptr. mantisă),
- REAL: real, virgulă mobilă (cu precizie mai mică decât FLOAT, dar la nivelul de intrare este
identic),
- DOUBLE PRECISION: reale, virgulă mobilă, dublă precizie (30 de poziţii ptr. mantisă),
- CHAR(n) sau CHARACTER(n): şir de caractere de lungime n (max. 240),
- VARCHAR(n) sau CHAR VARYING(n) sau CHARACTER VARYING(n): şir de caractere de
lungime variabilă (max. 254),
- DATE: dată calendaristică,
- TIME: ora etc.,
- TIMESTAMP: an, lună, zi, ora, minutul, secunda, plus o fracţiune zecimală dintr-o secundă.
Joe Celko tratează riguros fiecare categorie de dată, aşa cum este prezentă în SQL-9253. În
ceea ce priveşte datele temporale, el operează o delimitare între:
evenimente, pentru care există tipurile DATE, TIME, TIMESTAMP,
intervale – perioade de timp dintre două evenimente (momente) - tipul INTERVAL (YEAR,
MONTH, DAY, HOUR, MINUTE, SECOND) şi
perioade – sunt intervale cu un moment fix de început, pentru care este necesară o combinaţie
de două câmpuri, fie TIMESTAMP - TIMESTAMP, fie TIMESTAMP - INTERVAL.

4.3.3. Crearea tabelelor: CREATE TABLE


Crearea unei baze de date are o componentă tehnică pronunţată, mai ales în cazul serverelor de
baze de date: Oracle, DB/2, SQL Server, Informix, PostgreSQL etc. Chiar dacă există instrumente de
admistrare care uşurează considerabil această activitate, crearea unei baze de date necesită cunoştinţe
de administrare, strict legate de produsul software de gestiune a bazei.
În cele ce urmează, vom eluda elementele tehnice/fizice (spaţiile-tabelă, segmentele de
rollback, fişierele de date, procesele sistem etc.), lăsându-le în seama administratorilor BD; vom

53
[Celko99]
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 93

considera baza de date creată din punct de vedere fizic, astfel încât ne interesează numai crearea
tabelelor, modificarea structurii lor şi declararea restricţiilor.
Comanda SQL utilizată pentru crearea unei tabele este CREATE TABLE. Precizăm că este
vorba de crearea structurii tabelei. Popularea cu înregistrări şi actualizarea ulterioară se realizează prin
alte comenzi: INSERT, UPDATE, DELETE. Tabelele sunt cele din baza de date VÎNZĂRI. Pentru
crearea unei tabele şi declararea atributelor acesteia, fără nici o referire la restricţii, comanda CREATE
TABLE are, în cazul tabelei JUDETE, următoarea formă:
CREATE TABLE JUDETE
(Jud CHAR(2),
Judet VARCHAR(25),
Regiune VARCHAR (15))
Întrucât atributele acestei tabele sunt exclusiv de tip şir de caractere, s-au folosit opţiunile CHAR şi
VARCHAR. CHAR este preferat pentru atributul Jud, deoarece indicativul auto al judeţului este
format exclusiv din două litere (cu excepţia Bucureştiului). Întrucât denumirile judeţului şi regiunii au
lungime variabilă, pentru aceste atribute a fost preferat tipul VARCHAR. Pentru ilustrarea şi altor
două tipuri de date, prezentăm comanda de crearea a tabelei FACTURI:
CREATE TABLE FACTURI ( NrFact NUMERIC(8), DataFact DATE, CodCl DECIMAL(6), Obs VARCHAR(50) )
NrFact şi CodCl sunt de tip numeric, în timp ce DataFact de tip dată calendaristică. Se mai cuvine
de adăugat că unele atribute pot fi iniţializate cu o valoare implicită la adăugarea unei linii în tabela
respectivă. Clauza este DEFAULT:
CREATE TABLE FACTURI ( NrFact NUMERIC(8), DataFact DATE DEFAULT CURRENT_DATE,
CodCl DECIMAL(6) DEFAULT 1001, Obs VARCHAR(50) )
Ca urmare a clauzei DEFAULT, în orice linie adăugată în tabela FACTURI, valoarea
implicită (dacă nu este specificată în comanda INSERT) a DataFact va fi data sistemului (data
curentă), iar CodCl se va iniţializa cu valoarea 1001.

4.3.4. Declararea restricţiilor


În SQL se pot declara toate tipurile de restricţii ale unei BDR: valori obligatorii (nenule),
unicitate, restricţii referenţiale şi restricţii utilizator.

Valori nenule
Unele atribute, obligatoriu cele din cheia primară, nu pot prezenta valori nule. Pentru aceasta,
la crearea tabelei şi declararea atributului se foloseşte opţiunea NOT NULL:
CREATE TABLE FACTURI (
NrFact NUMERIC(8) NOT NULL, DataFact DATE DEFAULT CURRENT_DATE NOT NULL,
CodCl DECIMAL(6) DEFAULT 1001 NOT NULL, Obs VARCHAR(50) )

Cheie primară/unicitate
Cheia primară a unei relaţii este definită prin clauza PRIMARY KEY, plasată fie imediat,
după atributul cheie, fie după descrierea ultimului atribut al tabelei. A doua variantă este întrebuinţată
cu precădere atunci când cheia primară a tabelei este compusă. Pentru atributele de tip cheie candidat
se poate folosi clauza UNIQUE care va asigura respectarea unicităţii valorilor. Iată câteva variante de
folosire a clauzelor PRIMARY KEY şi UNIQUE:
CREATE TABLE FACTURI (
94 Baze de date I

NrFact NUMERIC(8) PRIMARY KEY, DataFact DATE DEFAULT CURRENT_DATE NOT NULL,
CodCl DECIMAL(6) DEFAULT 1001 NOT NULL, Obs VARCHAR(50) ) ;
CREATE TABLE JUDETE (
Jud CHAR(2) PRIMARY KEY, Judet VARCHAR(25) NOT NULL UNIQUE, Regiune VARCHAR (15) ) ;
CREATE TABLE LINIIFACT (
NrFact NUMERIC(8) NOT NULL, Linie SMALLINT NOT NULL, CodPr NUMERIC(6) NOT NULL,
Cantitate NUMERIC(10) NOT NULL, PretUnit NUMBER (12),
PRIMARY KEY (NrFact, Linie), UNIQUE (NrFact, CodPr) ) ;
Pentru tabela LINIIFACT cheia primară este combinaţia de atribute (NrFact, Linie); restricţia de
unicitate pentru cuplul (NrFact, CodPr) înseamnă că se interzice ca, pe o factură, un produs să apară
repetat.

Restricţii referenţiale
Declarea restricţiilor referenţiale se realizează utilizând clauza FOREIGN KEY. Astfel, pentru
stabilirea legăturii LINIIFACT-FACTURI comanda de creare are forma:
CREATE TABLE LINIIFACT (
NrFact NUMERIC(8) NOT NULL, Linie SMALLINT NOT NULL, CodPr NUMERIC(6) NOT NULL,
Cantitate NUMERIC(10) NOT NULL, PretUnit NUMBER (12),
PRIMARY KEY (NrFact, Linie),
UNIQUE (NrFact, CodPr),
FOREIGN KEY NrFact REFERENCES FACTURI(NrFact),
FOREIGN KEY CodPr REFERENCES PRODUSE(CodPr) )
Este drept, şi următoarea variantă este corectă:
CREATE TABLE LINIIFACT (
NrFact NUMERIC(8) NOT NULL REFERENCES FACTURI(NrFact),
Linie SMALLINT NOT NULL,
CodPr NUMERIC(6) NOT NULL REFERENCES PRODUSE(CodPr),
Cantitate NUMERIC(10) NOT NULL, PretUnit NUMBER (12),
PRIMARY KEY (NrFact, Linie), UNIQUE (NrFact, CodPr) )
În SQL92 se poate specifica şi modul în care va fi păstrată integritatea bazei de date la
ştergerea unei linii-părinte sau modificarea unei chei primare ce prezintă înregistrări-copil. Pentru a
interzice ştergerea unor facturi (înregistrări din FACTURI) pentru care există tupluri corespondente în
LINIIFACT şi, pe de altă parte, pentru ca la modificarea unui număr de factură (NrFact) în FACTURI
să se modifice automat, în cascadă, toate liniile-copil din LINIIFACT, forma comenzii se schimbă în:
CREATE TABLE LINIIFACT ( NrFact NUMERIC(8) NOT NULL, Linie SMALLINT NOT NULL,
CodPr NUMERIC(6) NOT NULL, Cantitate NUMERIC(10) NOT NULL, PretUnit NUMBER (12),
PRIMARY KEY (NrFact, Linie), UNIQUE (NrFact, CodPr),
FOREIGN KEY NrFact REFERENCES FACTURI(NrFact)
ON DELETE RESTRICT ON UPDATE CASCADE,
FOREIGN KEY CodPr REFERENCES PRODUSE(CodPr)
ON DELETE RESTRICT ON UPDATE CASCADE )

Restricţii-utilizator
În SGBD-urile actuale, restricţiile-utilizator, denumite şi restricţii de comportament, sunt
implementate sub forma regulilor de validare la nivel de câmp (field validation rule), la nivel de
înregistrare (record validation rule) sau, eventual, pot fi incluse în declanşatoare (triggere). Cu rezerva
că aceste reguli pot avea forme complexe şi întrebuinţa elemente avansate de SQL şi/sau extensiile
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 95

procedurale ale SQL în mediul respectiv, prezentăm în continuare o regulă de validare pentru atributul
DataFact în tabela FACTURI şi o alta pentru atributul Linie în LINIIFACT.
CREATE TABLE FACTURI (
NrFact NUMERIC(8)
CONSTRAINT nn_facturi_nrfact NOT NULL
CONSTRAINT pk_facturi PRIMARY KEY,
DataFact DATE DEFAULT CURRENT_DATE
CONSTRAINT nn_datafact NOT NULL
CONSTRAINT ck_datafact CHECK (DataFact >= '01/08/2005' AND DataFact <= '31/12/2010'),
CodCl DECIMAL(6) DEFAULT 1001
CONSTRAINT nn_facturi_codcl NOT NULL
CONSTRAINT fk_facturi_clienti REFERENCES CLIENTI(CodCl)
ON DELETE RESTRICT ON UPDATE CASCADE,
Obs VARCHAR(50) ) ;
CREATE TABLE LINIIFACT (
NrFact NUMERIC(8) CONSTRAINT nn_liniifact_nrfact NOT NULL,
Linie SMALLINT CONSTRAINT nn_linie NOT NULL CONSTRAINT ck_linie CHECK (Linie > 0),
CodPr NUMERIC(6) CONSTRAINT nn_liniifact_codpr NOT NULL,
Cantitate NUMERIC(10) CONSTRAINT nn_cantitate NOT NULL,
PretUnit NUMERIC (12) CONSTRAINT nn_pretunit NOT NULL,
CONSTRAINT pk_liniifact PRIMARY KEY (NrFact, Linie),
CONSTRAINT un_liniifact UNIQUE (NrFact, CodPr),
CONSTRAINT fk_liniifact_facturi FOREIGN KEY NrFact REFERENCES FACTURI(NrFact)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_liniifact_produse FOREIGN KEY CodPr REFERENCES PRODUSE(CodPr)
ON DELETE RESTRICT ON UPDATE CASCADE )

Ca urmare a clauzei CHECK, data unei facturi nu poate avea valori în afara intervalului 1
august 2005 – 31 decembrie 2010, iar atributul Linie trebuie să prezinte valori întregi mai mari ca 0.
Ca un alt element de noutate, fiecărei restricţii (cheie primară, unicitate, regulă la nivel de
atribut etc.) i s-a dat un nume, lucru util atunci când, la un moment dat (sălvari, restaurări, încarcarea
BD), vrem sa dezactivăm una sau mai multe dintre acestea. Pentru a uşura lucrul, se prefixează numele
fiecărei restricţii cu tipul său:
pk_ (PRIMARY KEY) pentru cheile primare
un_ (UNIQUE) pentru cheile alternative
nn_ (NOT NULL) pentru atributele obligatorii (ce nu pot avea valori nule)
ck_ (CHECK) pentru reguli de validare la nivel de atribut
fk_ (FOREIGN KEY) pentru cheile străine.

Pe portalul FEAA, la pagina disciplinei, este disponibil listingul cu toate comenzile pentru
crearea tabelelor din baza de date VÎNZĂRI.

4.3.5. Modificarea structurii unei tabele


Odată creată o tabelă, schema sa rămâne constantă. Dacă totuşi, ulterior, se doreşte introducerea,
modificarea sau ştergerea unor atribute, sau declararea/anularea unor restricţii, comanda necesată este ALTER
TABLE. Câteva exemple:
96 Baze de date I

- Adăugarea atributului DataNast (data naşterii) în tabela PERSOANE se realizează astfel:


ALTER TABLE PERSOANE ADD DataNast DATE
- Ştergerea unui atribut: ALTER TABLE PERSOANE DROP COLUMN DataNast
- Modificarea tipului/lungimii unui atribut: ALTER TABLE PERSOANE MODIFY (Nume VARCHAR(21))
- Adăugarea/modificarea valorii implicite: ALTER TABLE PERSOANE MODIFY (Sex DEFAULT 'F')
- Anularea unei valori implicite: ALTER TABLE PERSOANE MODIFY (Sex DEFAULT NULL)
- Instituirea restricţiei de nenulitate: ALTER TABLE PERSOANE MODIFY (Sex NOT NULL)
- Invers, acceptarea valorilor NULL: ALTER TABLE PERSOANE MODIFY (Sex NULL)
Toate restricţiile: cheie primară - PRIMARY KEY, unicitate - UNIQUE, referenţială -
FOREIGN KEY, de comportament - CHECK pot fi declarate ulterior creării tabelei şi, bineînţeles,
anulate la un moment dat. Spre exemplu, formatul general al comenzii pentru dezactivarea cheii
primare este: ALTER TABLE PERSOANE DROP PRIMARY KEY. Pentru reinstituirea cheii primare a
tabelei PERSOANE comanda are forma: ALTER TABLE PERSOANE ADD PRIMARY KEY (CNP).

4.3.6. Ştergerea tabelelor


Orice tabelă creată poate fi ştearsă prin comanda DROP TABLE. Dacă, însă, tabela este ună
părinte (adică are copii), ştergerea trebuie făcută împreună cu toţi copii (DELETE CASCADE),
altminteri SGBD-ul se va împotrivi (va afişa un mesaj de eroare).

4.4 COMENZI PENTRU MODIFICAREA CONŢINUTULUI


SQL prezintă comenzi dedicate modificării conţinutului unei tabele, înţelegând prin aceasta
trei acţiuni prin care se actualizează baza:
a) adăugarea de noi linii la cele existente în tabelă,
b) ştergerea unor linii,
c) modificarea valorii unui atribut.
Comanda SQL de adăugare de noi linii este INSERT. Exemple:
INSERT INTO judete VALUES ('IS', 'Iasi', 'Moldova') ;
INSERT INTO coduri_postale VALUES ('700505', 'Iasi', 'IS') ;
INSERT INTO clienti VALUES (1001, 'Client 1 SRL', 'R1001', 'Tranzitiei, 13 bis', '700505', NULL) ;
INSERT INTO persoane VALUES ('CNP1', 'Ioan', 'Vasile', 'I.L.Caragiale, 22', 'B', '700505', '123456', '987654',
'094222222', NULL) ;
INSERT INTO persclienti VALUES ('CNP1', 1001, 'Director general');
INSERT INTO produse VALUES (1, 'Produs 1','buc', 'Tigari', .19) ;
INSERT INTO facturi (nrfact, datafact, codcl) VALUES (1111, TO_DATE('01/08/2005','DD/MM/YYYY'), 1001);
VALUES (1121, TO_DATE('07/08/2005','DD/MM/YYYY'), 1001);
INSERT INTO liniifact (nrfact, linie, codpr, cantitate, pretunit) VALUES (1111, 1, 1, 50, 1000) ;
INSERT INTO liniifact (nrfact, linie, codpr, cantitate, pretunit) VALUES (1111, 2, 2, 75, 1050) ;
INSERT INTO incasari VALUES (1234, TO_DATE('15/08/2005','DD/MM/YYYY'),
'OP', '111', TO_DATE('10/08/2005','DD/MM/YYYY')) ;
INSERT INTO incasfact VALUES (1234, 1111, 53996) ;
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 97

Ordinea valorilor din clauza VALUES trebuie să fie identică cu cea declarată la crearea (sau
modificarea structurii) tabelelor. Modificarea ordinii este posibilă numai prin enumerarea, după
numele tabelei, a atributelor ce vor primi valorile specificate.
Pentru tabelele FACTURI şi LINIIFACT au fost incluse în clauza VALUES mai puţine valori
decât atributele tabelei. Este obligatorie, în aceste cazuri, precizarea atributelor care vor primi valorile.
Restul, adică cele nespecificate, vor avea, pe liniile respective, valori NULL. Funcţia TO_DATE
(valabilă în PostgreSQL şi Oracle) ajută la specificarea datelor calendaristice (DD/MM/YYY
înseamnă că două primele două poziţii desemnează ziua, următoarele două de după bara oblică luna,
iar ultimele patru anul).

Comanda SQL pentru ştergerea uneia sau mai multor linii dintr-o tabelă este DELETE, al
cărei formatul general este: DELETE FROM nume-tabelă WHERE predicat. Din tabelă vor fi şterse
toate liniile care îndeplinesc condiţia specificată în predicatul din clauza WHERE:.
- DELETE FROM FACTURI WHERE NrFact = 1122 elimină din tabela facturi toate liniile în care valoarea
atributului NrFact este egală cu 1122, altfel spus, din tabela FACTURI este ştearsă linia care se referă la factura
1122. Când pentru această factură există înregistrări copil în LINIIFACT sau INCASFACT, SGBD-ul ţine cont
de opţiunea declarată la crearea tabelei. Dacă s-a specificat (implicit sau explicit) ON DELETE RESTRICT,
ştergerea este interzisă. În cazul ON DELETE CASCADE sunt şterse, odată cu linia din FACTURI, toate liniile
copil din LINIIFACT şi INCASFACT.
- DELETE FROM JUDETE WHERE Regiune = „Moldova‟ elimină din baza de date toate judeţele din
Moldova (şterge toate liniile tabelei JUDETE în care valoarea atributului Regiune este Moldova).

Pentru a modifica valoarea unuia sau mai multor atribute pe una sau mai multe linii dintr-o
tabelă se foloseşte comanda UPDATE cu formatul general (simplificat): UPDATE tabelă SET atribut1
= expresie1 [, atribut2= expresie2 ….] WHERE predicat. Modificarea se va produce pe toate liniile
tabelei care îndeplinesc condiţia formulată prin predicat. Exemple:
- Noul număr de telefon al clientului ce are codul 1001 este 0232-313131: UPDATE CLIENTI SET
Telefon = „0232-313131‟ WHERE CodCl = 1001
- În cadrul unei noi relaxări fiscale, se decide creşterea procentului TVA de la 19% la 22% pentru
toate produsele. Atributul modificat este ProcTVA din tabela PRODUSE, pe toate liniile: UPDATE
PRODUSE SET ProcTVA = .22

Pe portalalul FEAA, la pagina disciplinei, este disponibil listingul cu toate comenzile pentru
popularea cu înregistrări a tabelelor din baza de date VÎNZĂRI.
98 Baze de date I

Capitolul 5

ALGEBRA RELAŢIONALĂ

Obiective:

I. Definirea unui limbaj de interogare


II. Explicarea operatorilor ansamblişti (reuniune, intersecţie, diferenţă,
produs cartezia) şi relaţionali (selecţie, proiecţie, joncţiune, diviziune)
III. Înlănţuirea operatorilor pentru a obţine soluţii la probleme de
interogare a bazei de date

Rezultate aşteptate:

Deprinderea redactării de interogări în algebra relaţională


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 99

În capitolul 2 am văzut că modelul relaţional formulat de Codd are la bază trei elemente:
structuri, operaţii şi reguli de integritate. Pentru exprimarea operaţiilor aplicabile structurilor de date
prezentul capitol prezentă algebra relaţională, limbaj teoretic bazat pe teoria ansamblurilor (seturilor).
Chiar dacă are cusurul de a fi teoretic, algebra relaţională poate constitui un bun punct de plecare în
înţelegerea chestiunilor esenţiale ale celui mai important limbaj dedicat bazelor de date – SQL.

5.1 GENERALITĂŢI DESPRE LIMBAJELE DE INTEROGARE


Odată cu apariţia bazelor de date, s-a încercat elaborarea fudamentelor teoretice ale unor
limbaje care să prelucreze conţinutul acestora. Pentru modelul relaţional, au fost propuse atât limbaje
ansambliste (Alpha, algebra relaţională, SQL), cât şi predicative (calcul relaţional, QBE), atât teoretice
(algebra relaţională, calcul relaţional), cât şi aplicative (SQL, QBE, Que). Noi vom zăbovi doar asupra
câtorva elemente care ar trebui să ajute la înţelegerea modalităţilor în care sunt extrase informaţiile
dintr-o bază de date relaţională.
Aşa cum algebra “obişnuită” aplică operatori, cum fi adunarea, scăderea etc., asupra unor
numere, rezultatul operatorilor fiind tot un număr, trebuie spus că:
- operatorii relaţionali se aplică relaţiilor luate în întregime, adică tuturor tuplurilor care alcătuiesc
relaţiile respective;
- rezultatul fiecărui operator (rezultatul consultării) este o nouă relaţie ce poate servi ca argument într-
o altă consultare s.a.m.d.;
- logica operatorilor se bazează pe valorile atributelor, ceea ce constituie dealtminteri suportul
singurului mod de acces în BD. Întrucât consultările mono şi multi-relaţii sunt efectuate exclusiv prin
compararea valorilor atributelor (definite pe domenii compatibile), accesul total independent de limbaj
este asigurat.
Limbajul algebric relaţional cuprinde două tipuri de operatori: ansamblişti - REUNIUNE,
INTERSECŢIE, DIFERENŢĂ, PRODUS CARTEZIAN - şi relaţionali - SELECŢIE, PROIECŢIE,
JONCŢIUNE şi DIVIZIUNE. O altă clasificare distinge operatorii fundamentali, ireductibili
(reuniunea, diferenţa, produsul cartezian, selecţia, proiecţia) de cei derivaţi, a căror funcţionalitate
poate fi realizată prin combinarea operatorilor fundamentali (intersecţia, joncţiunea, diviziunea).
Pentru cele ce urmează se notează cu: t sau r, un tuplu al unei relaţii (linie a unei tabele) şi t(A), un
sub-tuplu al relaţiei R, relativ la atributul A (valoarea atributului A în linia t).
Algebra, ca şi calculul relaţional, serveşte ca punct de referinţă în caracterizarea unui limbaj ca
fiind complet sau incomplet, din punct de vedere relaţional. Dacă un limbaj permite exprimarea tuturor
operatorilor enumeraţi mai sus şi oferă cel puţin facilităţile algebrei relaţionale, se spune că acesta este
un limbaj relaţional complet54.

5.2 OPERATORII ANSAMBLIŞTI


Trei dintre operatorii ansamblişti - reuniunea (" "), intersecţia (" ") şi diferenţa (“-“) - pot
opera numai cu două relaţii uni-compatibile. Fie R1 ( A1, A2, ..., An,) şi R2 (B1, B2, ..., Bm) două

54
[Saleh94], p.35
100 Baze de date I

relaţii. Se spune despre R1 şi R2 că sunt unicompatibile dacă: (a) n = m; (b) i {1,2, ..., n}, Ai şi Bi
sunt de acelaşi tip sintactic (aceasta nu înseamnă că trebuie să prezinte, neapărat, domenii identice de
definire). Relaţiile R1 şi R2 din figura 5.1 sunt unicompatibile deoarece: (a) ambele au acelaşi număr
de atribute; (b) atributele A, B, C din R1 (le putem nota şi R1.A, R1.B, R1.C) corespund sintactic
(sunt de acelaşi tip) atributelor C, D şi E din R2 (R2.C, R2.D, R2.E).

Figura 5.1. Două relaţii unicompatibile

Reuniunea
Reuniunea a două relaţii unicompatibile, R1 şi R2 , este definită astfel: R1 R2 = { tuplu t |
t R1 sau t R2 }. Se notează: R3 R1 R2. Conţinutul tabelei reuniune R3 este prezentat
în figura 5.2. Primele trei tupluri din rezultat sunt preluate din R1, iar ultimele două din R2. R3 are
numai cinci tupluri deoarece un tuplu este comun tabelelor R1 şi R2. Algebra relaţională elimină
automat dublurile (tuplurile identice), astfel încât restricţia de unicitate este asigurată după orice
operaţie.

Figura 5.2. Reuniunea a două relaţii

Există suficiente situaţii informaţionale care fac uz de reuniunea a două tabele. Să luăm două
exemple:
A. Tabelele ce reflectă tranzacţii economice pot fi descompuse (sparte) în funcţie de anul (luna sau
cincinalul, deceniul) la care se referă; dacă ne raportăm la baza noastră de date, tabela LINIIFACT
poate fi ruptă în alte două, LF_2000_2001 care ar conţine facturile emise în anii 2000 şi 2001 şi
LINIIFACT ce conţine numai înregistrări aferente anului calendaristic 2002. Începând cu 2002, orice
situaţie statistică privind vânzările în perioada 2000-2002, 2000-2003 etc. necesită reuniunea celor
două tabele, LF_2000_2001 şi LINIIFACT.
B. Pentru a afla care sunt clienţii care au cumpărat cel puţin unul din produsele Produs 1 şi Produs 2,
se poate proceda la reuniunea tabelei ce conţine clienţii care au cumpărat Produs 1 cu tabela clienţilor
care au cumpărat Produs2.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 101

Reuniunea este comutativă. Singura problemă neclară ar fi legată de numele atributelor în


relaţia rezultat. În acest sens, se poate institui regula potrivit căreia numele atributelor relaţiei-reuniune
sunt numele primei relaţii participantă în operaţie. Aceasta nu are importanţă asupra comutativităţii,
deoarece conţinutul tabelei-rezultat este identic, indiferent care este prima relaţie enumerată.

Intersecţia
Intersecţia a două relaţii uni-compatibile, R1 şi R2, poate fi definită astfel: R1 R2 = {tuplu
t t R1 şi t R2 } Se notează: R4 R1 R2. Conţinutul tabelei intersecţie R4 este
prezentat în figura 5.3. Cum numai un tuplu este absolut identic şi în R1 şi R2, tabela rezultat este
alcătuită dintr-o singură linie.

Figura 5.3. Intersecţia a două relaţii

Exemple de informaţii care fac necesară recurgerea la intersecţie:


- Pentru a afla care sunt clienţii care au cumpărat şi Produs 1 şi Produs 2, se poate proceda la
intersecţia tabelei clienţilor care au cumpărat Produs 1 cu tabela alcătuită din clienţii care au cumpărat
Produs2;
- Zilele în care s-a făcut vânzări şi clientului Client 1 SRL şi clientului Client 2 SA;
- Persoanele de la firmele-client care cumulează posturile de Director vânzări şi Şef aprovizionare.
Ca şi reuniunea, intersecţia este comutativă, iar numele atributelor relaţiei-intersecţie sunt
extrase din prima relaţie participantă în operaţie.

Diferenţa
Diferenţa a două relaţii uni-compatibile, notate R1 şi R2, este definită astfel: R1 - R2 = {tuplu
t t R1 şi t R2 }. Se notează: R5 R1 R2. Conţinutul tabelei-diferenţă R5 (figura
5.4) conţine numai tuplurile din prima relaţie, R1, care nu se regăsesc în a doua relaţie, R2.

Figura 5.4. Diferenţa a două relaţii


102 Baze de date I

Aşadar, din rezultat este eliminat al treilea tuplu din R1, deoarece valorile acestuia există şi în
R2 (al doilea tuplu din R2). Exemple ce informaţii care fac necesară recurgerea la diferenţă:
- Care sunt clienţii care au cumpărat Produs 1 dar nu au cumpărat Produs 2 ?
- Care sunt zilele în care s-a făcut vânzări clientului Client 1 SRL dar nu există nici o factură către
Client 2 SA ?
Spre deosebire de reuniune şi intersecţie, diferenţa nu este este comutativă. Atributelor
relaţiei-diferenţă sunt cele ale primei relaţii (descăzutul), iar tuplurile sunt extrase din relaţia descăzut
nu se regăsesc în relaţia scăzător. În plus, nu există restricţii privind cardinalitatea (numărul de tupluri)
celor două relaţii, adică nu este musai ca relaţia descăzut să conţină mai multe tupluri decât cea
scăzător.

Produsul cartezian
Produsul cartezian al două relaţii R1 şi R2, denumit de Codd joncţiune încrucişată (CROSS
JOIN), este ansamblul tuturor tuplurilor obţinute prin concatenarea fiecărei linii din tabela R1 cu toate
liniile tabelei R2. Formal, dacă notăm cele două relaţii: R1 (A1, A2, ..., An) şi R2 (B1, B2, ..., Bm),
produsul cartezian este definit astfel: R1 R2 = { ( t1 , t2 ) t1 R1 şi t2 R2 }. Spre
deosebire de celelalte trei operaţiuni precedente, produsul cartezian nu face apel la noţiunea de relaţii
uni-compatibile, iar relaţia rezultat cumulează atributele celor două relaţii-argument. În figura 5.5 este
ilustrat rezutatul produsului cartezian a tabelelor R1 şi R2.

Figura 5.5. Produsul cartezian

Se notează: R6 ― R1 R2. Tabela rezultat R6 are o nouă structură - şase atribute (trei
preluate din R1 şi trei din R2). Întrucât există un atribut cu nume comun, C, pentru a diferenţia cele
două apariţii, acestea sunt prefixate, în antetul tabelei, cu numele relaţiei din care provine. Prima linie
din R6 este obiţinută prin “lipirea” primului tuplu din R1 cu primul tuplu din R2, a doua din primul
tuplu din R1 cu al doilea din R2 etc. Cum R1 are 3 tupluri, iar R2 tot 3, relaţia-rezultat al produsului
cartezian are 3 * 3 = 9 tupluri.
Nu prea există situaţii care să reclame folosirea directă şi exclusivă a produsului cartezian. Cel
mai important “merit” al acestuia în algebra relaţională este că permite “alipirea” a două relaţii,
fundamentând astfel operatorul-cheie care este joncţiunea.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 103

5.3 OPERATORII RELAŢIONALI


Cei patru operatori prezentaţi în paragraful precedent sunt generali, spre deosebire de
următorii care sunt specifici algebrei relaţionale. De obicei gruparea operatorilor relaţionali se face
astfel: (a) operatori unari de restricţie, care permit decupajul unei relaţii, pe orizontală - SELECŢIA şi
pe verticală - PROIECŢIA; (b) operatori binari de extensie: JONCŢIUNEA şi DIVIZIUNEA. O altă
deosebire majoră faţă de paragraful precedent este că vom putea recurge şi la exemple concrete din
baza de date “cobai” prezentată în capitolul anterior.

Selecţia
Selecţia triază dintr-o relaţie (tabelă) numai tuplurile ce satisfac o condiţie specificată printr-
un predicat. Ca preambul, definim noţiunea de formulă F asupra unei relaţii R, ca o expresie logică
compusă din: operanzi care sunt nume de atribute sau constante; operatori de comparaţie aritmetică:
, , , , =, ; operatori logici: ŞI, SAU, NON. Selecţia unei relaţii R, printr-o condiţie F, notată
SF(R), poate fi definită: SF(R) = { tuplu t (t R şi F(t) = adevărată)}. O notaţie ceva mai
pământească este: R1 SELECŢIE (R; <expresie-logică>), dar, de dragul ştiinţei, vom detalia:
R este relaţia R (A1,A2,...An) asupra căreia se aplică selecţia (Ai sunt atributele sale).
R1 este noua relaţie obţinută în urma selecţiei, care va avea aceeaşi schemă relaţională cu R -
R1 (A1,A2, ... An).
<expresie-logică> poate fi scrisă mai analitic astfel:
<expresie-logică> = (termen1) şi/sau (termen2) ... şi/sau (termenk), unde
o termen j = expresie1 expresie2 .
o expresie1 sau expresie2 sunt expresii calculate plecând de la atributele Ai.
o poate fi unul dintre operatorii pentru comparaţie.

Exemplu 1: Pentru a pune în operă savanta notaţie de mai sus, luăm în discuţie o primă
problemă: Care sunt liniile din R1 pentru care valorile atributelor A şi C sunt mai mari decât 20 ?
Ajungem, astfel, la notaţia:
R SELECŢIE (R1; A > 20 AND C > 20). Tabela R este prezentată în figura 5.6.

Figura 5.6. Rezultat selecţie – exemplul 1

Exemplu 2: Care sunt judeţele din Moldova ?


Mai întâi, se identifică în baza de date tabela (sau tabelele) din care se extrage rezultatul. În
acest exemplu, aceasta este JUDETE. Apoi se stabilesc atributele (atributul) asupra cărora se va aplica
predicatul de selecţie. Se obţine: R SELECŢIE (JUDETE; Regiune = “Moldova”) – vezi fig. 5.7.

Figura 5.7. Rezultat selecţie – exemplul 2


104 Baze de date I

Exemplu 3: Care sunt facturile emise în perioada 2-5 august 2000 ?


Tabela în care va opera operatorul de selecţie este FACTURI. Predicatul de selecţie utilizează atributul
DataFact: R SELECŢIE (FACTURI; DataFact >= 02/08/2000 AND DataFact <= 05/08/2000)

Figura 5.8. Rezultat selecţie – exemplul 3

Proiecţia
Prin proiecţie, o relaţie poate fi "decupată" pe verticală. Dacă selecţia extrage dintr-o tabelă
anumite linii, pe baza condiţiei îndeplinite de valorile unora dintre atribute, proiecţia permite
selectarea într-o tabelă-rezultat numai a coloanelor (atributelor) dorite dintr-o relaţie. Formal, fie o
relaţie R (A1,A2,...An). Proiecţia relaţiei R asupra unui subansamblu alcătuit din atribute proprii este o
relaţie care se obţine după parcurgerea a doi paşi: a. eliminarea dintre Ai a acelor atribute care nu sunt
specificate şi b. suprimarea dublurilor (tuplurile identice). Se notează: R1 PROIECŢIE (R; Aj,
Ak, ..., Ax). Spre deosebire de R, schema relaţiei R1 este alcătuită numai din atributele indicate: R1
(Aj, Ak, ..., Ax). Dacă după extragerea coloanelor nu există tupluri (linii) identice, R 1 va avea acelaşi
număr de linii ca şi relaţia R. În caz contrar, numărul lor va fi mai mic, în funcţie de numărul
dublurilor.

Exemplu 4: Începem, ca de obicei, cu un exemplu ceva mai arid - Care sunt valorile
combinaţiei atributelor A şi C în relaţia R1 ?
R PROIECŢIE (R1; A, C). Tabela R are două coloane, A şi C, şi trei linii, ca în figura 5.9.

Figura 5.9. Rezultat proiecţie – exemplul 4

Exemplu 5: Ce regiuni ale ţării sunt preluate în bază ?


Tabela în care se află răspunsul este JUDETE. Singura coloană care interesează este Regiune.
R PROIECŢIE (JUDETE; Regiune). În primul pas, se face decupajul pe verticală, obţinându-se o
relaţia notată R‟, apoi se elimină dublurile, rezultatul final fiind relaţia R.

Figura 5.10. Rezultat proiecţie – exemplul 5


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 105

Exemplu 6 - Care sunt: codul, denumirea şi numărul de telefon ale fiecărui client ?
Tabela care interesează este CLIENTI, din care se decupează trei coloane: CodCl, DenCl şi Telefon.
R PROIECŢIE (CLIENTI; CodCl, DenCl, Telefon).

Figura 5.11. Rezultat proiecţie – exemplul 6

Înlănţuirea consultărilor
Rezultatul unei consultări este o relaţie (tabelă) nouă. Pe baza acestui fapt, se pot înlănţui două
sau mai multe operaţiuni, redactându-se astfel interogări complexe.

Exemplu 7 - Care este numărul de telefon al clientul Client 2 SA ?


Soluţia este una foarte simpă. Cu ajutorul selecţiei se decupează din relaţia CLIENTI numai linia
corespunzăoare clientului “incriminat”. Se obţine o relaţie nou-nouţă denumită (de noi) R1. Asupra lui
R1 se aplică o proiecţie, deoarece interesează numai numărul de telefon; astfel, R2 conţine răspunsul
la problema luată în discuţie (vezi figura 5.12).
R1 SELECŢIE (CLIENTI; DenCl = ”Client 2 SA”)
R2 PROIECŢIE (R1; Telefon)

Figura 5.12. Înlănţuirea unei selecţii cu o proiecţie – exemplul 7

Exemplu 8 - Care sunt denumirile şi codurile poştale introduse pentru localităţile (prezente în
bază) din judeţele Iaşi (IS) şi Vrancea (VN) ?
Tabela “interogată” este CODURI_POSTALE. Pentru a răspunde la întrebarea pe care tot noi am
formulat-o, putem alege între următoarele două soluţii:
Soluţia 1 – figura 5.13:
R1 SELECŢIE (CODURI_POSTALE; Jud = ”IS” OR Jud = ”VN”)
R2 PROIECŢIE (R1; Loc, CodPost)

Figura 5.13. Exemplu 8 – soluţia 1

Soluţia 2 – figura 5.14:


106 Baze de date I

R1 SELECŢIE (CODURI_POSTALE; Jud = ”IS”)


R2 PROIECŢIE (R1; Loc, CodPost)
R3 SELECŢIE (CODURI_POSTALE; Jud = ”VN”)
R4 PROIECŢIE (R3; Loc, CodPost)
R5 R2 R4

Figura 5.14. Exemplu 8 – soluţia 2

Prima soluţie este, prin simplitate, cea mai tentantă. Este, însă, un prim caz în care pentru
rezolvarea unei probleme pot fi formulate două (sau mai multe) soluţii.

Exemplu 9 - Care sunt codurile produselor care apar deopotrivă în factura 1111 şi în factura
1117 ?
Tabela din care vor fi extrase datele este LINIIFACT. Soluţia se bazează pe intersecţia relaţiei care
conţine produsele prezente în factura 1111 (R2) cu relaţia produselor prezente în factura 1117 (R4),
după cum reiese din figura 5.15.
R1 SELECŢIE (LINIIFACT; NrFact = 1111)
R2 PROIECŢIE (R1; CodPr)
R3 SELECŢIE (LINIIFACT; NrFact = 1117)
R4 PROIECŢIE (R3; CodPr)
R5 R2 R4

Figura 5.15. Soluţie - exemplu 9

Joncţiunea
Într-un paragraf anterior am văzut că produsul cartezian permite fuzionarea a două tabele într-
o tabelă mamut ce conţine toate atributele şi liniile obţinute prin combinarea fiecărui tuplu dintr-o
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 107

relaţie cu fiecare tuplu din cealaltă. Tot atunci ne exprimam regretul sincer că operatorul produs
cartezian nu poate fi folosit, de unul singur, în interogări, dar că acest “neajuns” va fi compensat din
plin de operatorul derivat joncţiune.
Dacă produsul cartezian este o fuziune necondiţionată a două tabele, joncţiunea reprezintă
fuziunea a două relaţii care au o proprietate comună. Fie două relaţii, notate: R1 (A1, A2, ..., An) şi
R2 (B1, B2, ..., Bp). Fie Ai şi Bj două atribute definite pe acelaşi domeniu şi ansamblul operatorilor
de comparaţie: {=, >, , <, , }, ce pot fi aplicaţi celor două atribute Ai şi Bj. Joncţiunea relaţiei R1,
prin Ai, cu relaţia R2, prin Bj, notată R1 (Ai Bj) R2 sau R1 R2, este relaţia ale cărei
tupluri sunt obţinute prin concatenarea fiecărui tuplu al relaţiei R1 cu tuplurile relaţiei R2, pentru care
este verificată condiţia instituită între Ai şi Bj. R1 (Ai Bj) R2 = { t | t R1 R2 şi t(Ai) t(Bj)}.
Joncţiunea este echivalenta unui produs cartezian urmat de o selecţie. Joncţiunea definită mai
sus este referită în lucrările de specialitate ca theta-joncţiune. În lucrul cu BDR se utilizează cu
precădere echi-joncţiunea, ce reprezentă un caz particular al theta-joncţiunii, atunci când este
operatorul de egalitate ("="). Formal, echijoncţiunea se defineşte astfel: R1 (Ai = Bj) R2 = { t | t R1
R2 şi t(Ai) = t(Bj)}. Apelăm şi la o altă notaţie, mai uşor de reprezentat şi suficient de inteligibilă.
R ECHI-JONCŢIUNE (R1, R2; Ai=Bj)

Exemplu 10 – Theta-joncţiune
Începem exemplificările cu aceleaşi două tabele folosite din precedentul paragraf, R1 şi R2. Rezultatul
joncţiunii (theta-joncţiunii) exprimată prin expresia: R JONCŢIUNE (R1, R2; R1.A >= R2.E) va
fi obţinut în doi paşi, după este descris în figura 5.16.

Figura 5.16. Mecanismul de (theta)joncţionare - exemplul 10

Exemplu 11 – Echi-joncţiune
Operatorul de comparaţie dintre cele două atribute este, obligatoriu, semnul de egalitate.
R JONCŢIUNE (R1, R2; R1.A = R2.E)
108 Baze de date I

Figura 5.17. Echi-joncţiune - exemplul 11

Exemplu 12 – Joncţiune naturală


Joncţiunea naturală presupune nu numai ca operatorul de comparaţie să fie semnul de egalitate, ci şi
denumirea identică a atributelor de legătură dintre cele două tabele. R JONCŢIUNE (R1, R2; R1.C
= R2.C). Datorită faptului că ambele atribute au acelaşi nume, se poate considera că tabela rezultat
păstrează numai unul din cele două atribute, ca în figura 5.18 şi se poate recurge la o notaţie
simplificată: R JONCŢIUNE (R1, R2; C).

Figura 5.18. Joncţiune naturală - exemplul 12

De ce se insistă atât de mult pe importanţa joncţiunii ? În primul rând pentru că permite re-
compunerea relaţiei universale iniţiale. Modelul relaţional se bazează pe spargerea bazei în relaţii,
astfel încât nivelul redundanţei datelor şi problemele la actualizarea tabelelor să fie reduse la minim.
Cele mai multe interogări, însă, operează cu date şi predicate aplicate simultan atributelor din două sau
mai multe tabele. Cum selecţia este un operator unar (poate fi aplicată unei singure relaţii), este
necesară fuzionarea prealabilă a celor două, trei… relaţii şi obţinerea unei relaţii-agregat, relaţie
agregat la care se aplică predicatul suplimentar de selecţie.
Fuzionarea este posibilă prin joncţiune. Prin joncţionarea tuturor relaţiilor dintr-o bază de date
se obţine relaţia universală (cea iniţială, atot-cuprinzătoare). Nu ne permitem să reconstituim structura
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 109

şi conţinutul relaţiei universale ale bazei de date VINZARI, însă, prin exemple, vom încercă să
demonstrăm utilitatea joncţiunii.

Exemplu 13 - Să se obţină, pentru fiecare localitate: codurile poştale, denumirea, indicativul


judeţului, denumirea judeţului şi regiunea din care face parte
Practic, tabelei CODURI_POSTALE îi trebuie “alipite” la dreapta informaţiile din tabela JUDETE.
Problema este dificil de formulat, însă rezolvarea sa este cât se poate de simplă: joncţionarea celor
două relaţii. R JONCŢIUNE (CODURI_POSTALE, JUDETE; Jud)
Una din întrebările “clasice” pentru verificarea modului în care a fost sau nu înţeleasă
joncţiunea este: Câte linii are tabela-rezultat al joncţiunii ? În cazul nostru (de obicei în BDR)
răspunsul este: câte linii are tabela-copil. Răspunsul este corect numai atunci când se respectă
integritatea referenţială, altfel spus, numai atunci când toate valorile cheii străine se regăsesc în tabela-
părinte. Ca de obicei, lucrurile sunt mai complicate în realitate, deoarece joncţiunea nu se instituie
musai între o tabelă părinte şi una copil.

Exemplu 14 - Care sunt localităţile din Banat ?


Soluţia 1 - după calapodul exemplului anterior:
R1 JONCŢIUNE (CODURI_POSTALE, JUDETE; Jud)
R SELECŢIE (R1; Regiune = “Banat”)
Soluţia 2: Mai întâi, se aplică selecţia asupra tabelei JUDETE, iar tabela intermediară se
joncţionează cu CODURI_POSTALE:
R1 SELECŢIE (JUDETE; Regiune = “Banat”)
R JONCŢIUNE (CODURI_POSTALE, R1; Jud)
A doua variantă pare mai bună decât prima, deoarece joncţiunea operează asupra a două relaţii mai
reduse ca dimensiuni. Diferenţa este cu atât mai vizibilă atunci când relaţia JUDETE conţine toate
judeţele ţării, iar CODURI_POSTALE are câteva sute de înregistrări. Iar dacă ne gândim că înaintea
oricărei joncţiuni se calculează produsul cartezian, apare drept firească ideea amânării joncţiunii, astfel
încât aceasta să opereze asupra unor tabele cu număr cât mai mic de linii şi coloane. Coborând cu
picioarele pe pământ, trebuie spus că discuţia noastră are un caracter de principiu, deoarece algebra
relaţională este, totuşi, un limbaj… curat teoretic.

Exemplu 15 - În ce zile s-a vândut produsul cu denumirea “Produs 1” ?


Elementul de noutate îl reprezintă interesul pentru o informaţie ce provine dintr-o relaţie (atributul
DataFact din FACTURI) pe baza unei condiţii aplicate altei relaţii (atributul DenPr din PRODUSE),
iar cele două relaţii, FACTURI şi PRODUSE nu sunt în raportul părinte-copil. În aceste cazuri, este
necesară atragerea altor relaţii, până se completează “lanţul”. Interogarea ce rezolvă problema ridicată
în acest exemplu necesită şi tabela LINIIFACT.
Soluţie 1 - neoptimizată
R1 JONCŢIUNE (PRODUSE, LINIIFACT; CodPr)
R2 JONCŢIUNE (R1, FACTURI; NrFact)
R3 SELECŢIE (R2; DenPr = “Produs 1”)
R PROIECŢIE (R3; DataFact)
110 Baze de date I

Soluţie 2 – optimizată la sânge


R1 SELECŢIE (PRODUSE; DenPr = “Produs 1”)
R2 PROIECŢIE (R1; CodPr)
R3 JONCŢIUNE (R2, LINIIFACT; CodPr)
R4 PROIECŢIE (R3; NrFact)
R5 JONCŢIUNE (R4, FACTURI; NrFact)
R PROIECŢIE (R4; DataFact)
În cea de-a doua soluţie, fideli principiului “joncţiunii celei mai economicoase”, am eliminat nu numai
tuplurile, dar şi atributele de prisos înaintea calculării relaţiei intermediare.

Exemplu 16 - În ce judeţe s-a vândut produsul cu denumirea “Produs 1” în perioada 3-5


august 2005 ?
Relaţia rezultat trebuie să conţină valori ale atributului Judet din tabela JUDETE. Predicatul de
selecţie se aplică însă, în alte două tabele: PRODUSE, în care DenPr = “Produs 1” şi FACTURI ale
cărei tupluri trebuie să verifice condiţia: DataFact >= 03/08/2005 AND DataFact <= 03/08/2005.
R1 SELECŢIE (PRODUSE; DenPr = “Produs 1”)
R2 JONCŢIUNE (R1, LINIIFACT; CodPr)
R3 PROIECŢIE (R2; NrFact)
R4 SELECŢIE(FACTURI; DataFact >= 03/08/2005 AND DataFact <= 03/08/2005)
R5 JONCŢIUNE (R3, R4; NrFact)
R6 JONCŢIUNE (R5, CLIENTI; CodCl)
R7 PROIECŢIE (R6; CodPost)
R8 JONCŢIUNE (R7, CODURI_POSTALE; CodPost)
R9 PROIECŢIE (R8; Jud)
R10 JONCŢIUNE (R9, JUDETE; Jud)
R PROIECŢIE (R10; Judet)

Exemplu 17 - În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu


denumirea “Produs 2” ?
Rezultatul conţine atributul DataFact. Predicatul de selecţie se aplică tabelei PRODUSE.
Soluţie 1
R1 SELECŢIE (PRODUSE; DenPr = “Produs 1”)
R2 JONCŢIUNE (R1, LINIIFACT; CodPr)
R3 JONCŢIUNE (R2, FACTURI; NrFact)
R4 PROIECŢIE (R3; DataFact)
R5 SELECŢIE (PRODUSE; DenPr = “Produs 2”)
R6 JONCŢIUNE (R5, LINIIFACT; CodPr)
R7 JONCŢIUNE (R6, FACTURI; NrFact)
R8 PROIECŢIE (R7; DataFact)
R R4 R8
Soluţie 2
R1 SELECŢIE (PRODUSE; DenPr = “Produs 1”)
R2 JONCŢIUNE (R1, LINIIFACT; CodPr)
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 111

R3 JONCŢIUNE (R2, FACTURI; NrFact)


R4 SELECŢIE (PRODUSE; DenPr = “Produs 2”)
R5 JONCŢIUNE (R5, LINIIFACT; CodPr)
R6 JONCŢIUNE (R6, FACTURI; NrFact)
R7 JONCŢIUNE (R3, R6; DataFact)
R PROIECŢIE (R7; DataFact)
Prima variantă este una “cuminte”. Se intersectează relaţia zilelor în care s-a vândut primul produs
(R4) cu relaţia zilelor în care s-a facturat produsul 2 (R8). A doua e ceva mai insolită. Pur şi simplu, în
loc de intersecţie folosim joncţiunea. Cum, spre deosebire de intersecţie, relaţiile joncţionate nu
trebuie să fie unicompatibile, putem să operăm direct joncţiunea între R3 şi R6.
Reţinem ideea: putem să simulăm intersecţia a două relaţii prin joncţiune. Pentru a fi mai
convingător, rogu-vă să examinaţi figura 5.19 în care, pornind de la relaţiile R1 şi R2, se obţine
relaţia-intersecţie R direct prin operatorul intersecţie (stânga figurii) şi mai pe ocolite, folosind
joncţiunea (dreapta).

Figura 5.19. Intersecţia prin joncţiune

Exemplu 18 - Ce clienţi au cumpărat “Produs 2” şi “Produs 3”, dar nu au cumpărat


“Produs 5” ?
Bună întrebare ! Lucrurile nu însă atât de complicate precum par, deoarece folosim elemente din
exemplul anterior (intersecţia) plus operatorul… diferenţă.
R1 SELECŢIE (PRODUSE; DenPr = “Produs 2”)
R2 JONCŢIUNE (R1, LINIIFACT; CodPr)
R3 JONCŢIUNE (R2, FACTURI; NrFact)
R4 JONCŢIUNE (R3, CLIENTI; CodCl)
R5 PROIECŢIE (R4; DenCl)
R6 SELECŢIE (PRODUSE; DenPr = “Produs 3”)
R7 JONCŢIUNE (R6, LINIIFACT; CodPr)
R8 JONCŢIUNE (R7, FACTURI; NrFact)
R9 JONCŢIUNE (R8, CLIENTI; CodCl)
R10 PROIECŢIE (R9; DenCl)
112 Baze de date I

R11 SELECŢIE (PRODUSE; DenPr = “Produs 5”)


R12 JONCŢIUNE (R11, LINIIFACT; CodPr)
R13 JONCŢIUNE (R12, FACTURI; NrFact)
R14 JONCŢIUNE (R13, CLIENTI; CodCl)
R15 PROIECŢIE (R14; DenCl)
R R5 R10 – R15

Exemplu 19 - Ce facturi au fost emise în aceeaşi zi cu factura 1120 ?


Spre deosebire de interogările de până acum, condiţia de selecţie este una indirectă. În prealabil,
trebuie determinată ziua în care a fost întocmită factura cu numărul 1120. Apoi trebuie extrase, din
relaţia FACTURI, liniile pentru care DataFact are valoarea zilei facturii-reper.
R1 SELECŢIE (FACTURI; NrFact = 1120)
R2 PROIECŢIE (R1; DataFact)
R3 JONCŢIUNE (FACTURI, R2; DataFact)
R PROIECŢIE (R3; NrFact)

Joncţiunea externă
Ideea de bază a joncţiunii externe este de a include în rezultat şi tupluri din una din relaţii, sau
din ambele relaţii, care prezintă valori ale atributului de legătură ce nu se regăsesc în cealaltă relaţie.
Dacă precedentele tipuri de joncţiune sunt comutative, în cazul joncţiunii externe trebuie specificat din
care relaţie se extrag liniile fără corepondent în cealaltă relaţie. De aceea, există joncţiune externă la
stânga şi joncţiune externă la dreapta. La acestea se adaugă joncţiunea externă totală (denumită şi
plină sau deplină) care reprezintă reuniunea celor două. Apelând la aceleaşi tabele “abstracte”, R1 şi
R2, diferenţa dintre joncţiunea internă (echi-joncţiunea) şi cele trei tipuri de joncţiune externă apare cu
mai multă claritate în figura 5.20.

Figura 5.20. Diferenţa dintre echi-joncţiune şi joncţiunile externe

Exemplu 20 - Care sunt localităţile în care nu avem nici un client ?


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 113

Soluţia 1 se bazează diferenţa dintre tabela tuturor localităţilor (din tabela CODURI_POSTALE) şi
tabela localităţilor în care există clienţi (tabela CLIENŢI).
R1 PROIECŢIE (CODURI_POSTALE; CodPost)
R2 PROIECŢIE (CLIENTI; CodPost)
R3 R1 – R2
R JONCŢIUNE (R3, CODURI_POSTALE; CodPost)
Soluţia 2 utilizează proaspăta joncţiune externă.
R1 JONCŢIUNE EXTERNĂ LA STÎNGA (CODURI_POSTALE, CLIENTI;
CODURI_POSTALE.CodPost = CLIENTI.CodPost)
R SELECTIE (R1; CodCl IS NULL)
Pentru a identifica localităţile fără clienţi, s-au extras, din joncţiunea externă la stânga a relaţiilor
CODURI_POSTALE şi CLIENTI, numai liniile în care unul din atributele preluate din CLIENTI (noi
ne-am oprit asupra primului, CodCl) are valoarea NULL.

Diviziunea
Este cel mai complex şi mai greu de explicat dintre operatorii prezentaţi în acest capitol. Codd
l-a imaginat ca operator invers al produsului cartezian. Pentru a-l defini, se porneşte de la două relaţii
R1(X,Y) şi R2(Y); prima are, care va să zică, două atribute sau grupe de atribute, notate X şi Y, în
timp ce a doua numai atributul sau grupul de atribute notat cu Y (definit pe acelaşi domeniu ca şi în
relaţia R1). O primă restricţie: relaţia R2(Y), fiind numitorul diviziunii, nu trebuie să fie vidă.
Diviziunea relaţională R1 R2 are ca rezultat o relaţie definită ca ansamblul sub-tuplurilor
R1(X) pentru care produsul (lor) cartezian cu R2(Y) este un subansamblu al R1(X,Y). Rezultatul
expresiei R1 R2 reprezintă câtul diviziunii, fiind o relaţie ce poate fi notată R3(X). Într-o altă
formulare, xi R3 dacă şi numai dacă yi Y R2 (xi, yi) R1. Pentru
simplificarea prezentării, în continuare am considerat X şi Y două atribute, deşi, după cum reiese din
preambul, acestea pot fi grupe (ansambluri) de atribute. Să examinăm elementele din figura 5.21.

Figura 5.21. Diviziunea relaţională

Determinarea relaţiei R3 R1 R2 este sinonimă cu rezolvarea problemei: care dintre x1,


x2, x3, x4 şi x5 apar în R1, în tupluri împreună cu toate valorile lui Y din R2, respectiv y1, y2, y3,
y4 şi y5 ?
114 Baze de date I

Se parcurg pe rând valorile xi ale atributului X din relaţia R1:


x1 apare cu y1 (în tuplul 1), cu y2 (în tuplul 4), cu y3 (în tuplul 7), cu y4 (în tuplul 10) şi cu y5
(în tuplul 13). Deci x1 îndeplineşte condiţia şi va fi inclus în relaţia R3;
x2 apare cu y1 (în tuplul 2) dar nu apare cu y2 - nu va face parte din R3;
x3 apare cu y1 (în tuplul 3), cu y2 (în tuplul 5), cu y3 (în tuplul 8), cu y4 (în tuplul 11) şi cu y5
(în tuplul 15) - îndeplineşte condiţia şi va fi tuplu în R3;
x4 nu apare cu y1 - nu va face parte din R3.
x5 nu apare cu y1 - nu va face parte din R3.
În urma raţionamentului de mai sus, tabela R3 va fi alcătuită din două tupluri. Deşi pare un operator
ceva mai metafizic, diviziunea relaţională este deosebit de utilă pentru formularea consultărilor în care
apare clauza " " ("oricare ar fi" sau "pentru toate").

Exemplu 22 -Care sunt clienţii pentrucare există cel puţin câte o factură emisă în fiecare zi ?
Într-o altă formulare, ne interesează clienţii care au cumpărat “câte ceva” în toate zilele în care s-au
efectuat vânzări ? Prin urmare, câtul va fi o tabelă cu singur atribut, DenCl (denumirea clientului), iar
divizorul va fi o relaţie alcătuită numai din atributul DataFact (conţine toate zilele în care s-au efectuat
vânzări). Dacă am merge pe calapodul prezentat, am putea nota R3(DenCl), R2(DataFact). Cunoscând
structura câtului şi a divizorului, putem determina structura tabelei-deîmpărţit: R1(DenCl, DataFact).
Relaţia R1 va conţine denumirile clienţilor şi zilele în care există măcar o factură pentru clientul
respectiv. Soluţia poate fi redactată în următorii paşi:
a. construire relaţiei-deîmpărţit:
R11 JONCŢIUNE (FACTURI, CLIENTI; CodCl)
R1 PROIECŢIE (R1; DenCl, DataFact)
b. construire relaţie-numitor: R2 PROIECŢIE (FACTURI; DataFact)
c. în fine, apoteoza: R3 R1 R2
Schema şi conţinutul relaţiilor implicate în această soluţie sunt prezentate în figura 5.22.

Figura 5.22. Diviziunea relaţională – exemplu 22


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 115

Poate o să spuneţi că n-a meritat efortul, dar acest gen de soluţii se aplică la o largă gamă de
probleme. Să discutăm câteva speţe.

Exemplu 23 - În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu denumirea


“Produs 2” ?
Vă gândiţi, probabil, că aţi mai văzut undeva acest enunţ. Este chiar textul problemei de la exemplul
17. La acel moment, am formulat două soluţii: una “clasică”, bazată pe intersecţie şi una mai
neconvenţională ce utilizează joncţiunea. Adăugăm la acestea o alta, încadrabilă probabil tot în
categoria “neconvenţionale”.
Soluţie 3
R11 SELECŢIE (PRODUSE; DenPr = “Produs 1” OR DenPr = “Produs 2”)
R12 JONCŢIUNE (R11, LINIIFACT; CodPr)
R13 JONCŢIUNE (R12, FACTURI; NrFact)
R1 PROIECŢIE (R13; DataFact, CodPr)
R2 PROIECŢIE (R11; CodPr)
R3 R1 R2
Relaţia divizor este alcătuită din două tupluri ce conţin codurile celor două produse. Deîmpărţitul este
alcătuit din atributele DataFact şi CodProd şi conţine toate produsele vândute în fiecare din zilele de
facturare.

Exemplu 24 - Ce facturi au fost emise în aceeaşi zi cu factura 1120 ?


Deşi reprezintă un regres vizibil faţă de nivelul interogărilor precdente, revenim discret la enunţul
exemplului 19. Pentru această problemă se poate încropi şi o variantă de rezolvare bazată pe
diviziunea relaţională.
R1 PROIECŢIE (FACTURI; NrFact, DataFact)
R2‟ SELECŢIE (FACTURI; NrFact = 1120)
R2 PROIECŢIE (R2‟; DataFact)
R DIVIZIUNE (R1, R2)

Exemplu 25 - Căror clienţi le-au fost vândute toate produsele firmei ?


R11 JONCŢIUNE (LINIIFACT, FACTURI; NrFact)
R1 PROIECŢIE (R11; CodCl, CodPr)
R2 PROIECŢIE (PRODUSE; CodPr)
R3 DIVIZIUNE (R1, R2)
R JONCŢIUNE (R3, CLIENŢI; CodCl)
116 Baze de date I

Capitolul 6

INTEROGĂRI SQL (1)

Obiective:

I. Scurtă incursiune în standardele SQL


II. Reflectarea în SQL a operatorilor ansamblişti
III. Prelucrarea datelor calendaristice
IV. Folosirea operatorilor BETWEEN, LIKE, IN
V. Joncţiunile în SQL
VI. Funcţii agregat: COUNT, SUM, AVG, MIN, MAX
VII. Gruparea tuplurilor: GROUP BY şi HAVING
VIII. Manipularea valorilor NULL şi joncţiuni externe
IX. Structuri alternative

Rezultate aşteptate:

Deprinderea tuturor opţiunilor pentru redactarea interogărilor de


complexitate medie
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 117

6.1 ISTORICUL SQL. STANDARDE


După mulţi autori, momentul decisiv în naşterea SQL ca limbaj îl constituie lansarea
proiectului System /R de către firma IBM, eveniment ce a avut loc în 1974. Tot în 1974 Chamberlin şi
Boyce au publicat o lucrare în care este prezentată forma unui limbaj structurat de interogare,
"botezat" SEQUEL (Structured English as QUEry Language). În 1975 Chamberlin, Boyce, King şi
Hammer publică un articol în care prezintă sub-limbajul SQUARE, asemănător SEQUEL-ului, dar
care utiliza expresii matematice şi nu cuvinte din limba engleză. Autorii celor două studii au
demonstrat că limbajele SEQUEL şi SQUARE sunt complete din punct de vedere relaţional.
Un colectiv de autori, condus de Chamberlin, elaborează în 1976 o nouă lucrare în care se face
referire la SEQUEL 2, acesta fiind "preluat" ca limbaj de interogare al SGBD-ului System /R al firmei
IBM. În 1980 Chamberlin schimbă denumirea SEQUEL în SQL - Structured Query Language (Limbaj
Structurat de Interogare), dar şi astăzi mulţi specialişti pronunţă SQL ca pe predecesorul său. Anii
următori au înregistrat apariţia a o serie întreagă de lucrări care l-au perfecţionat, ultimul deceniu
consacrându-l ca pe cel mai răspândit limbaj de interogare a BDR, fiind prezent în numeroase
"dialecte" specifice tuturor SGBDR-urilor actuale, de la DB2 la Microsoft SQL Server, de la Oracle la
FoxPro şi ACCESS.
American National Standard Institute publică în 1986 standardul SQL ANSI X3.136-1986.
Este un standard care se bazează, într-o mare măsură, pe "dialectul" SQL al produsului DB2 de la
IBM. În 1989 are loc revizuirea extinderea acestui standard, "născându-se" SQL-89, care mai este
denumit şi SQL1. Deşi recunoscut ca bază a multor SGBDR-uri comerciale, SQL1 şi-a atras
numeroase critici. În plus, variantele comercializate de diferiţii producători, deşi asemănătoare în
esenţă, erau (şi sunt) incompatibile la nivel de detaliu. Pentru a umple golurile SQL1, ANSI a elaborat
în 1992 "versiunea" SQL2, specificaţiile fiind prezentate la un nivel mult mai detaliat (dacă SQL1 se
întindea pe numai 100 de pagini, SQL2 a fost publicat în aproape 600). IBM a avut un aport
incontestabil la apariţia şi maturizarea SQL, fiind un producător cu mare influenţă în "lumea" SGBD-
urilor, iar produsul său, DB2, este unul din standardele de facto ale SQL.
Standardul SQL:1999 a fost amânat de câteva ori până la publicarea sa, iar cea mai recentă
versiune este SQL:2008 (între timp a fost publicată şi versiunea SQL:2003). Actualmente, principalele
orientări ale SQL vizează transformarea acestuia într-un limbaj complet, în vederea definirii şi
gestionării obiectelor complexe şi persistente. Aceasta include: generalizare şi specializare, moşteniri
multiple, polimorfism, încapsulare, tipuri de date definite de utilizator, triggere (declanşatoare) şi
proceduri stocate, expresii privind interogări recursive şi instrumente adecvate de administrare a
datelor.
La momentul actual, SQL reprezintă cel mai important limbaj actual în domeniul bazelor de
date, atât prin gama comenzilor şi opţiunilor de care dispune, dar mai ales datorită faptului că s-a
reuşit standardizarea sa şi portarea pe toate Sistemele de Gestiune a Bazelor de date semnificative. Cu
atât mai mult, cu cât, spre deosebire de majoritatea covârşitoare a altor limbaje, poate fi deprins relativ
uşor de neinformaticieni şi utilizat pentru chestiuni de mare fineţe de către profesionişti. Acest capitol
se doreşte a fi o prezentare a elementelor esenţiale prin care, dată fiind structura unei baze de date
relaţionale, pot fi formulate interogări (fraze SELECT) prin care se obţin răspunsuri la gamă eterogenă
118 Baze de date I

de întrebări. În plus, sunt evocate pe scurt comenzile pentru actualizarea unei tabele (INSERT,
UPDATE, DELETE), precum şi cele de declarare a structurii bazei de date (CREATE TABLE).
Din perspectiva prezentei lucrări, obiectivul principal al SQL constă în a oferi utilizatorului
mijloacele necesare formulării unei consultări numai prin descrierea rezultatului dorit, cu ajutorul unei
aserţiuni (expresie logică), fără a fi necesară şi explicitarea modului efectiv în care se face căutarea în
BD. Altfel spus, utilizatorul califică (specifică) rezultatul, iar sistemul se ocupă de procedura de
căutare. Deşi toate clasificările îl încadrează la limbaje de generaţia a IV-a, SQL nu este, totuşi, un
limbaj de programare propriu-zis, prin comparaţie cu Basic, Pascal, C, COBOL etc. SQL nu conţine
(până la SQL3) instrucţiuni/comenzi pentru codificarea secvenţelor alternative şi repetititive, cu atât
mai puţin facilităţi de lucru cu obiecte vizuale, specifice formularelor de preluare a datelor (căsuţe-
text, butoane radio, liste, butoane de comandă etc.). Din acest punct de vedere, poate fi referit ca sub-
limbaj orientat pe lucrul cu bazele de date. Comenzile sale pot fi, însă, inserate în programe redactate
în limbaje de programare "clasice".
Principalele atuuri ale SQL sunt:
1. Independenţa de producător, nefiind o tehnologie "proprietară".
2. Portabilitate între diferite sisteme de operare.
3. Este standardizat.
4. "Filosofia" sa se bazează pe modelul relaţional de organizare a datelor.
5. Este un limbaj de nivel înalt, cu structură relativ apropiată limbii engleze.
6. Furnizează răspunsuri la numeroase interogări simple, ad-hoc, neprevăzute iniţial.
7. Constituie suportul programatic pentru accesul la BD.
8. Permite multiple imagini asupra datelor bazei.
9. Este un limbaj relaţional complet.
10. Permite definirea dinamică a datelor, în sensul modificării structurii bazei chiar în timp ce o parte
din utilizatori sunt conectaţi la BD.
11. Constituie un excelent suport pentru implementarea arhitecturilor client-server.

6.2 SINTAXA DE BAZĂ A FRAZEI SELECT


Fără îndoială, cea mai gustată parte din SQL este cea legată de interogarea bazei, adică de
obţinerea de informaţii din cele mai diverse, prin prelucrări, grupări etc. În SQL o interogare se
formulează printr-o frază SELECT care are un format pe cât de simplu, pe atât de flexibil. Cele trei
clauze principale sunt SELECT, FROM şi WHERE55, din care numai primele două sunt obligatorii.
Pentru început, vom face o paralelă cu operatorii algebrei relaţionale prezentaţi în capitolul 5.

6.2.1 Reflectarea operatorilor din algebra relaţională (1)

Selecţia şi proiecţia
Clauza SELECT corespunde operatorului proiecţie din algebra relaţională, fiind utilizată
pentru desemnarea listei de atribute (coloanele) din rezultat. Clauza FROM este cea în care sunt
enumerate relaţiile din care vor fi extrase informaţiile aferente consultării. Prin WHERE se
desemnează predicatul selectiv al algebrei relaţionale, relativ la atribute ale relaţiilor care apar în
clauza FROM.

55
La adresa http://w3.one.net/~jhoffman/sqltut.htm este un bun curs introductiv despre SQL
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 119

Prin execuţia unei fraze SELECT se obţine un rezultat de formă tabelară. Acesta poate fi o
listă (text), o tabelă propriu-zisă sau o tabelă temporară (care, de obicei, nu poate fi actualizată), dar şi
o tabelă derivată (imagine). Uneori rezultatul poate fi obţinut şi ca o variabilă masiv (tablou). Atunci
când clauza WHERE este omisă, se consideră implicit că predicatul P are valoarea logică "adevărat",
astfel încât vor fi incluse în rezultat toate liniile din tabela, sau produsul cartezian al tabelelor,
enumerată/enumerate în clauza FROM. Dacă, în locul coloanelor C1, C2, ... Cn, apare simbolul *,
rezultatul va fi alcătuit din toate coloanele (atributele) relaţiilor specificate în clauza FROM. De
asemenea, atributele rezultatului preiau numele din tabela/tabelele specificate în FROM. Schimbarea
numelui se realizează prin clauza AS.
În capitolul 1 era subliniată echivalenţa noţiunilor relaţie-tabelă. Conform restricţiei de
unicitate, într-o relaţie nu pot exista două linii identice. În SQL, tabela obţinută dintr-o consultare
poate conţine două sau mai multe tupluri identice. Spre deosebire de algebra relaţională, în SQL nu
se elimină automat tuplurile identice (dublurile) din rezultat. Pentru aceasta este necesară utilizarea
opţiunii DISTINCT:
SELECT DISTINCT C1, C2, ..., Cn
FROM R1, R2, ..., Rm
WHERE P
În concluzie, o frază SELECT, în forma în care a fost prezentată, corespunde: unei selecţii algebrice
(clauza WHERE - P); unei proiecţii (SELECT - Ci); unui produs cartezian (FROM - R1 R2 ...
Rm), şi conduce la obţinerea unui rezultat cu n coloane, fiecare coloană fiind un atribut din R1, R2, ...,
Rm sau o expresie calculată pe baza unor atribute din R1, R2, ..., Rm. Acum vom trece în SQL câteva
interogări din algebra relaţională, pe baza exemplelor din capitolul 5.
Exemplul 1 - selecţie
SELECT * FROM R1 WHERE A > 20 AND C > 20
Exemplul 2 – selecţie (Care sunt judeţele din Moldova ?)
SELECT * FROM JUDETE WHERE Regiune = “Moldova”
Exemplul 3 – selecţie (Care sunt facturile emise în perioada 2-5 august 2005 ?)
Formatul standard al unei constante de tip dată calendaristică este YYYY-MM-DD, aşa încât o
interogarea în SQL-92 (şi în dialectul PostgreSQL) poate avea forma:
SELECT * FROM FACTURI WHERE DataFact >= '2005-08-02' AND DataFact <= '2005-08-05'
Exemplul 4 – proiecţie
SELECT A,C FROM R
Exemplul 5 (Care sunt regiunile ţării preluate în bază ?)
După cum aminteam mai sus, spre deosebire de algebra relaţională, SQL nu elimină automat dublurile.
Tabela obţinută prin consultarea:
SELECT Regiune FROM JUDETE
are structura şi conţinutul identice cu R‟ (figura 5.10). Pentru a obţine răspunsul de forma tabelei R (o
regiune să apară o singură dată în răspuns) se foloseşte clauza DISTINCT:
SELECT DISTINCT Regiune FROM JUDETE
Exemplul 6 (Care sunt: codul, denumirea şi numărul de telefon ale fiecărui client ?)
SELECT CodCl, DenCl, Telefon FROM CLIENTI
Nu este necesară clauza DISTINCT, deoarece CodCl este cheia primară a tabelei CLIENTI.
120 Baze de date I

Exemplul 7 (Care este numărul de telefon al clientul Client 2 SA ?)


SELECT Telefon FROM CLIENTI WHERE DenCl = 'Client 2 SA'
Exemplul 8 (Care sunt denumirile şi codurile poştale ale localităţilor (prezente în bază) din
judeţele Iaşi (IS) şi Vrancea (VN) ?)
SELECT Loc, CodPost FROM coduri_postale WHERE Jud = 'IS' OR Jud = 'VN'

Reuniunea
Un rezultat identic cu tabela R3 din figura 5.2. se obţine prin fraza SELECT următoare:
SELECT * FROM R1 UNION SELECT * FROM R2
La reuniunea a două tabele, SQL elimină automat liniile identice din rezultat. Dacă se doreşte
preluarea tuturor liniilor celor două relaţii, şi, implicit, apariţia de linii duplicate se foloseşte clauza
ALL astfel:
SELECT * FROM R1 UNION ALL SELECT * FROM R2

Revenim la exemplul 8 din capitolul 5 (Care sunt denumirile şi codurile poştale ale
localităţilor (prezente în bază) din judeţele Iaşi (IS) şi Vrancea (VN) ?). Fraza SQL echivalentă
soluţiei 2 (bazată pe reuniune este):
SELECT Loc, CodPost FROM CODURI_POSTALE WHERE Jud = „IS‟
UNION SELECT Loc, CodPost FROM CODURI_POSTALE WHERE Jud = „VN‟

Intersecţia
Raportându-ne la exemplul din figura 2.3, echivalentul tabelei R4 se obţine în SQL prin:
SELECT * FROM R1 INTERSECT SELECT * FROM R2
SQL92 permite, ca şi în cazul reuniunii, prezenţa repetată în rezultat a unor linii (tupluri duplicate),
bineînţeles atunci când cele două relaţii asupra cărora se aplică intersecţia nu respectă restricţia de
unicitate:
SELECT * FROM R1 INTERSECT ALL SELECT * FROM R2
Dacă tuplul t1 apare repetat şi în R1 şi în R2, mai precis, de n ori în R1 şi de m ori în R2, în rezultatul
operatorului INTERSECT t1 apare o singură dată, în timp ce utilizând INTERSECT ALL t1 va apărea
de un număr de ori care este minimul dintre n şi m.
Pentru o primă ilustrare a utilizării a intersecţiei, transcriem soluţia din algebra relaţională
formulată la exemplul 9 (Care sunt codurile produselor care apar simultan şi în factura 1111 şi în
factura 1117 ?):
SELECT CodPr FROM LINIIFACT WHERE NrFact = 1111
INTERSECT SELECT CodPr FROM LINIIFACT WHERE NrFact = 1117
Exemplele de mai sus funcţionează în DB2, Oracle, şi PostgreSQL nu însă şi în alte SGBD-
uri (ex. Visual FoxPro) care nu are implementat operatorul INTERSECT, astfel încât intersecţia
trebuie realizată prin alte clauze şi operatori.

Diferenţa
Operatorul aşteptat ar fi MINUS. În standardul SQL92, şi în câteva SGBDR-uri precum DB2,
operatorul MINUS nu există, fiind substituit de EXCEPT, iar în alte SGBD-uri nu există nici unul, nici
altul. Tabela R5 din figura 2.5, calculată prin expresia R1-R2, se obţine în SQL (şi PostgreSQL) prin
interogarea:
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 121

SELECT * FROM R1 EXCEPT SELECT * FROM R2


Ca şi în cazul reuniunii şi intersecţii, eliminarea tuplurilor duplicate se face automat, iar repetarea lor
se asigură prin EXCEPT ALL.
Modificăm enunţul exemplului 9 în următorul: Care sunt codurile produselor care apar în
factura 1111, dar nu apar în factura 1117 ?
SELECT CodPr FROM LINIIFACT WHERE NrFact = 1111
EXCEPT SELECT CodPr FROM LINIIFACT WHERE NrFact = 1117

Produsul cartezian
SQL nu pune la dispoziţie vreun operator special dedicat produsului cartezian. Nici nu este nevoie.
Tabela R6 din figura 5.5 se obţine, pur şi simplu, prin enumerarea celor două relaţii în clauza FROM:
SELECT * FROM R1, R2

6.2.2 Coloane-expresii
O facilitate importantă în multe interogări SQL ţine de definirea, pe lângă atributele
tabelelelor, a unor coloane noi, pe baza unor expresii. Clauza AS permite denumirea coloanelor
calculate, sau redenumirea unor coloane ale tabelelor. Să luăm un exemplu banal: Care este, pentru
fiecare produs din factura 1111, codul, cantitatea, preţul unitar şi valoarea fără TVA ?
SELECT CodPr, Cantitate, PretUnit, Cantitate * PretUnit AS ValFaraTVA
FROM LINIIFACT WHERE NrFact = 1111
Rezultatul interogării este prezentat în figura 6.1.

Figura 6.1. Exemplu de coloană calculată

A patra coloana este denumită ValFaraTVA, după cum a fost specificat în clauza AS. Valorile sale sunt
determinate pe baza expresiei Cantitate * PretUnit.
Un alt tip de expresie este cel bazat pe concatenare, adică pe alipirea mai multor constante şi
variabile într-o coloană nouă. Operatorul SQL pentru concatenare este alcătuit din două bare verticale
(||). Spre exemplu, interogarea următoare produce rezultatul din figura 6.2.
SELECT 'Judetul ' || Judet || ' se afla in ' || Regiune AS Exemplu_Concatenare
FROM JUDETE

Figura 6.2. Concatenarea unor literali şi atribute şir de caractere

În PostgreSQL şi Oracle se pot concatena direct, adică fără conversie prealabilă, literali,
atribute-şir de caractere, numerice etc. Alte SGBD-uri, precum DB2, necesită transformarea valorilor
122 Baze de date I

non-şir de caractere (atribute/constante numerice, dată calendaristică etc.), operaţiune realizată prin
funcţia CAST. Spre exemplu, în Oracle şi PostgreSQL interogarea următoare este funcţională:
SELECT 'Factura ' || NrFact || ' a fost emisa pe data ' || DataFact AS Concatenare_Oracle_PgSQL
FROM FACTURI
în timp ce DB2 nu o suportă. Folosind funcţia de conversie CAST, putem redacta următoarea variantă
a interogării care funcţionează şi în PostgreSQL:
SELECT 'Factura ' || CAST (NrFact AS CHAR(8)) || ' a fost emisa pe data ' ||
CAST (DataFact AS VARCHAR(10)) AS Concatenare_DB2_PgSQL
FROM FACTURI
Valorile atributului NrFact sunt transformate şi şiruri de caractere de lungime fixă (8 poziţii), în timp
ce ale DataFact vor fi convertite în şiruri de caractere de lungime variabilă (vorba vine, deoarece
formatul datei este unitar pentru toate liniile rezultatului) de maximum 10 poziţii.
În finalul discuţiei despre coloanele-expresii, mai zăbovim preţ de câteva rânduri la expresiile
de tip dată calendaristică. Modurile în care au fost implementate aceste funcţiuni sunt foarte eterogene
de la SGBD la SGBD. Să presupunem că orice factură trebuie încasată în maximum două săptămâni.
DataFact fiind un atribut de tip DATE, implicit se consideră 14 ca fiind numărul zilelor:
SELECT NrFact AS Factura, DataFact AS Data_Facturare, DataFact + 14 AS Scadenta_Incasare
FROM FACTURI
Specificarea intervalelor calendaristice este o sarcină uşoară în mai toate serverele de baze de
date. Astfel, interogarea următoare este redactată în PostgreSQL şi furnizează acelaşi rezultat în
privinţa datei scadente:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
DataFact + 14 AS Scadenta_Incasare1,
DataFact + INTERVAL '14 DAYS' AS Scadenta_Incasare2,
DataFact + INTERVAL '2 WEEKS' AS Scadenta_Incasare3
FROM FACTURI
Dacă am presupune că scadenţa ar fi peste două luni de la facturare, interogările ar trebui
modificate astfel în PostgreSQL:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
DataFact + INTERVAL '2 MONTHS' AS Scadenta_Incasare
FROM FACTURI
sau
SELECT NrFact AS Factura, DataFact AS Data_Facturare, DataFact + INTERVAL '2' MONTH
AS Scadenta_Incasare FROM FACTURI
Ultima variantă funcţionează şi în Oracle care are însă şi o funcţie proprie – ADD_MONTHS:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
ADD_MONTHS(DataFact,2) AS Scadenta_Incasare
FROM FACTURI
Complicându-ne şi mai zdravăn, dorim să afişăm pentru fiecare factură ce dată va fi peste 1
an, două luni şi 25 de zile de la momentul emiterii.
Soluţia 1 PostgreSQL:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
DataFact + INTERVAL '1 YEAR' + INTERVAL '2 MONTH'
+ INTERVAL '25 DAY' AS O_Data_Viitoare
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 123

FROM FACTURI
Soluţia 2 PostgreSQL:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
DataFact + INTERVAL '1 YEAR 2 MONTH 25 DAY' AS O_Data_Viitoare
FROM FACTURI
Soluţia 1 Oracle (transformarea anilor în luni):
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
ADD_MONTHS(DataFact,14)+15 AS O_Data_Viitoare
FROM FACTURI
Soluţia 2 Oracle:
SELECT NrFact AS Factura, DataFact AS Data_Facturare,
DataFact + INTERVAL '1-2' YEAR TO MONTH + 25 AS O_Data_Viitoare
FROM FACTURI

În ceea ce priveşte operaţiunile de adunare şi scădere între două date calendaristice, aici
lucrurile se prezintă şi mai diferenţiat. Spre exemplu, interesează intervalul scurs între momentul
curent şi cel al emiterii fiecărei facturi. Interogarea PostgreSQL/Oracle are forma:
SELECT NrFact, DataFact AS Data_Facturare, CURRENT_DATE - DataFact AS Timp_Scurs
FROM FACTURI
Rezultatul scăderii a două date calendaristice conţine numărul zile. În PostgreSQL avem
nevoie de funcţia specială AGE care calculează intervalul dintre două date calendaristice. Interogarea
următoare afişează numărul anilor şi lunilor scurse de la data emiterii fiecărei facturi până în prezent
(vezi figura 6.3):
SELECT NrFact, DataFact, CURRENT_Date AS Astazi,
AGE(CURRENT_DATE, DataFact) AS Interval_Total, EXTRACT (YEAR FROM
AGE(CURRENT_DATE, DataFact)) AS Interval_Ani,
EXTRACT (YEAR FROM AGE( CURRENT_DATE,DataFact)) * 12 +
EXTRACT (MONTH FROM AGE( CURRENT_DATE,DataFact)) AS Interval_Luni
FROM facturi

Figura 6.3 Folosirea funcţiei AGE


124 Baze de date I

6.2.3 Opţiunea ORDER BY


Una din caracteristicile modelului relaţional este că nici ordinea atributelor, nici ordinea
liniilor în relaţii nu prezintă importanţă din punctul de vedere al conţinutului informaţional. În
practică, însă, forma de prezentare a rezultatelor interogării este importantă. Spre exemplu, o listă a
localităţilor este cu mult mai de folos decă se prezintă în ordine alfabetică. Ordonarea liniilor în
rezultatul unei interogări este posibilă prin clauza ORDER BY.
Să se obţină lista localităţilor în ordine alfabetică.
SELECT DISTINCT loc FROM coduri_postale ORDER BY Loc
Rezultatul se prezintă ca în figura 6.4.

Figura 6.4 Localităţile ordonate alfabetic

Aranjarea se face implicit crescător (ASC). Prin opţiunea DESC, ordinea prezentării se inversează. În
plus, se pot specifica mai multe coloane care să servească drept criterii suplimentare de ordonare. La
valori egale ale primului atribut, intră în acţiune criteriul de “balotaj” care este al doilea atribut etc.

Să se obţină, în ordinea descrescătoare a indicativului judeţelor, lista localităţilor în ordinea


crescătoare a denumirii.
SELECT Jud, Loc, CodPost FROM coduri_postale ORDER BY Jud DESC, Loc ASC
Rezultatul este cel din figura 6.5. Ultimul indicativ de judeţ (e vorba de ordine alfabetică) din tabela
CODURI_POSTALE este VS (pentru Vaslui), deci primele localităţi afişate vor fi din acest judeţ; prin
urmare, prima linie a rezultatului se referă la municipiul Bîrlad, iar a doua la municipiul Vaslui.

Figura 6.5. Două criterii de ordonare


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 125

6.2.4 Operatorii BETWEEN, LIKE, IN


Pentru formularea predicatului de selecţie, SQL permite utilizarea, pe lângă "clasicii" >, , <,
, =, , şi a altor operatori, dintre care ne vom opri la: BETWEEN (între, cuprins între), LIKE (ca şi, la
fel ca), IN (în), la care se adaugă IS NULL ce va fi prezentat ceva mai târziu.

Operatorul BETWEEN este util pentru definire intervalelor de valori. Ex: Care sunt
facturile emise în perioada 2-5 august 2007 ?
Soluţie PostgreSQL/Oracle – vizualizare rezultat (pgAdmin) - figura 6.6.
SELECT * FROM FACTURI
WHERE DataFact BETWEEN DATE'2007-08-02' AND DATE'2007-08-05'

Figura 6.6. Operator BETWEEN – rezultat PostgreSQL

Soluţia Oracle/PostgreSQL. Specificarea constantelor de tip dată calendaristică fac necesară în


PostgreSQL şi Oracle funcţia TO_DATE.
SELECT * FROM FACTURI WHERE DataFact BETWEEN TO_DATE('02/08/2007','DD/MM/YYYY')
AND TO_DATE('05/08/2007','DD/MM/YYYY') ;

Să se obţină, în ordinea judeţelor, lista localităţilor cu indicativul judeţului cuprins între IS


(Iaşi) şi TM (Timiş).
SELECT Jud, Loc, CodPost FROM coduri_postale WHERE Jud BETWEEN „IS‟ AND „TM‟
ORDER BY Jud, Loc

Operatorul LIKE este util când se doreşte obţinerea unor informaţii din bază, suntem în
postura, oarecum ingrată, de a nu şti cu exactitate cum se numeşte un client sau un produs, sau, pur şi
simplu, unei persoane îi cunoaştem numai unul dintre prenume etc. Sunt numai câteva situaţii pentru
rezovarea cărora a fost gândit operatorul LIKE. Operatorul LIKE permite compararea unui atribut
(expresii) cu un literal utilizând o “mască” construită cu ajutorul specificatorilor multipli % şi _.
Simbolurile procent şi liniuţă_de_jos (underscore, diferită de cratimă sau liniuţa-de-unire) sunt
denumite şi jokeri. Procentul substituie un şir de lungime variabilă, 0 - n caractere, în timp ce liniuţa
un singur caracter.

Care din firmele-client sunt societăţi cu răspundere limitată (SRL-uri) ?


Întrucât, în tabela CLIENTI, denumirea fiecărui client se termină cu forma sa de societate (SA, SRL
etc.), se poate redacta consultarea:
SELECT * FROM CLIENTI WHERE DenCl LIKE '%SRL‟
Rezultatul se prezintă ca în figura 6.7.
126 Baze de date I

Figura 6.7. Clienţii - SRLuri

Care dintre clienţi au numele (fără forma de societate şi spaţiul dinainte) din 8 caractere şi
sunt socieţi pe acţiuni ?
SELECT * FROM CLIENTI WHERE DenCl LIKE '________ SA'
Cele şapte liniuţe (nu se observă, dar sunt şapte), plus spaţiul care le urmează, nu au efect vizibil/im-
presionant pentru baza noastră de date, deoarece în capitolul 1, leneş fiind, am denumit toţi clienţii o
de manieră simplistă - Client 1 SRL, Client 2 SA etc. Dacă am avea mai mulţi clienţi (sau măcar un
“Client 10 SA”), atunci interogarea ar avea cu totul alt farmec.

Ce persoane au numele ce conţine litera S pe a treia poziţie ?


SELECT * FROM PERSOANE WHERE Nume LIKE '__s%'

Figura 6.8. Persoane cu litera “s” pe a treia poziţie a numelui

Rezultatul este vizualizat în figura 6.8. Atenţie, dacă există persoane al căror nume are litera S
majusculă pe a treia poziţie, acestea nu sunt extrase în rezultat ! În asemenea situaţii, soluţia de mai jos
este ceva mai sigură:
SELECT * FROM PERSOANE WHERE Nume LIKE '__s%' OR Nume LIKE '__S%'

În numele căror persoane apare, măcar o dată, litera S (indiferent de poziţie/poziţii) ?


SELECT * FROM PERSOANE WHERE Nume LIKE '%s%' OR Nume LIKE ‟%S%'

Figura 6.9. Persoane al căror nume conţine litera S

Care sunt persoanele ce trebuie felicitate de Sfântul Ion ?


Fireşte am fi tentaţi să redactăm varianta:
SELECT * FROM PERSOANE WHERE UPPER(Prenume) LIKE '%ION%'
Funcţia UPPER face conversia valorilor atributului Prenume în majuscule. Rezultatul se vede cu
ochiul liber în figura 6.10.

Figura 6.10. Tentativă ratată de a extrage “Sfinţii Ioni”


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 127

Interogarea nu şi-a atins ţinta, deoarece, pe de o parte, nu au fost extrase persoanele cu prenume ca
Ioan, Ioana, Ioanid, iar, pe de altă parte, au fost eronat extrase şi persoanele cu prenumele Caraion şi
Simion. Prin urmare, cele trei litere - ION trebuie să fie plasate la începutul prenumelui. La acest
şablon se adaugă şi IOAN. Bun, dar dacă pe omul nostru îl cheamă Marius Ion (are două prenume) ?
Cele două şabloane trebuie să se găsească la începutul fiecărui cuvânt din atributul Prenume. Iar dacă
avem în vedere că uneori cele două prenume se despart prin cratimă (Marius-Ion), rezultă o mândreţe
de interogare (răspunsul este prezentat în figura 6.11):
SELECT * FROM PERSOANE
WHERE UPPER(Prenume) LIKE 'ION%' OR UPPER(Prenume) LIKE 'IOAN%'
OR UPPER(Prenume) LIKE '% ION%' OR UPPER(Prenume) LIKE '% IOAN%'
OR UPPER(Prenume) LIKE '%-ION%' OR UPPER(Prenume) LIKE '%-IOAN%'

Figura 6.11. Persoanele ce trebuie felicitate de Sf. Ion

Varianta funcţionează în această formă deopotrivă în PostgreSQL/Oracle. Deşi rare, există


cazuri când printre caracterele căutate în valorile unui atribut şir de caractere se găseşte chiar unul
dintre cele două şabloane, _ sau %. Dacă, spre exemplu, interesează toţi clienţii care conţin simbolul
% în numele lor (s-ar putea ca, la un moment dat, să avem în bază clienţi de genul “Procentul vesel %
SRL”). Soluţia este:
SELECT * FROM CLIENTI WHERE DenCl LIKE '%\%%' ESCAPE '\'
Datorită primului şi ultimului simbol procent, poziţia caracterului căutat (în cazul nostru, chiar %) nu
prezintă importanţă: poate fi prima, ultima sau oricare între prima şi ultima. Rezultatul corect este
obţinut graţie unui caracter declarat prin clauza ESCAPE. Aceste este backslash-ul, dar poate fi
oricare altul. Prin clauza ESCAPE s-a indicat SQL-ului că procentul ce urmează simbolului \ nu este
joker, ci are regim de caracter oarecare, ce trebuie căutat în tabelă ca atare.

Operatorul IN se recomandă atunci când se verifică dacă valoarea unui atribut este
încadrabilă într-o listă de valori dată. În locul folosirii abundente a operatorului OR, este mai elegant
să se apeleze la serviciile operatorului IN. Format general: expresie1 IN (expresie2, expresie3, ...)
Rezultatul evaluării unui predicat ce conţine acest operator va fi adevărat dacă valoarea expresiei1
este egală cu (cel puţin) una din valorile: expresie2, expresie3, ....

Care sunt localităţile din judeţele Iaşi (IS), Vaslui (VS) şi Timiş (TM) ?
Fără operatorul IN:
SELECT DISTINCT loc, jud FROM coduri_postale
WHERE Jud = 'IS' OR Jud = 'VS' OR Jud = 'TM' ORDER BY Jud, Loc

Cu operatorul IN:
SELECT DISTINCT loc, jud FROM coduri_postale
WHERE Jud IN ('IS', 'VS', 'TM') ORDER BY Jud, Loc
128 Baze de date I

Care sunt facturile întocmite pe 1, 3 şi 7 august 2005 ?


SELECT * FROM facturi WHERE DataFact IN ('01/08/2005', '03/08/2005', '07/08/2005')
Fireşte, sintaxa trebuie adaptată în funcţie de felul în care fiecare SGBD lucrează cu atribute şi
constante de tip dată calendaristică.

6.2.5 Theta şi echi-joncţiunea


Dintre tipurile de joncţiune prezentate în capitolul 5, vom insista asupra theta-joncţiunii şi
echi-joncţiunii. SQL nu prezintă clauze sau operatori speciali pentru joncţiune, însă aşa cum am văzut,
o joncţiune este o combinaţie de produs cartezian şi selecţie. În consecinţă, pentru theta-joncţionarea
relaţiilor R1 şi R2 din exemplu 10 al algebrei relaţionale (figura 5.16) se scrie:
SELECT * FROM R1, R2 WHERE R1.A >= R2.E
iar pentru echi-joncţiune:
SELECT * FROM R1, R2 WHERE R1.A = R2.E

Joncţiunea naturală poate fi realizată numai prin specificarea numelor atributelor în clauza
SELECT a frazei de interogare. În standardul SQL92 şi în implementările SQL ale multor SGBD-uri
se poate folosi o variantă mai elegantă, ţinând seama că tot ce înseamnă theta şi echi joncţiune
reprezintă, pentru SQL, INNER JOIN (joncţiune internă). Prin urmare, cele două soluţii de mai sus pot
fi rescrise după cum urmează:
SELECT * FROM R1 INNER JOIN R2 ON R1.A >= R2.E
respectiv
SELECT * FROM R1 INNER JOIN R2 ON R1.A >= R2.E

Reluăm, pentru comparaţie, exemple din algebra relaţională. Exemplul 13 (Să se obţină,
pentru fiecare localitate: codul poştal, denumirea, indicativul şi denumirea judeţului şi regiunea din
care face parte)
Varianta 1 (SQL-1):
SELECT CodPost, Loc, coduri_postale.Jud, Judet, Regiune
FROM coduri_postale, judete
WHERE coduri_postale.Jud = judete.Jud
Varianta 2 (SQL-2):
SELECT CodPost, Loc, coduri_postale.Jud, Judet, Regiune
FROM coduri_postale INNER JOIN judete ON coduri_postale.Jud = judete.Jud
Numai atributul Jud a fost prefixat de numele tabelei din care provine. Prefixarea este
obligatorie atunci când câmpul există în două sau mai multe din tabelele enumerate în clauza FROM.

Exemplul 14 (Care sunt localităţile din Banat ?)


Varianta SQL1:
SELECT CodPost, Loc, coduri_postale.Jud, Judet, Regiune FROM coduri_postale, judete
WHERE coduri_postale.Jud = judete.Jud AND Regiune=‟Banat‟
În clauza WHERE, predicatului de selecţie pentru realizarea joncţiunii i s-a adăugat secvenţa de test a
regiunii.
Varianta SQL2:
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 129

SELECT CodPost, Loc, coduri_postale.Jud, Judet, Regiune


FROM coduri_postale INNER JOIN judete ON coduri_postale.Jud = judete.Jud
WHERE Regiune='Banat'
Avantajul celei de-a doua variante ţine de separarea condiţiei ce ţine de regiune de condiţia legată de
joncţiunea propriu-zisă.

Exemplul 15 (În ce zile s-a vândut produsul cu denumirea “Produs 1” ?)


SELECT DISTINCT DataFact
FROM PRODUSE
INNER JOIN LINIIFACT ON PRODUSE.CodPr = LINIIFACT.CodPr
INNER JOIN FACTURI ON LINIIFACT.Nrfact = FACTURI.NrFact
WHERE DenPr = 'Produs 1'
De notat folosirea clauzei DISTINCT pentru eliminarea eventualelor dubluri.

Exemplul 16 (În ce judeţe s-a vândut produsul cu denumirea “Produs 1” în perioada 3-5
august 2005 ?)
SELECT DISTINCT Judet
FROM PRODUSE
INNER JOIN LINIIFACT ON PRODUSE.CodPr = LINIIFACT.CodPr
INNER JOIN FACTURI ON LINIIFACT.Nrfact = FACTURI.NrFact
INNER JOIN CLIENTI ON FACTURI.CodCl = CLIENTI.CodCl
INNER JOIN CODURI_POSTALE ON CLIENTI.CodPost = CODURI_POSTALE.CodPost
INNER JOIN JUDETE ON CODURI_POSTALE.Jud = JUDETE.Jud
WHERE DenPr = 'Produs 1' AND DataFact BETWEEN ‟03-08-2005‟ AND ‟05-08-2005‟
Şi în acest exemplu este recomandată folosirea clauzei DISTINCT.

Exemplul 17 (În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu denumirea
“Produs 2” ?)
SELECT DISTINCT DataFact
FROM PRODUSE INNER JOIN LINIIFACT ON PRODUSE.CodPr = LINIIFACT.CodPr
INNER JOIN FACTURI ON LINIIFACT.Nrfact = FACTURI.NrFact
WHERE DenPr = 'Produs 1'
INTERSECT
SELECT DISTINCT DataFact
FROM PRODUSE INNER JOIN LINIIFACT ON PRODUSE.CodPr = LINIIFACT.CodPr
INNER JOIN FACTURI ON LINIIFACT.Nrfact = FACTURI.NrFact
WHERE DenPr = 'Produs 2'
Ar mai fi o soluţie bazată pe joncţiunea tabelei cu ea-însăşi, lucru pe care îl vom discuta în
paragraful următor.

6.2.6 Sinonime locale şi joncţiunea unei tabele cu ea însăşi


Lucrul cu nume lungi de tabele şi atribute are marele avantaj al lejerităţii la citire şi înţelegii
rapide a logicii de derulare a interogării. În schimb, suficienţi informaticieni nu agrează risipa de
caractere (şi, implicit, de timp şi nervi) presupuse de redactările “logoreice”. Ambele părţi au dreptate
130 Baze de date I

în oarecare măsură (sesizaţi sindromul rabinului !). Astfel încât în frazele SELECT tabelelor li se pot
asocia sinonime sau aliasuri mai scurte. Pentru ilustrare, ultima interogare se poate rescrie astfel:
SELECT DISTINCT DataFact
FROM produse P INNER JOIN liniifact LF ON P.CodPr = LF.CodPr
INNER JOIN facturi F ON LF.Nrfact = F.NrFact
WHERE DenPr = 'Produs 1'
INTERSECT
SELECT DISTINCT DataFact
FROM produse P INNER JOIN liniifact LF ON P.CodPr = LF.CodPr
INNER JOIN facturi F ON LF.Nrfact = F.NrFact
WHERE DenPr = 'Produs 2'
Tabelei PRODUSE i s-a asociat sinonimul P, LINIIFACT LF, iar pentru FACTURI F. Sinonimele
prefixează (când este necesar) numele atributelor în clauzele SELECT şi WHERE (eventual ORDER
BY, GROUP BY).
Există situaţii în care utilizarea sinonimelor n-are cu nimic de-a face cu lenea/comoditatea sau
depresiile nervoase. În afara interogărilor corelate pe care le vom discuta într-un capitol viitor, o
operaţiune în care musai trebuie folosite sinonimele este joncţionarea tabelei cu ea-însăşi sau, cum
spunem noi, moldovenii, cu dânsa-însăşi.

Revenim la exemplul 19 din algebra relaţională: Ce facturi au fost emise în aceeaşi zi cu


factura 1120 ?
Este, probabil, cel mai bun exemplu pentru prezentarea subconsultărilor; noi însă ne vom servi acum
de acest caz spre a introduce noul tip de joncţiune.
SELECT F2.NrFact
FROM FACTURI F1 INNER JOIN FACTURI F2 ON F1.DataFact = F2.DataFact
WHERE F1.NrFact=1120
Ca piatră de încercare, revenim la a doua soluţie formulată în algebra relaţională la
exemplul 17 (În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu denumirea “Produs
2” ?) .Joncţionăm o instanţă obţinută prin joncţiunea PRODUSE-LINIIFACT-FACTURI (în care
DenPr = ‟Produs 1‟) cu o altă instanţă a aceleaşi combinaţii (în care DenPr = ‟Produs 2‟).
SELECT DISTINCT F1.DataFact
FROM PRODUSE P1 INNER JOIN LINIIFACT LF1 ON P1.CodPr = LF1.CodPr
INNER JOIN FACTURI F1 ON LF1.NrFact = F1.NrFact
INNER JOIN FACTURI F2 ON F1.DataFact=F2.DataFact
INNER JOIN LINIIFACT LF2 ON LF2.NrFact = F2.NrFact
INNER JOIN PRODUSE P2 ON LF2.CodPr = P2.CodPr
WHERE P1.DenPr = 'Produs 1' AND P2.DenPr = 'Produs 2'

6.3 FUNCŢII-AGREGAT: COUNT, SUM, AVG, MIN, MAX


Formatul general al unei fraze SELECT ce conţine funcţii agregat este:
SELECT funcţia-predefinită1, ... , funcţia-predefinităN
FROM listă-tabele WHERE condiţii.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 131

În lipsa opţiunii GROUP BY (vezi paragraful următor), dacă în clauza SELECT este prezentă
o funcţie agregat, rezultatul va conţine o singură linie.

Funcţia COUNTcontorizează valorile nenule ale unei coloane sau numărul de linii dintr-un
rezultat al unei interogări, altfel spus, în rezultatul unei consultări, COUNT numără câte valori diferite
de NULL are o coloană specificată sau câte linii sunt.

Câţi clienţi are firma ?


SELECT COUNT (*) AS NrClienti FROM CLIENTI
Prezenţa asteriscului ca argument al funcţiei COUNT are ca efect numărarea liniilor tabelei CLIENŢI.
Rezultatul este prezentat în figura 6.12.

Figura 6.12. Câţi clienţi are firma ?

Tabela CLIENTI are cheie primară atributul CodCl care nu poate avea valori nule; de aceea,
la fel corectă este şi soluţia:
SELECT COUNT (CodCl) AS NrClienti FROM CLIENTI

Câte linii are produsul cartezian al tabelelor FACTURI şi LINIIFACT ?


SELECT COUNT(*) FROM FACTURI, LINIIFACT

Pentru câţi clienţi se cunoaşte adresa ?


SELECT COUNT (Adresa) AS NrClienti FROM CLIENTI
Rezultatul, 5, putea fi obţinut şi ceva mai complicat, folosind în clauza WHERE operatorul IS NULL
pe care-l tot amânăm pentru capitolul următor.

Câte facturi au fost emise pe 7 august 2005 ?


SELECT COUNT(NrFact) AS NrFacturi FROM FACTURI WHERE DataFact = „07/08/2005‟

Câte facturi au fost emise clienţilor din judeţul Vaslui ?


SELECT COUNT(NrFact) AS NrFacturi
FROM FACTURI F
INNER JOIN CLIENTI C ON C.CodCl=F.CodCl
INNER JOIN CODURI_POSTALE L ON C.CodPost=L.CodPost
INNER JOIN JUDETE J ON L.Jud = J.Jud
WHERE Judet=‟Vaslui‟

Câte localităţi sunt preluate în baza de date ?


Tabela CODURI_POSTALE poate conţine mai multe linii pentru acelaşi oraşe, deoarece oraşele mai
mare prezintă mai multe coduri poştale:
SELECT COUNT(Loc || Jud) AS NrLocalit FROM CODURI_POSTALE
132 Baze de date I

Necazul e că rezultatul obţinut, poate fi incorect, deoarece funcţia COUNT numără toate valorile
nenule. Există însă o clauză prin care o valoare să se ia în calcul o singură dată: DISTINCT. Rezultatul
corect presupune varianta:
SELECT COUNT(DISTINCT Loc || Jud) AS NrLocalit FROM CODURI_POSTALE

Funcţia SUM este una dintre cele mai utilizate funcţii în aplicaţiile economice, deoarece
datele financiar-contabile şi cele ale evidenţei tehnico-operative sunt preponderent cantitative.
Probabil că prea multe explicaţii teoretice despre modul în care operează această funcţie sunt inutile,
aşa încât vom trece în revistă câteva exemple.

Care este valoarea fără TVA a facturii 1111 ?


SELECT SUM(Cantitate * PretUnit) AS ValFaraTVA FROM LINIIFACT WHERE NrFact = 1111

Care este valoarea fără TVA a facturiilor emisei pe 7 august 2007 ?


SELECT SUM(Cantitate * PretUnit) AS ValFaraTVA_17aug2007
FROM LINIIFACT LF INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE DataFact = DATE'2007-08-07'

Care sunt cele trei valori: fără TVA, TVA şi totală ale facturii 1111 ?
SELECT SUM(Cantitate * PretUnit) AS ValFaraTVA,
SUM(Cantitate * PretUnit * ProcTVA) AS TVA,
SUM(Cantitate * PretUnit + Cantitate * PretUnit * ProcTVA) AS ValTotala
FROM LINIIFACT LF, PRODUSE P WHERE LF.CodPr = p.CodPr AND NrFact = 1111
În condiţiile actuale în care procentul TVA este unic - 19% este corectă şi varianta:
SELECT SUM(Cantitate * PretUnit) AS ValFaraTVA,
SUM(Cantitate * PretUnit * .19) AS TVA,
SUM(Cantitate * PretUnit + Cantitate * PretUnit * .19) AS ValTotala
FROM LINIIFACT WHERE NrFact = 1111
Soluţia propusă este una atemporală, altfel spus, a-guvernamentală şi a-relaxare fiscală. O vizualizare
mai elegantă a rezultatului poate fi realizată în Oracle/PostgreSQL utilizând interogarea următoare:
SELECT 'Pentru factura 1111, valoarea fara TVA este '|| SUM(Cantitate * PretUnit)||
', TVA este '||SUM(Cantitate * PretUnit * ProcTVA)|| ', iar valoarea totala este '||
SUM(Cantitate * PretUnit + Cantitate * PretUnit * ProcTVA) AS Rezultat
FROM LINIIFACT, PRODUSE
WHERE LINIIFACT.CodPr=PRODUSE.CodPr AND NrFact = 1111

La cât se situează cifra vânzărilor pe data de 7 august 2007 ?


SELECT '7 aug. 2007' AS Data, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE DataFact = DATE„2005-08-07‟

Care este valoarea vânzărilor pentru “Client 1 SRL” ?


SELECT 'Client 1 SRL' AS Client, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 133

FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
WHERE DenCl = 'Client 1 SRL'

Care este valoarea medie a preţului (inclusiv TVA) la care a fost vândut “Produs 1” ?
Nu, n-am trecut la funcţia AVG fără să vă fi anunţat din timp. Soluţia se bazează pe raportul dintre
suma valorilor şi cantitatea însumată pentru acest produs.
SELECT SUM(Cantitate*PretUnit*(1+ProcTVA)) / SUM(Cantitate) AS PretUnitMediu
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE DenPr = 'Produs 1'

Funcţia AVG calculează media aritmetică a unei coloane într-o tabelă oarecare, prin divizarea
sumei valorilor coloanei respective la numărul de valori nenule ale acesteia.

Care este valoarea (fără TVA) medie a produselor vândute în factura 1111 ?
SELECT 'Val. medie (fara TVA) a prod. din fact. 1111' AS Explicatii,
AVG(Cantitate * PretUnit) AS ValMedie
FROM LINIIFACT WHERE NrFact = 1111

Care este media valorilor (cu TVA) la care a fost vândut “Produs 1” ?
SELECT 'Val. medie a vinzarilor prod. Produs 1' AS Explicatii,
ROUND(AVG(Cantitate*PretUnit*(1+ProcTVA)),2) ValTotMedie
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE DenPr = 'Produs 1'
Adesea, prin aplicarea funcţiei AVG, sau prin formularea unor expresii ce conţin rapoarte între
atribute/constante, se obţin rezultate cu numeroase zecimale. În cazul de faţă, pentru preîntâmpinarea
acestui disconfort vizual şi intelectual, a fost preferată funcţia ROUND ce rotunjeşte media la două
zecimale.

Funcţiile MAX şi MIN sunt deosebit de utile în diverse tipuri de analiză, determinând
valoarea maximă, respectiv minimă, pentru o coloană (atribut). Se pot folosi şi pentru atribute de tip
şir de caractere, caz în care elementul de comparaţie este codul ASCII al caracterelor.
Care este localitatea cu ultima denumire, în ordine alfabetică ?
SELECT MAX(Loc) AS UltimaLoc FROM CODURI_POSTALE

Care este primul client şi ultimul client (în ordinea numelui) din judeţul Iasi ?
SELECT MIN(DenCl) AS Primul_Client, MAX(DenCl) AS Ultimul_Client
FROM CLIENTI C INNER JOIN CODURI_POSTALE L ON C.CodPost = L.CodPost
INNER JOIN JUDETE J ON L.Jud = J.Jud
WHERE Judet = 'Iasi'
134 Baze de date I

Care este cel mai mare preţ unitar (fără TVA) la care a fost vândut Produs 1 ?
SELECT MAX(PretUnit) FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
WHERE DenPr = 'Produs 1'

Din păcate, dacă dorim să aflăm şi în ce factură produsul are preţul unitar maxim, soluţia:
SELECT MAX(PretUnit), NrFact
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
WHERE DenPr = 'Produs 1'
nu funcţionează ! Până la subconsultările din capitolul viitor încercăm o soluţie gen “cârpeală”,
concatenând preţul cu numărul facturii:
SELECT 'Pret maxim='||MAX(TO_CHAR(LF.PretUnit,'99999999') || ', apare in fact.'||NrFact)
AS Produs1
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
WHERE DenPr = 'Produs 1'

Interogarea funcţionează, cel puţin dacă ne luăm după rezultatul din figura 6.13, aşa că n-are de ce să
ne fie jenă de improvizaţie. Rezultatul este incomplet, însă, atunci când preţul maxim apare în două
sau mai multe facturi, deoarece interogarea de mai sus extrage numai una dintre facturi (cea cu
numărul cel mai mare).

Figura 6.13. Preţul maxim pentru Produs 1 şi factura în care apare acest preţ

Care este cel mai mare şi cel mai mic preţ unitar (fără TVA) la care a fost vândut Produs 2 ?
SELECT MAX( 'Pret maxim=' || CAST (PretUnit AS CHAR(15)) || ', factura' ||
CAST (NrFact AS CHAR(10))) AS Max_Pret_Produs_2,
MIN( 'Pret minim=' || CAST (PretUnit AS CHAR(15))
|| ', factura' || CAST (NrFact AS CHAR(10))) AS Min_Pret_Produs_2
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
WHERE DenPr = 'Produs 2'

Care sunt cele mai mari două preţuri unitare (fără TVA) la care a fost vândut Produs 2 ?
SELECT 'Produs 2: '|| MAX('Primul PU: '||TO_CHAR(LF1.PretUnit,'99999999999')||', al doilea PU: '||
TO_CHAR(LF2.PretUnit,'99999999999')|| ' - factura primului:'||LF1.NrFact||
', factura-al doilea:'|| LF2.Nrfact) AS "Cele mai mari doua PretUnit"
FROM LINIIFACT LF1, LINIIFACT LF2, PRODUSE P
WHERE LF1.CodPr = P.CodPr AND DenPr = 'Produs 2' AND LF1.CodPr = LF2.CodPr
AND LF1.PretUnit > LF2.PretUnit

6.4 GRUPAREA TUPLURILOR. GROUP BY ŞI HAVING


Clauza GROUP BY formează grupe (grupuri) de tupluri ale unei relaţii, pe baza valorilor
comune ale unui atribut. În frazele SELECT formulate până în acest paragraf, prin intermediul
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 135

WHERE au fost selectate tupluri ale tabelei. Prin asocierea unei clauze HAVING la GROUP BY este
posibilă selectarea anumitor grupuri de tupluri ce îndeplinesc un criteriu, criteriu valabil numai la
nivel de grup (nu şi la nivel de linie).

Clauza GROUP BY
Rezultatul unei fraze SELECT ce conţine această clauză se obţine prin regruparea tuturor
liniilor din tabelele enumerate în FROM, extrăgându-se câte o apariţie pentru fiecare valoare distinctă
a coloanei/grupului de coloane. Formatul general este: SELECT coloană 1, coloană 2, ....,
coloană m FROM tabelă GROUP BY coloană-de-regrupare

Care este valoarea fără TVA a fiecărei facturi emise ?


SELECT NrFact, SUM(Cantitate*PretUnit) as ValFaraTVA
FROM LINIIFACT
GROUP BY NrFact
Rezultatul se obţine prin următoarea succesiune de operaţii:
1. Se constituie un grup pentru fiecare valoare distinctă a atributului NrFact.
2. Se execută funcţia SUM(Cantitate*PretUnit) în cadrul fiecărui grup.
3. Se obţine rezultatul al cărui număr de linii coincide cu valorile distincte ale NrFact.

Care este valoarea totală a vânzărilor pentru fiecare zi în care s-au emis facturi ?
SELECT DataFact, TRUNC(SUM(Cantitate * PretUnit * (1+ProcTVA)),0) AS ValTotala
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
GROUP BY DataFact

Figura 6.14. Vânzările zilnice

După cum puteţi observa, în lipsa clauzei ORDER BY zilele nu sunt dispuse în ordine
crescătoare (sau descrescătoare)

Care sunt, pentru fiecare client, numărul de facturi şi valoarea vânzărilor ?


SELECT DenCl, COUNT(DISTINCT F.NrFact) AS NrFacturilor,
136 Baze de date I

TRUNC(SUM(Cantitate * PretUnit * (1+ProcTVA)),0) AS ValTotala


FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
GROUP BY DenCl ORDER BY DenCl
Analizând rezultatul din figura 6.15 şi comparându-l cu tabela FACTURI se observă că al cincilea
client ar trebui să aibă cinci facturi, nu trei. Această anomalie aparentă se datorează faptului că
facturile 1122 şi 2122 nu au nici o linie corespondentă în LINIIFACT, astfel încât aceste facturi “cad”
la joncţiune.

Figura 6.15. Numărul facturilor şi valoarea vânzărilor pe clienţi

Care sunt vânzările, cantitativ şi valoric, pentru fiecare produs ?


SELECT DenPr, SUM(Cantitate) AS Cantitativ, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Valoric
FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
GROUP BY DenPr
ORDER BY DenPr
Rezultatul – vezi figura 6.16.

Figura 6.16. Vânzările cantitative şi valorice pe produse

O informaţie esenţială care lipseşte din figura 6.16 este unitatea de măsură, fără de care nu
putem fi siguri dacă totatul cantitativ se referă la cutii, sticle, pachete, baxuri etc. Din păcate, varianta:
SELECT DenPr, UM, SUM(Cantitate) AS Cantitativ, SUM(Cantitate*PretUnit*(1+ProcTVA)) AS Valoric
FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
GROUP BY DenPr ORDER BY DenPr
nu funcţionează, deoarece UM apare separat de atributul de grupare, nefiind inclus în funcţia/funcţiile
care se execută la nivelul grupului. Există însă un remediu simplu: includerea în clauza de grupare şi a
atributului UM. Altminteri, gruparea este identică, deoarece DenPr este cheie candidată a tabelei
PRODUSE, lucru observabil în figura 6.17.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 137

SELECT DenPr, UM, SUM(Cantitate) AS Cantitativ, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Valoric


FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
GROUP BY DenPr, UM ORDER BY 1,2

Figura 6.17. Includerea în rezultat a unităţii de măsură

Care este situaţia vânzărilor pe clienţi şi zile pentru luna septembrie 2007?
Interesează valoarea facturilor emise pe clienţi, şi, pentru fiecare client, cuantumul zilnic al acestora.
Este momentul să folosim o veritabilă grupare după două atribute (spre deosebire de exemplul
precendent, când gruparea celor două atribute a fost mai mult de nevoie, decât de voie).
SELECT DenCl, DataFact AS Data, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact INNER JOIN CLIENTI C
ON F.CodCl = C.CodCl
WHERE EXTRACT(YEAR FROM DataFact)=2007 AND EXTRACT(MONTH FROM DataFact)=9
GROUP BY DenCl, DataFact ORDER BY 1, 2

Figura 6.18. Vânzările zilnice pentru fiecare client

Care este situaţia vânzărilor fiecărui produs pe fiecare regiune ?


SELECT DenPr AS Produs, Regiune, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM LINIIFACT LF
INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
INNER JOIN CODURI_POSTALE L ON C.CodPost=L.CodPost
INNER JOIN JUDETE J ON L.Jud=J.Jud
GROUP BY DenPr, Regiune ORDER BY 1,2

Să se obţină situaţia vânzărilor din septembrie 2007 obţinută pe clienţi şi zile, afişându-se
câte un subtotal la nivel de client şi un total general.
Ideea improvizaţiei este să “alipim”, pentru fiecare client, după vânzările zilnice, câte o linie care să
conţină subtotalul:
138 Baze de date I

SELECT DenCl AS DenumireClient, TO_CHAR(DataFact,'DD-MM-YYYY') AS Data,


TO_CHAR(SUM(Cantitate * PretUnit * (1+ProcTVA)), '999999999999') AS Vinzari
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
WHERE EXTRACT(YEAR FROM DataFact)=2007 AND EXTRACT(MONTH FROM DataFact)=9
GROUP BY DenCl, DataFact
UNION
SELECT DenCl || ' - Subtotal' , NULL,
TO_CHAR(SUM(Cantitate * PretUnit * (1+ProcTVA)), '999999999999')
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
WHERE EXTRACT(YEAR FROM DataFact)=2007 AND EXTRACT(MONTH FROM DataFact)=9
GROUP BY DenCl
UNION
SELECT CHR(255)||'TOTAL GENERAL' , NULL,
TO_CHAR(SUM(Cantitate * PretUnit * (1+ProcTVA)),
'999999999999')
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE EXTRACT(YEAR FROM DataFact)=2007 AND EXTRACT(MONTH FROM DataFact)=9

Figura 6.19. Rânduri curente, subtotaluri şi total general

Clauza HAVING
Cea mai simplă definiţie: clauza HAVING este WHERE-ul ce operează la nivel de grupuri.
Dacă WHERE acţionează la nivel de tuplu, selectând acele linii care îndeplinesc o condiţie specificată,
HAVING permite specificarea unor condiţii de selecţie care se aplică grupurilor de linii create prin
clauza GROUP BY. Din rezultat sunt eliminate toate grupurile care nu satisfac condiţia specificată.
Formatul general este:
SELECT coloană 1, coloană 2, .... , coloană m
FROM tabelă
GROUP BY coloană-de-regrupare
HAVING caracteristică-de-grup
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 139

Care sunt zilele în care s-au întocmit cel puţin trei facturi ?
SELECT DataFact AS "Zi", COUNT(*) AS "Numar facturi"
FROM facturi
GROUP BY DataFact
HAVING COUNT(*) >= 3

Figura 6.20. Zilele în care s-au întocmit trei sau mai multe facturi

Care sunt clienţii pentru care vânzările depăşesc 5 milioane (lei) ?


SELECT DenCl AS Client,
SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
INNER JOIN CLIENTI C ON F.CodCl = C.CodCl
GROUP BY DenCl
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) > 5000000
ORDER BY 2 DESC

În ce zilele s-au emis mai multe facturi decât pe 2 august 2007 ?


SELECT F1.DataFact AS Zi1, COUNT(DISTINCT F1.NrFact) AS Nr_Facturilor1,
F2.DataFact AS Zi2, COUNT(DISTINCT F2.NrFact) AS Nr_Facturilor2
FROM FACTURI F1, FACTURI F2
WHERE F2.DataFact = TO_DATE('02/08/2007', 'DD/MM/YYYY')
GROUP BY F1.DataFact, F2.DataFact
HAVING COUNT(DISTINCT F1.NrFact) > COUNT(DISTINCT F2.NrFact)
Să zăbovim asupra logicii acestei interogări. Mai întâi se efectuează produsul cartezian pentru două
instanţe (F1 şi F2) ale tabelei FACTURI, eliminându-se pentru F2 linile în care data este alta decât 2
august 2005, adică:
SELECT *
FROM FACTURI F1, FACTURI F2
140 Baze de date I

WHERE F2.DataFact = TO_DATE('02/08/2007', 'DD/MM/YYYY')


ORDER BY F1.DataFact, F2.DataFact
În pasul următor, se constituie grupuri pentru fiecare combinaţie de valori (F1.DataFact, F2.DataFact).
F2.DataFact are aceeaşi valoare, 02/08/2007, prin urmare grupurile se constituie în funcţie de
F1.DataFact. Pentru ca funcţia COUNT să calculeze corect numărul facturilor dintr-o zi, este necesară
clauza DISTINCT.

Diviziunea relaţională
Multe situaţii ce reclamă diviziunea relaţională pot fi soluţionate elegant cu ajutorul clauzelor
GROUP BY şi HAVING. În exemplul următor este vorba de o intersecţie “simulată” printr-un
mecanism apropiat de diviziune.

În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu denumirea “Produs 2” ?


SELECT DISTINCT DataFact
FROM LINIIFACT LF INNER JOIN PRODUSE P ON LF.CodPr = P.CodPr
INNER JOIN FACTURI F ON LF.NrFact = F.NrFact
WHERE DenPr IN ('Produs 1', 'Produs 2')
GROUP BY DataFact
HAVING COUNT(DISTINCT LF.CodPr) = 2
Pentru fiecare grup asociat unei zile de vânzări se numără câte produse, dintre Produs 1 şi Produs 2,
au fost facturate. Funcţia COUNT() din clauza HAVING poate “întoarce” maxim valoarea 2, caz în
care data respectivă se încadrează în zilele căutate.
Una din facilităţile SQL ţine de includerea în predicatul clauzei HAVING nu numai a
constantelor şi/sau variabilelor, ci şi a altor consultări (subconsultări). Dar despre această facilitate
vom vorbi într-un paragraf viitor.

6.5 VALORILOR NULE ŞI JONCŢIUNI EXTERNE

6.5.1 Despre NULL-ităţi în SQL


Valoarea NULL a fost introdusă în capitolul 2, la explicarea noţiunilor modelului relaţional,
ca posibilitate de reprezentate a informaţiilor… inexistente sau inaplicabile56. Raportul Interim 75-02-
09 înaintat ANSI X3 (SPARC Study Group 1975) a delimitat 14 tipuri de date incomplete ce ar putea
apărea ca rezultate ale unor operaţii sau valori ale atributelor, printre care: depăşiri ale capacităţii de
stocare, diviziune la zero, trunchierea şirurilor de caractere, ridicarea lui zero la puterea zero şi alte
erori computaţionale, precum şi valori necunoscute. La popularea bazei de date, unui client nu i se
cunoştea codul fiscal, unor clienţi nu li se ştia adresa sau telefonul. Aceste trei atribute aveau, pe una
sau mai multe linii, valoarea NULL.
Pentru care dintre clienţi nu se cunoaşte adresa ?
Soluţia cvasi-generală se bazează pe utilizarea operatorului IS NULL care extrage toate valorile NULL
pentru un atribut:

56
Vezi şi [Fotache00-2]
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 141

SELECT *
FROM CLIENTI
WHERE Adresa IS NULL

Figura 6.21. Clienţii „fără adresă”

Valoarea NULL nu se confundă cu valoarea 0 - pentru atributele numerice - sau cu valoarea „ „


– spaţiu - pentru atributele de tip şir de caractere. Este important de notat că, în vederea identificării
valorilor nule, operatorul are forma IS NULL şi nu =NULL. Prin execuţia frazei SQL:
SELECT * FROM CLIENTI WHERE Adresa = NULL
se va obţine o tabelă cu 0 (zero) linii. Rezultatul evaluării Adresa = NULL va fi întotdeauna FALSE.
Logica operatorilor NOT, AND şi OR este ilustrată în tabelul 6.1.

Tabel 6.1. Rezultatele utilizării operatorilor NOT, AND şi AND


Aplicarea operatorului NOT unei condiţii
NOT TRUE FALSE UNKNOWN
FALSE TRUE UNKNOWN

Combinarea a două expresii utilizând operatorul AND


AND TRUE FALSE UNKNOWN
TRUE TRUE FALSE UNKNOWN
FALSE FALSE FALSE FALSE
UNKNOWN UNKNOWN FALSE UNKNOWN

Combinarea a două expresii utilizând operatorul OR


OR TRUE FALSE UNKNOWN
TRUE TRUE TRUE TRUE
FALSE TRUE FALSE UNKNOWN
UNKNOWN TRUE UNKNOWN UNKNOWN

Deşi baza de date prezentată este alcătuită deja dintr-un număr considerabil de tabele şi
atribute, introducem încă două tabele cu scop colateral temei “vânzări/încasări” – acela de a gestiona o
parte din datele privind drepturile băneşti (salariu negociat şi sporuri) ale angajaţilor firmei. Prima se
numeşte PERSONAL2 şi conţine date generale despre angajaţi: marcă; nume şi prenume; data
naşterii; compartiment; marca şefului (direct); salariu tarifar. A doua, SPORURI, evidenţiază sporurile
lunare primite de fiecare angajat: sporul de vechime (SporVechime), sporul pentru orele lucrate
noaptea (SporNoapte), sporuri pentru condiţii deosebite (SporCD) şi sporuri diverse (AlteSpor).
Cu ajutorul valorii NULL se poate face diferenţa între angajaţii pentru care nu s-a calculat
valoarea sporului pe luna curentă (şi care vor avea în câmpul corespunzător valoarea NULL) şi cei
care nu au dreptul la un asemenea spor, adică valoarea este 0. În continuare este prezentat scriptul de
creare a celor două tabele, iar în figurile 6.22 şi 6.23 conţinuturile acestora.
Listing 6.1. Script de creare a tabelelor SPORURI şi PERSONAL2
DROP TABLE sporuri ;
DROP TABLE personal2 ;
142 Baze de date I

CREATE TABLE personal2 (


marca NUMERIC(5) CONSTRAINT pk_personal2 PRIMARY KEY,
numepren VARCHAR(40),
datanast DATE,
compart VARCHAR(20),
marcasef NUMERIC(5) CONSTRAINT fk_personal2 REFERENCES personal2(marca),
saltarifar NUMERIC(12,2) ) ;

CREATE TABLE sporuri (


an NUMERIC(4),
luna NUMERIC(2),
marca NUMERIC(5) REFERENCES personal2 (marca),
sporvechime NUMERIC(12,2),
spornoapte NUMERIC(12,2),
sporcd NUMERIC(12,2),
altespor NUMERIC(12,2),
PRIMARY KEY (an,luna,marca) ) ;
Datele din cele două tabele pot fi interpretate în maniera următoare: firma s-a înfiinţat în aprilie 2008,
când avea numai trei angajaţi; La momentul curent (luna iulie 2008) sunt 10 angajaţi. Popularea celor
trei tabele este conţinută în listingul 6.2.

Listing 6.2. Script de populare a tabelelor SPORURI şi PERSONAL2


DELETE FROM personal2 ;
INSERT INTO personal2 VALUES (1, 'ANGAJAT 1', '1962-07-01', 'DIRECTIUNE', NULL, 1600) ;
INSERT INTO personal2 VALUES (2, 'ANGAJAT 2', '1977-10-11', 'FINANCIAR', 1, 1450) ;
INSERT INTO personal2 VALUES (3, 'ANGAJAT 3', '1962-08-02', 'MARKETING', 1, 1450) ;
INSERT INTO personal2 VALUES (4, 'ANGAJAT 4', NULL, 'FINANCIAR', 2, 1380) ;
INSERT INTO personal2 VALUES (5, 'ANGAJAT 5', '1965-04-30', 'FINANCIAR', 2, 1420) ;
INSERT INTO personal2 VALUES (6, 'ANGAJAT 6', '1965-11-09', 'FINANCIAR', 5, 1350) ;
INSERT INTO personal2 VALUES (7, 'ANGAJAT 7', NULL, 'FINANCIAR', 5, 1280) ;
INSERT INTO personal2 VALUES (8, 'ANGAJAT 8', '1960-12-31', 'MARKETING', 3, 1290) ;
INSERT INTO personal2 VALUES (9, 'ANGAJAT 9', '1976-02-28', 'MARKETING', 3, 1410) ;
INSERT INTO personal2 VALUES (10, 'ANGAJAT 10', '1972-01-29', 'RESURSE UMANE', 1, 1370) ;

DELETE FROM sporuri ;


INSERT INTO sporuri VALUES (2008, 4, 1, 160, 0, 0, 132) ;
INSERT INTO sporuri VALUES (2008, 4, 2, 130, 45, 0, 70) ;
INSERT INTO sporuri VALUES (2008, 4, 3, 145, 156, 420, 157) ;
INSERT INTO sporuri VALUES (2008, 5, 1, 160, 0, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 5, 2, 80, 45, 0, 70) ;
INSERT INTO sporuri VALUES (2008, 5, 3, 145, 0, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 5, 10, 137, 0, 0, 430) ;
INSERT INTO sporuri VALUES (2008, 6, 1, 160, 0, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 6, 2, 80, 0, 0, 150) ;
INSERT INTO sporuri VALUES (2008, 6, 4, 50, 15, 88, 120) ;
INSERT INTO sporuri VALUES (2008, 6, 5, 130, 15, 0, 20) ;
INSERT INTO sporuri VALUES (2008, 6, 10, 200, 12, 0, 6) ;
INSERT INTO sporuri VALUES (2008, 7, 1, 160, 0, NULL, NULL) ;
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 143

INSERT INTO sporuri VALUES (2008, 7, 2, 80, 0, 0, 158) ;


INSERT INTO sporuri VALUES (2008, 7, 3, 145, 0, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 7, 4, 50, 15, NULL, 15) ;
INSERT INTO sporuri VALUES (2008, 7, 5, 130, 0, 0, 120) ;
INSERT INTO sporuri VALUES (2008, 7, 6, 110, 147, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 7, 7, 60, 210, 0, 0) ;
INSERT INTO sporuri VALUES (2008, 7, 8, 130, 0, 15, 0) ;
INSERT INTO sporuri VALUES (2008, 7, 9, 140, 100, 77, 0) ;
INSERT INTO sporuri VALUES (2008, 7, 10, 200, 0, 0, 120) ;

Figura 6.22. Conţinutul tabelei PERSONAL2

Figura 6.23. Conţinutul tabelei SPORURI

Care sunt persoanele şi lunile pentru care nu s-a calculat (nu se cunoaşte) sporul pentru
condiţii deosebite ?
Prin interogarea:
SELECT SPORURI.Marca, NumePren, Compart, An, Luna
FROM PERSONAL2 INNER JOIN SPORURI ON PERSONAL2.Marca=SPORURI.Marca
144 Baze de date I

WHERE SporCD IS NULL


se obţine situaţia din figura 6.24.

Figura 6.24. Angajaţii pentru care nu s-au operat sporurile pentru condiţii deosebite

Care sunt angajaţii şi lunile în care aceştia nu au primit spor pentru condiţii deosebite ?
Atât soluţia cât şi rezultatul sunt sensibil diferite – vezi figura 6.25.
SELECT SPORURI.Marca, NumePren, Compart, An, Luna
FROM PERSONAL2 INNER JOIN SPORURI ON PERSONAL2.Marca=SPORURI.Marca
WHERE SporCD = 0
ORDER BY An, Luna, NumePren

Figura 6.25. Angajaţii şi lunile pentru care SporCD este zero (neNULL)

Care dintre angajaţi sunt născuţi înainte de 1 ianuarie 1970 şi care după această dată ?
Persoanele născute înainte (figura 6.26.):
SELECT * FROM PERSONAL2 WHERE DataNast < '01-01-1970'

Figura 6.26. Persoane născute înainte de 1 ian. 1970

şi după (figura 6.27):


SELECT * FROM PERSONAL2 WHERE DataNast >= '01-01-1970'
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 145

Figura 6.27. Persoane născute după 1 ian. 1970

Se observă un lucru curios: dacă reunim mulţimea angajaţilor născuţi înainte de data fixată cu
mulţimea celor născuţi după această dată nu obţinem relaţia iniţială PERSONAL2 (figura 6.28).
SELECT * FROM PERSONAL2 WHERE DataNast < '01-01-1970' UNION
SELECT * FROM PERSONAL2 WHERE DataNast >= '01-01-1970'

Figura 6.28. Reuniunea persoanelor născute înainte de 1 ian. 1970 cu persoanele născute după
această dată

Din tabela obţinută în figura 6.28 lipsesc angajaţii care nu au precizată data naşterii, altfel
spus, persoanele “fără vârstă”. Pentru recompunerea tabelei PERSONAL2, în reuniune mai trebuie
adăugate şi liniile pentru care DataNast IS NULL:
SELECT * FROM PERSONAL2 WHERE DataNast < '01-01-1970' UNION
SELECT * FROM PERSONAL2 WHERE DataNast >= '01-01-1970' UNION
SELECT * FROM PERSONAL2 WHERE DataNast IS NULL
ORDER BY Marca
Acest exemplu este grăitor în privinţa logicii trivalente a modelului relaţional în ceea ce
priveşte valorile nule. În continuare, interesează un alt aspect al NULLităţilor: modul de evaluare a
expresiilor în care unul sau mai mulţi operanzi au valori nule.

Care este totalul sporurilor fiecărui angajat pe luna iulie 2008 ?


SELECT SPORURI.Marca, NumePren, Compart,
SporVechime + SporNoapte + SporCD + AlteSpor AS TotalSporuri
FROM PERSONAL2 INNER JOIN SPORURI ON
PERSONAL2.Marca=SPORURI.Marca AND An = 2008 AND Luna=7
Din păcate, rezultatul este incorect – vezi figura 6.29 – deoarece, din prezentarea conţinutului tabelei
SPORURI (figura 6.23), reiese că, pe luna iulie 2005, ANGAJAT 1 are calculat spor de vechime, iar
ANGAJAT 4 are, pe acceaşi lună şi spor de vechime, şi de noapte şi alte sporuri, iar aceste sporuri nu
au fost luate în calcul la însumare.
146 Baze de date I

Figura 6.29. Rezultatul unei expresii în care cel puţin un operand este NULL

Explicaţia este simplă: dacă, într-o expresie, unul dintre operanzi este NULL, atunci rezultatul
evaluării întregii expresii este NULL. Fac excepţie funcţiile statistice. Dacă, spre exemplu, vrem să
calculăm: Totalul sporurilor de noapte acordate pentru luna iulie 2008, fraza:
SELECT SUM(SporNoapte) AS Total_SporuriNoapte_Luna_7
FROM SPORURI
WHERE An = 2005 AND Luna=7
calculează corect rezultatul.
Revenim la cazul cu probleme. Pentru a asigura corectitudinea totalului, ar trebui ca în
expresie orice valoare nulă să fie considerată zero. Lucru realizabil, deoarece SQL92 este “prevăzut”
cu o funcţie în acest sens – COALESCE:
SELECT s.Marca, NumePren, Compart, COALESCE(SporVechime,0) +
COALESCE (SporNoapte,0) + COALESCE (SporCD,0) +
COALESCE (AlteSpor,0) AS TotalSporuri
FROM PERSONAL2 p
INNER JOIN SPORURI s ON p.Marca=S.Marca AND s.An = 2008 AND s.Luna=7
Sumele obţinute sunt în acest caz cele din figura 6.30.

Figura 6.30. Conversia valorilor nule în zero şi evaluarea corectă a expresiei

Este important de reţinut că funcţiile VALUE, COALESCE, NVL nu se aplică la nivel de


expresie, ci fiecărui operand susceptibil de nulitate. De asemenea, un alt element interesant legat de
valorile nule ţine de conversia în sens invers, dintr-o valoare oarecare, în NULL.

Să se determine totalul sporurilor de noapte pentru luna iulie 2008, dar, în rezultat, să nu fie
luate în calcul valoarea (valorile) 300000 lei.
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 147

Soluţia “clasică” este:


SELECT SUM(SporVechime) FROM SPORURI
WHERE An = 2008 AND Luna=7 AND SporVechime <> 300000
O variantă ceva mai elegantă se redactează prin utilizarea funcţiei NULLIF prezentă în SQL92 care, în
interogarea de mai jos, converteşte orice apariţie a valorii în NULL:
SELECT SUM(NULLIF(SporVechime,300000)) FROM SPORURI WHERE An = 2008 AND Luna=7

6.5.2 Joncţiunea externă


Standardul SQL2 introduce operatorii necesari joncţiunii externe:
LEFT OUTER JOIN pentru joncţiune externă la stânga,
RIGHT OUTER JOIN pentru joncţiune externă la dreapta,
FULL OUTER JOIN pentru joncţiune externă totală (în ambele direcţii).
Dacă ne raportăm la exemplul teoretic din algebra realaţională (paragraful 5.3, figura 5.20),
atunci joncţiunile externe la stânga, la dreapta şi totală dintre relaţiile R1 şi R2 se transcriu în
standardul SQL92 astfel:
Joncţiune externă la stânga
SELECT * FROM R1 LEFT OUTER JOIN R2 ON R1.C=R2.C
Joncţiune externă la dreapta
SELECT * FROM R1 RIGHT OUTER JOIN R2 ON R1.C=R2.C
Joncţiune externă totală
SELECT * FROM R1 FULL OUTER JOIN R2 ON R1.C=R2.C
Următorul exemplu este desprins tot din algebra relaţională (exemplul 20): Care sunt
localităţile în care nu avem nici un client ?
SELECT *
FROM CODURI_POSTALE LEFT OUTER JOIN CLIENTI
ON CODURI_POSTALE.CodPost = CLIENTI.CodPost
WHERE CLIENTI.CodPost IS NULL

Figura 6.31. Localităţile în care nu sunt clienţi

Care au fost sporurile de noapte acordate angajaţilor pe lunile mai şi iunie 2008 ?
Situaţia obţinută se referă la două luni. Există însă angajaţi care nu au acest spor pe una sau chiar pe
ambele luni. Cu interogarea:
SELECT AN, Luna, PERSONAL2.Marca, NumePren, SporNoapte
FROM PERSONAL2 INNER JOIN SPORURI
ON PERSONAL2.Marca=SPORURI.Marca AND an=2008 AND Luna IN (5,6)
ORDER BY NumePren, An, Luna
rezultatul arată ca în figura 6.32.
148 Baze de date I

Figura 6.32. Sporurile de noapte pe mai şi iunie – varianta 1 de afişare

Pentru acest exemplu, interesează însă formatul de prezentare din figura 6.33.

Figura 6.33. Sporurile de noapte pe mai şi iunie – rezultatul dorit

Schimbăm alura SELECT-ului:


SELECT AN, Luna, PERSONAL2.Marca, NumePren, SporNoapte
FROM PERSONAL2 LEFT OUTER JOIN SPORURI ON
PERSONAL2.Marca=SPORURI.Marca AND An=2008 AND Luna=5
ORDER BY NumePren, An, Luna
Se obţine astfel tabela din figura 6.34.

Figura 6.34. Sporurile de noapte pe luna mai

Elementul îmbucurător este că în rezultat au fost incluşi toţi angajaţii. Cei care nu erau
angajaţi în această perioadă prezintă valori NULL pentru atributele An şi Luna. Pentru afişarea pe
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 149

coloane separate a sporurilor de noapte pe lunile mai şi iunie (ca în figura 6.33) sunt necesare două
joncţiuni externe ale tabelei PERSONAL2 cu două instanţe ale tabelei SPORURI.
SELECT PERSONAL2.Marca, NumePren, S1.SporNoapte AS SporNoapte_Mai,
S2.SporNoapte AS SporNoapte_Iunie
FROM PERSONAL2
LEFT OUTER JOIN SPORURI S1 ON PERSONAL2.Marca=S1.Marca
AND 2008=S1.An AND 5 = S1.Luna
LEFT OUTER JOIN SPORURI S2 ON PERSONAL2.Marca=S2.Marca
AND 2008=S2.An AND 6 = S2.Luna
ORDER BY NumePren
Elementul-cheie îl constituie prezenţa operatorului joncţiunii externe în dreptul atributelor An şi Luna.
Prin această se includ în rezultat şi liniile din tabela PERSONAL2 care nu prezintă corespondenţă
după atributul Marca cu tabela SPORURI pentru cele două luni.

Să se obţină sporurile de noapte pentru al doilea trimestru al anului 2008, atât lunar, cât şi
cumulat.
Sunt necesare trei instanţe ale tabelei SPORURI, fraza SELECT devenind supraponderală:
SELECT PERSONAL2.Marca, NumePren, COALESCE(S1.SporNoapte,0) AS Spor_Noapte_Aprilie,
COALESCE (S2.SporNoapte,0) AS Spor_Noapte_Mai,
COALESCE (S3.SporNoapte,0) AS Spor_Noapte_Iunie,
COALESCE (S1.SporNoapte,0) + COALESCE (S2.SporNoapte,0)+
COALESCE (S3.SporNoapte,0) AS Spor_Noapte_Trim_II
FROM PERSONAL2
LEFT OUTER JOIN SPORURI S1 ON PERSONAL2.Marca=S1.Marca
AND 2008=S1.An AND 4 = S1.Luna
LEFT OUTER JOIN SPORURI S2 ON PERSONAL2.Marca=S2.Marca
AND 2008=S2.An AND 5 = S2.Luna
LEFT OUTER JOIN SPORURI S3 ON PERSONAL2.Marca=S3.Marca
AND 2008=S3.An AND 6 = S3.Luna
ORDER BY NumePren
Rezultat est asemănător celui din figura 6.35.

Figura 6.35. Sporurile de noapte pe trimestrul al II-lea, pe luni şi cumulat


150 Baze de date I

6.6 STRUCTURI ALTERNATIVE: CASE, DECODE


Până la standardul SQL:1999, SQL a fost un limbaj pur neprocedural. Cu toate acestea,
începând cu standardul 92, SQL a introdus facilitatea codării structurilor alternative prin clauza CASE.

Câţi dintre clienţi sunt din Iaşi (cod poştal 6600) şi câţi din afara Iaşului ?
Începem cu o versiune “ajutătoare”. Pentru a scrie în dreptul fiecărui client dacă e din Iaşi sau din
afara Iaşului, se foloseşte interogarea:
SELECT DenCl, CodCl, CLIENTI.CodPost, Loc,
CASE Loc WHEN 'Iasi' THEN ' Din Iasi' ELSE 'Din afara Iasului' END AS Pozitionare
FROM CLIENTI INNER JOIN CODURI_POSTALE ON CLIENTI.CodPost = CODURI_POSTALE.CodPost
Rezultatul arată ca în figura 6.36.

Figura 6.36. Atribut calculat pe baza unei secvenţe alternative

Pentru a răspunde exact la întrebare, se poate redacta o variantă după cum urmează (rezultatul
final este prezentat în figura 6.37):
SELECT CASE Loc WHEN 'Iasi' THEN 'Din Iasi' ELSE 'Din afara Iasului' END AS Pozitionare,
COUNT(*) AS NrClienti
FROM CLIENTI INNER JOIN CODURI_POSTALE ON CLIENTI.CodPost = CODURI_POSTALE.CodPost
GROUP BY CASE Loc WHEN 'Iasi' THEN 'Din Iasi' ELSE 'Din afara Iasului' END

Figura 6.37. Numărul clienţilor ieşeni şi al celor din afara Ieşilor

Câţi angajaţi au primit, pe luna iulie 2008, spor pentru condiţii deosebite şi câţi nu ?
SELECT CASE WHEN SporCD > 0 THEN 'Au spor CD' ELSE 'Nu au spor CD' END,
COUNT(*) AS Nr
FROM SPORURI WHERE An=2008 AND Luna=7
GROUP BY CASE WHEN SporCD > 0 THEN 'Au spor CD' ELSE 'Nu au spor CD' END

Scadenţa fiecărei facturi emise este 20 de zile. Dacă însă data limită cade într-o sâmbătă sau
duminică, atunci scadenţa se mută în lunea următoare. Care sunt zilele scadente în aceste condiţii ?
Soluţia Oracle/PostgreSQL este una simplă, un rol decisiv avându-l, pe lângă structura CASE,
puternica funcţie TO_CHAR:
SELECT NrFact, TO_CHAR(DataFact,'YYYY-MM-DD') AS DataFact,
TO_CHAR(DataFact + 20,'YYYY-MM-DD') AS Scadenta1,
TO_CHAR(DataFact + 20,'DAY') AS NumeZi1,
CASE WHEN TO_CHAR(DataFact + 20,'DAY') = 'SATURDAY'
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 151

THEN TO_CHAR(DataFact + 22,'YYYY-MM-DD')


ELSE CASE WHEN TO_CHAR(DataFact + 20,'DAY') = 'SUNDAY'
THEN TO_CHAR(DataFact + 21,'YYYY-MM-DD')
ELSE TO_CHAR(DataFact + 20,'YYYY-MM-DD')
END
END AS Scadenta,
TO_CHAR( CASE WHEN TO_CHAR(DataFact + 20,'DAY') = 'SATURDAY'
THEN DataFact + 22
ELSE CASE WHEN TO_CHAR(DataFact + 20,'DAY') = 'SUNDAY'
THEN DataFact + 21
ELSE DataFact + 20
END
END,
'DAY') AS Zi_Scadenta
FROM FACTURI

Figura 6.38. Scadenţa rectificată a încasării facturilor

Capitolul 7

INTEROGĂRI SQL (2). SUBCONSULTĂRI


152 Baze de date I

Obiective:

Rezolvarea problemelor legate de interogarea BD folosind:


I. Subconsultări în clauza WHERE
II. Subconsultări în clauza HAVING
III. Subconsultări în clauza FROM
IV. Subconsultări sclarare în clauza SELECT
V. Actualizări avansate – UPDATE & SELECT

Rezultate aşteptate:

Formarea deprinderilor pentru redactarea de interogări SQL complexe


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 153

7.1 SUBCONSULTĂRI ÎN CLAUZA WHERE. OPERATORUL IN


Una din cele mai importante facilităţi ale SQL constă în includerea unei consultări în alta, pe
două sau mai multe nivele, altfel spus, utilizarea subconsultărilor. Prin subconsultări se obţin tabele
temporare intermediare folosite ca “argumente” ale frazelor SELECT superioare.

7.1.1 Operatorul IN în subconsultări


Operatorul cel mai utilizat în materie de subconsultări este IN pe care l-am întâlnit deja într-un
paragraf precedent într-o cu totul altă ipostază - testarea încadrării valorii unui atribut într-o listă de
constante. Pentru cele ce urmează, domeniul de test al este alcătuit din liniile unei tabele obţinute
printr-o (sub)consultare.
Revenim (pentru a doua oară) la exemplul 19 din algebra relaţională: Ce facturi au fost emise
în aceeaşi zi cu factura 1120 ? Într-un paragraf anterior a fost formulată o soluţie bazată pe joncţiunea
a două instanţe ale tabelei FACTURI. Iată şi o soluţie mai simplă bazată pe subconsultări:
SELECT NrFact FROM facturi WHERE DataFact IN
(SELECT DataFact FROM facturi WHERE NrFact=1120)
Execuţia acestei interogări se derulează în doi timp. Mai întâi, se execută sub-consultarea SELECT
DataFact FROM facturi WHERE NrFact=1120 obţinându-se o tabelă intermediară cu o singură linie şi
o singură coloană (DataFact). În al doilea pas sunt selectate liniile tabelei FACTURI care îndeplinesc
condiţia DataFact = „2007-08-07‟. În rezultat a fost inclusă şi factura de referinţă – 1120. Dacă se
doreşte excluderea acesteia, fraza SELECT se modifică astfel:
SELECT NrFact FROM facturi WHERE DataFact IN
(SELECT DataFact FROM facturi WHERE NrFact=1120)
AND NrFact <> 1120

Ce facturi au fost emise în alte zile decât factura 1120 ?


Acest exemplu necesită folosirea operatorului de negaţie:
SELECT NrFact FROM facturi WHERE DataFact NOT IN
(SELECT DataFact FROM facturi WHERE NrFact=1120)

Care sunt clienţii cărora li s-au trimis facturi în aceeaşi zi în care a fost întocmită factura
1120 ?
SELECT DenCl FROM clienti WHERE CodCl IN
(SELECT CodCl FROM facturi WHERE DataFact IN
(SELECT DataFact FROM facturi WHERE NrFact=1120) )
Rezultatul prezentat în figura 7.1 se obţine folosind trei nivele de interogare (fraza principală, o sub-
consultare şi o sub-sub-consultare).

Figura 7.1. Clienţii pentru care există facturi emise în aceeaşi zi ca 1120
154 Baze de date I

În ce judeţe s-a vândut produsul “Produs 2” ?


Am ales acest exemplu pentru a “vântura”, prin subconsultări, cât mai multe tabele ale bazei:
SELECT Judet FROM judete WHERE Jud IN
(SELECT Jud FROM coduri_postale WHERE CodPost IN
(SELECT CodPost FROM clienti WHERE CodCl IN
(SELECT CodCl FROM facturi WHERE NrFact IN
(SELECT NrFact FROM liniifact WHERE CodPr IN
(SELECT CodPr FROM produse WHERE DenPr = 'Produs 2') ) ) ) )

Revenim la tabela PERSONAL2 din figura 6.22: Câţi subordonaţi direcţi are ANGAJAT 2 ?
La această problemă (la care răspunsul este 2) formulăm, pentru comparaţie, două soluţii. Soluţia
bazată pe joncţiune este:
SELECT COUNT(*) AS NrSubordonati
FROM personal2 SUBORDONATI, personal2 SEFI
WHERE SUBORDONATI.MarcaSef=SEFI.Marca AND SEFI.NumePren='ANGAJAT 2'
A doua soluţia utilizează o subconsultare:
SELECT COUNT(Marca) AS NrSubordonati
FROM personal2
WHERE MarcaSef IN
(SELECT Marca FROM personal2 WHERE NumePren='ANGAJAT 2')

Tot prin subconsultări putem realiza intersecţia şi diferenţa relaţională. Raportându-ne la


intersecţia a două relaţii, R1 şi R2, operaţiunea se poate realoza în SQL şi astfel:
SELECT * FROM R1 WHERE (A,B,C) IN (SELECT C,D,E FROM R2)

Un alt exemplu de intersecţie, În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi


cel cu denumirea “Produs 2” ?, se poate rezolva şi astfel:
SELECT DISTINCT DataFact
FROM produse p
INNER JOIN liniifact lf ON p.CodPr = lf.CodPr
INNER JOIN facturi f ON lf.Nrfact = f.NrFact
WHERE DenPr = 'Produs 1' AND DataFact IN
(SELECT DISTINCT DataFact FROM produse p INNER JOIN liniifact lf
ON p.CodPr = lf.CodPr INNER JOIN facturi f ON lf.Nrfact = f.NrFact
WHERE DenPr = 'Produs 2')

Cât priveşte diferenţa relaţională, cheia rezolvării este NOT IN. Ce clienţi au cumpărat şi
“Produs 2” şi “Produs 3”, dar nu au cumpărat “Produs 5” ?
SELECT DISTINCT DenCl
FROM produse p
INNER JOIN liniifact lf ON p.CodPr = lf.CodPr
INNER JOIN facturi f ON lf.NrFact = f.NrFact
INNER JOIN clienti c ON f.CodCl = c.CodCl
WHERE DenPr = „Produs 2‟ AND f.CodCl IN
(SELECT CodCl
FROM produse p INNER JOIN liniifact lf ON p.CodPr = lf.CodPr
INNER JOIN facturi f ON lf.Nrfact = f.NrFact
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 155

WHERE DenPr = „Produs 3‟)


AND f.CodCl NOT IN
(SELECT CodCl
FROM produse p
INNER JOIN liniifact lf ON p.CodPr = lf.CodPr
INNER JOIN facturi f ON lf.Nrfact = f.NrFact
WHERE DenPr = „Produs 5‟)

Până la apariţia operatorilor LEFT OUTER JOIN, RIGHT OUTER JOIN şi FULL OUTER
JOIN în multe SGBD-uri joncţiunea externă era realizată prin reuniunea liniilor obţinute din echi-
joncţiune cu liniile unei tabele (completate cu zerouri/spaţii pentru atributele celeilalte tabele) ce nu au
corespondent în cealaltă. Joncţiunea externă la stânga a relaţiilor R1 şi R2 prin atributul C ar putea fi
realizată şi astfel:
SELECT * FROM R1 INNER JOIN R2 ON R1.C = R2.C
UNION
SELECT A,B,C, 0, ' ', 0 FROM R1 WHERE C NOT IN
(SELECT C FROM R2)

Tot cu ajutorul operatorului IN (şi NOT IN) se poate aborda şi "problema" diviziunii
relaţionale în SQL. Diviziunea tabelelor din figura 5.21 se realizează astfel:
SELECT X FROM R1 EXCEPT
SELECT DISTINCT R1.X FROM R1, R2 WHERE (R1.X, R2.Y) NOT IN
(SELECT X, Y FROM R1)

Aflaţi clienţii pentru care există cel puţin câte o factură emisă în fiecare zi cu vânzări din
perioada 10-20 septembrie 2007.
Urmăm logica pe care tocmai am prezentat-o.
SELECT DISTINCT DenCl FROM clienti INNER JOIN facturi ON clienti.CodCl=facturi.CodCl
WHERE DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20' AND clienti.CodCl IN
(SELECT CodCl FROM clienti C EXCEPT
SELECT DISTINCT C.CodCl FROM clienti C, facturi F1, facturi F2
WHERE C.CodCl=F1.CodCl AND F1.DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20'
AND F2.DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20'
AND (C.CodCl, F2.DataFact) NOT IN
(SELECT C.CodCl, DataFact FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
WHERE DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20'
))

7.1.2 Operatori de comparaţie în subconsultări


Până acum subconsultările au fost conectate la fraza SELECT superioară exclusiv prin
operatorul IN. În paragraful următor vom vedea că pentru (sub)interogările comparative, pot fi
întrebuinţaţi ALL, SOME, ANY. Atunci când rezultatul unei subconsultări se concretizează într-o
tabelă cu o singură coloană şi o singură linie, corelarea poate fi făcută cu operatorii de comparaţie
obişnuiţi: = , >, >=, <, <=. Vom ilustra această facilitate prin câteva exemple.
156 Baze de date I

Care este cel mai mare preţ unitar la care s-a vândut un produs ?
SELECT MAX(PretUnit) FROM liniifact

Care este cel mai mare preţ unitar, şi care este produsul, precum şi factura unde se
înregistrează respectivul preţ maxim ?
SELECT NrFact, DenPr, PretUnit FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
WHERE PretUnit = (SELECT MAX(PretUnit) FROM liniifact)

Care sunt cele mai mari două preţuri unitare de vânzare, care sunt produsele şi facturile
pentru care se înregistrează respectivele preţuri maxime ?
SELECT NrFact, DenPr, PretUnit FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
WHERE PretUnit >=
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact)
)
Pentru a înţelege mecanismul interogării de mai sus, pornim de la SELECT-ul “cel mai de
jos”. SELECT MAX(PretUnit) FROM liniifact extrage preţul unitar maxim din tabela LINIIFACT.
Subconsultarea superioară, (SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit < ( …ultima
subconsultare… ), determină al doilea preţ unitar din LINIIFACT. SELECT-ul principal afişează toate
preţurile unitare mai mari sau egale cu penultimul.

Care sunt cele mai mari cinci preţuri unitare de vânzare, produsele şi facturile în care apar
cele cinci preţuri maxime ? Aici voiam, de fapt, să ajungem:
SELECT NrFact, DenPr, PretUnit
FROM liniifact INNER JOIN produse ON liniifact.CodPr=PRODUSE.CodPr
WHERE PretUnit >
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact WHERE PretUnit <
(SELECT MAX(PretUnit) FROM liniifact)
)
)
)
) )
ORDER BY PretUnit DESC
Celor care nu au reuşit să fie impresionaţi de această ultimă variantă, le sugerez să încerce cu
primele 10, 20 s.a.m.d. preţuri unitare. Revenim însă la soluţia prezentată; iată rezultatul acesteia
(figura 7.2).
În PostgreSQL o clauză de mare ajutor este LIMIT prin care se extrag primele n valori ale
unei expresii dintr-un set de înregistrări:
SELECT NrFact, DenPr, PretUnit
FROM liniifact INNER JOIN produse ON liniifact.CodPr= produse.CodPr
WHERE PretUnit IN
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 157

(SELECT DISTINCT PretUnit FROM liniifact ORDER BY PretUnit DESC LIMIT 3)

Figura 7.2. Cele mai mari cinci preţuri unitare

7.1.3 Operatorii ALL, SOME, ANY


Cei trei operatori prezentaţi în acest paragraf sunt grozav de utili în interogările cu iz
“cantitativist” mai pronunţat, deoarece permit utilizarea unui predicat de comparaţie care este aplicat
rezultatului unei sub-consultări, predicat bazat pe unul din operatorii: =, <, >, <=, >=, < > sau #. Dacă,
în cea mai mare parte a cazurilor de până acum, se compara un atribut (sau rezultatul unei
expresii/funcţii) cu o constantă, ALL, SOME şi ANY permit compararea valorii atributului/fun-
cţiei/expresiei cu un set de tupluri (absamblu de linii) extras printr-o subconsultare.

Care sunt produsele vândute la preţuri unitare superioare oricărui preţ unitar la care a fost
vândut „Produs 1‟ ?
SELECT DISTINCT DenPr FROM liniifact INNER JOIN produse ON liniifact.CodPr= produse.CodPr
WHERE PretUnit > ALL
(SELECT DISTINCT PretUnit FROM liniifact INNER JOIN produse
ON liniifact.CodPr= produse.CodPr
WHERE DenPr ='Produs 1')
Ca orice interogare pe două nivele, ostilităţile se derulează în doi paşi. Mai întâi se execută
subconsultarea şi se obţine o tabelă intermediară în care se găsesc toate preţurile unitare la care a fost
vândut, în decursul istoriei, Produs 1 – vezi figura 7.3.

Figura 7.3. Rezultatul subconsultării – preţurile unitare pentru Produs 1

Cum operatorul de conexiune a frazei SELECT principale cu subconsultarea este > ALL, din
joncţiunea tabelelor PRODUSE şi LINIIFACT vor fi extrase numai liniile care au valoarea atributului
PretUnit mai mare decât toate valorile din figura 7.3. Aşa încât rezultatul final se prezintă ca în figura
7.4.
158 Baze de date I

Figura 7.4 Produse cu cel puţin un preţ unitar superior oricărui preţ unitar al Produsului 1

Care sunt produsele vândute la preţuri unitare superioare măcar unui preţ unitar al
„Produsului 1‟ ?
Este genul de situaţii în care se foloseşte SOME sau ANY (au aceeaşi funcţiune).
SELECT DISTINCT DenPr FROM liniifact INNER JOIN produse ON liniifact.CodPr= produse.CodPr
WHERE PretUnit > ANY
(SELECT DISTINCT PretUnit
FROM liniifact INNER JOIN produse ON liniifact.CodPr= produse.CodPr
WHERE DenPr ='Produs 1')
Rezultatul este cel din figura 7.5, deoarece, spre deosebire de ALL, şi ANY (şi SOME) selectează
liniile pentru care preţul unitar este mai mare decât măcar una din valorile obţinute prin subconsultare.

Figura 7.5. Produse cu cel puţin un preţ unitar superior măcar unui preţ unitar al Prod. 1

Operatorul =ANY este echivalent cu IN.

Câţi alţi angajaţi au salariul tarifar egal celui al ANGAJAT 2 ?


Soluţia:
SELECT COUNT(*) - 1 AS Nr FROM personal2 WHERE SalTarifar IN
(SELECT Saltarifar FROM personal2 WHERE NumePren='ANGAJAT 2')
este echivalentă cu:
SELECT COUNT(*) - 1 AS Nr FROM personal2 WHERE SalTarifar =ANY
(SELECT Saltarifar FROM personal2 WHERE NumePren='ANGAJAT 2')

Folosirea unuia din cei trei operatori nu este obligatorie atunci când subconsultarea conţine o
funcţie-agregat (ce întoarce o valoare dintr-un ansamblu de tupluri) - MIN, MAX, COUNT, SUM,
AVG.
Care este ultima factură întocmită (factura cea mai recentă) şi data în care a fost emisă ?
SELECT DataFact, NrFact AS UltimaFactura FROM facturi WHERE NrFact IN
(SELECT MAX(NrFact) FROM facturi)
sau
SELECT DataFact, NrFact AS UltimaFactura FROM facturi WHERE NrFact =
(SELECT MAX(NrFact) FROM facturi)
sau
SELECT DataFact, NrFact AS UltimaFactura FROM FACTURI WHERE NrFact =ANY
(SELECT MAX(NrFact) FROM facturi)
sau
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 159

SELECT DataFact, NrFact AS UltimaFactura FROM FACTURI WHERE NrFact =ALL


(SELECT MAX(NrFact) FROM facturi)

Fără funcţia MAX, inteogarea este:


SELECT DataFact, NrFact AS UltimaFactura FROM facturi WHERE NrFact >= ALL
(SELECT NrFact FROM facturi)

7.2 SUBCONSULTĂRI ÎN CLAUZA HAVING


Predicatele incluse în clauza HAVING ale interogărilor de până acum compară o expresie cu o
constantă. În cele ce urmează vom discuta despre includerea în clauza HAVING a subconsultărilor.
Revenim asupra unei probleme formulate anterior:

Care sunt zilele în s-au emis mai multe facturi decât pe 2 august 2005 ?
SELECT DataFact AS Zi, COUNT(NrFact) AS Nr_Facturilor
FROM facturi GROUP BY DataFact HAVING COUNT(NrFact) >
(SELECT COUNT(NrFact) FROM facturi WHERE DataFact = '08/02/2005')

Care este ziua în care s-au emis cele mai multe facturi ?
SELECT DataFact, COUNT(*) AS Nr_Facturilor FROM facturi GROUP BY DataFact
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM facturi GROUP BY DataFact)
Subconsultarea calculează numărul de facturi corespunzător fiecărei zile. Predicatul clauzei HAVING
compară numărul de facturi al fiecărei zile cu toate valorile extrase de subconsultare. Se obţine tabela
din figura 7.6.

Figura 7.6. Zilele în care s-au emis cele mai multe facturi

În PostgreSQL ne putem folosi fără ruşine de clauza LIMIT:


SELECT DataFact, COUNT(*) AS Nr_Facturilor
FROM facturi GROUP BY DataFact HAVING COUNT(*) =
(SELECT COUNT(*) FROM facturi GROUP BY DataFact ORDER BY COUNT(*) DESC LIMIT 1)

Care este clientul care a cumpărat cele mai multe produse ?


SELECT DenCl, COUNT(DISTINCT CodPr) AS CiteProduse
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
GROUP BY DenCl
HAVING COUNT(DISTINCT CodPr) >= ALL
(SELECT COUNT(DISTINCT CodPr)
FROM facturi F INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
GROUP BY CodCl)
160 Baze de date I

Care este compartimentul cu cea mai bună medie a salariilor tarifare ?


Se exclude din discuţie compartimentul DIRECŢIUNE în care apare, singur şi ferice, directorul
general.
SELECT Compart, ROUND(AVG(SalTarifar),2) AS Medie_Sal
FROM personal2 WHERE Compart <> 'DIRECTIUNE' GROUP BY Compart
HAVING AVG(SalTarifar) >= ALL
(SELECT AVG(SalTarifar) FROM personal2 WHERE Compart <> 'DIRECTIUNE'
GROUP BY Compart)

Pentru aducerea aminte a structurilor alternative, putem folosi şi varianta:


SELECT Compart, AVG (CASE WHEN Compart <> 'DIRECTIUNE' THEN SalTarifar ELSE 0
END) AS Medie_Sal
FROM personal2
GROUP BY Compart
HAVING AVG (CASE WHEN Compart <> 'DIRECTIUNE' THEN SalTarifar ELSE 0 END) >= ALL
(SELECT AVG (CASE WHEN Compart <> 'DIRECTIUNE' THEN SalTarifar ELSE 0 END)
FROM personal2 GROUP BY Compart)

Care este judeţul în care berea s-a vândut cel mai bine ?
În tabela PRODUSE există un atribut care reprezintă grupa în care se încadrează produsul
respectiv. Berea este una dintre grupele îndrăgite.
SELECT Judet, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari_Bere
FROM judete J INNER JOIN coduri_postale cp ON j.Jud=cp.Jud
INNER JOIN clienti c ON cp.CodPost=c.CodPost
INNER JOIN facturi f ON c.CodCl=f.CodCl
INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
WHERE Grupa='Bere'
GROUP BY Judet
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) >= ALL
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA))
FROM judete J INNER JOIN coduri_postale cp ON j.Jud=cp.Jud
INNER JOIN clienti c ON cp.CodPost=c.CodPost
INNER JOIN facturi f ON c.CodCl=f.CodCl
INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
WHERE Grupa='Bere'
GROUP BY Judet)

Care sunt clienţii cu valoarea vânzărilor peste medie ?


SELECT DenCl AS Client, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM clienti c INNER JOIN facturi f ON c.CodCl=f.CodCl
INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY DenCl
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 161

HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) >=


(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) / COUNT(DISTINCT F.CodCl)
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
)

Figura 7.7. Clienţi cu vânzări peste medie

Extrageţi factura cu valoarea imediat peste cea medie !


SELECT f.NrFact, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Valoare
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY f.NrFact
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) <= ALL
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA))
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY f.NrFact
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) >
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) / COUNT(DISTINCT F.NrFact)
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr))
AND SUM(Cantitate * PretUnit * (1+ProcTVA)) >
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) / COUNT(DISTINCT F.NrFact)
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr)

Element de noutate al acestei interogări este includerea, într-o consultare ce prezintă clauza
HAVING, a unei alte subconsultări în care apare, de asemenea, HAVING.

Din nou despre diviziunea relaţională


Revenind la cazul diviziunii relaţionale a R1 : R2 (altfel spus, găsirea tuturor icşilor care sunt
în R1 încombinaţie cu toţi igrecii din R2), se calculează în SQL şi astfel:
SELECT X FROM R1 GROUP BY X HAVING COUNT(Y) = (SELECT COUNT(Y) FROM R2)

Căror clienţi li s-a trimis măcar o factură în toate zilele cu vânzări din perioada 10-20
septembrie 2007 ?
SELECT DenCl AS Client, COUNT(DISTINCT DataFact) AS NrZile
FROM liniifact LF INNER JOIN produse P ON P.CodPr = LF.CodPr
INNER JOIN facturi F ON LF.NrFact = F.NrFact
INNER JOIN clienti C ON F.CodCl = C.CodCl
WHERE DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20'
GROUP BY DenCl
162 Baze de date I

HAVING COUNT(DISTINCT DataFact) =


(SELECT COUNT(DISTINCT DataFact)
FROM facturi
WHERE DataFact BETWEEN DATE'2007-09-10' AND DATE'2007-09-20')

Ce produse au fost vândute tuturor clienţilor ?


SELECT DenPr
FROM facturi f
INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY DenPr
HAVING COUNT(DISTINCT CodCl) = (SELECT COUNT (CodCl) FROM clienti)

Răspunsul este Produs 2.

7.3 SUBCONSULTĂRI ÎN CLAUZA FROM


Posibilitatea de a defini tabele „ad-hoc”, în clauza FROM, este unul dintre cele mai importante
daruri pe care SQL-ul îl oferă utilizatorilor, deopotrivă profesionişti şi neprofesionişti în ale bazelor de
date. Vom intra abrupt în câteva exemple, urmând ca după formularea soluţiilor să discutăm
ingredintele acestui artificiu.

Care sunt valorile facturate şi încasate, precum şi situaţia (“fără nici o încasare”, “încasată
parţial” sau “încasată total”) pentru fiecare factură ?
SELECT VINZARI.Nrfact, Facturat, COALESCE(Incasat,0) AS Incasat,
Facturat - COALESCE(Incasat,0) AS Diferenta,
CASE WHEN COALESCE(Incasat,0) = 0 THEN 'Fara nici o incasare'
WHEN Facturat > COALESCE(Incasat,0) THEN 'Incasata partial'
ELSE 'INCASATA TOTAL'
END AS Situatiune
FROM (
SELECT NrFact, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Facturat
FROM liniifact LF INNER JOIN produse P ON LF.CodPr = P.CodPr
GROUP BY NrFact) VINZARI LEFT OUTER JOIN
(SELECT NrFact, SUM(Transa) AS Incasat FROM incasfact GROUP BY NrFact) INCASARI
ON VINZARI.NrFact = INCASARI.NrFact
În clauza FROM a frazei SELECT principale au fost definite două tabele în toată regula, VINZARI şi
INCASARI. Prima conţine valoarea totală a fiecărei facturi, în timp ce a doua valoarea încasată.
Aceste două tabele sunt joncţionate extern după atributul NrFact.

Să se obţină sporurile de noapte pentru al doilea trimestru al anului 2008, atât lunar, cât şi
cumulat.
SELECT SL1.Marca, NumePren, COALESCE(SL1.SporNoapte,0) AS Spor_Noapte_Aprilie,
COALESCE(SL2.SporNoapte,0) AS Spor_Noapte_Mai, COALESCE(SL3.SporNoapte,0) AS
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 163

Spor_Noapte_Iunie, COALESCE(SL1.SporNoapte,0) + COALESCE(SL2.SporNoapte,0)+


COALESCE(SL3.SporNoapte,0) AS Spor_Noapte_Trim_II
FROM
(SELECT personal2.Marca, NumePren, SporNoapte FROM personal2
LEFT OUTER JOIN sporuri ON personal2.Marca= sporuri.Marca AND An=2008
AND Luna =4) SL1
INNER JOIN
(SELECT personal2.Marca, SporNoapte
FROM personal2 LEFT OUTER JOIN sporuri ON personal2.Marca= sporuri.Marca
AND An=2008 AND Luna =5) SL2 ON SL1.Marca=SL2.Marca
INNER JOIN
(SELECT personal2.Marca, SporNoapte FROM personal2 LEFT OUTER JOIN
Sporuri ON personal2.Marca= sporuri.Marca AND An=2008 AND Luna =6) SL3
ON SL1.Marca=SL3.Marca
ORDER BY NumePren
Clauza FROM principală “calculează” trei tabele ce conţin sporurile de noapte ale lunilor
aprilie (SL1), mai (SL2) şi iunie (SL3) 2005 ale fiecărui angajat (indiferent de data angajării
acestuia). Cele trei tabele sunt joncţionate după atributul Marca.

Revenim la diviziunea relaţională din figura 5.21 care poate fi realizată în următoarea variantă
SQL:
SELECT DISTINCT X
FROM R1
WHERE X NOT IN
(SELECT DISTINCT PROD_CART.X
FROM (SELECT DISTINCT R1.X, R2.Y FROM R1,R2) PROD_CART
LEFT OUTER JOIN R1 ON PROD_CART.X=R1.X AND PROD_CART.Y=R1.Y
WHERE R1.X IS NULL)

Putem încerca însă şi ceva mai elegant. Ce ziceţi de soluţia:


SELECT DISTINCT X
FROM
(SELECT X, COUNT(Y) AS Nr FROM R1 GROUP BY X) TEMP1,
(SELECT COUNT(Y) AS Nr FROM R2) TEMP2
WHERE TEMP1.Nr=TEMP2.Nr
Prima tabelă, TEMP1, conţine numărul valorilor lui Y pentru fiecare X din R1, iar a doua, TEMP2
numai numărul total al valorilor lui Y din R2 – figura 7.8.

Figura 7.8. Tabelele intermediare (ad-hoc) TEMP1 şi TEMP2


164 Baze de date I

Care sunt clienţii pentru care există cel puţin câte o factură emisă în fiecare zi cu vânzări din
perioada 10-20 septembrie 2007 ?
Este tot exemplul 22 din algebra relaţională formulat pentru ilustrarea operatorului diviziune.
Soluţia 1:
SELECT DISTINCT DenCl FROM clienti WHERE CodCl NOT IN
(SELECT DISTINCT PROD_CART.CodCl FROM
(SELECT DISTINCT clienti.CodCl, facturi.DataFact
FROM clienti, facturi WHERE DataFact BETWEEN DATE‟2007-09-10‟ AND DATE‟2007-09-20‟)
PROD_CART LEFT OUTER JOIN
(SELECT * FROM facturi WHERE DataFact BETWEEN DATE‟2007-09-10‟ AND
DATE‟2007-09-20‟) FACTURI ON PROD_CART.CodCl= FACTURI.CodCl AND
PROD_CART.DataFact= FACTURI.DataFact
WHERE FACTURI.CodCl IS NULL)

Soluţia 2 (cea preferată):


SELECT DISTINCT DenCl FROM
(SELECT CodCl, COUNT(DISTINCT DataFact) AS Nr FROM facturi
WHERE DataFact BETWEEN DATE‟2007-09-10‟ AND DATE‟2007-09-20‟
GROUP BY CodCl) TEMP1,
(SELECT COUNT(DISTINCT DataFact) AS Nr FROM facturi
WHERE DataFact BETWEEN DATE‟2007-09-10‟ AND DATE‟2007-09-20‟) TEMP2,
CLIENTI
WHERE TEMP1.Nr=TEMP2.Nr AND TEMP1.CodCl=CLIENTI.CodCl

În ce zile s-au vândut şi produsul cu denumirea “Produs 1” şi cel cu denumirea “Produs 2” ?


Soluţia 1:
SELECT DISTINCT DataFact
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
WHERE DenPr IN ('Produs 1', 'Produs 2') AND DataFact NOT IN
(SELECT DISTINCT PROD_CART.DataFact
FROM
(SELECT DISTINCT DataFact, CodPr
FROM FACTURI, PRODUSE
WHERE DenPr IN ('Produs 1', 'Produs 2')) PROD_CART
LEFT OUTER JOIN
(SELECT F.DataFact, LF.CodPr
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact INNER JOIN produse p
ON lf.CodPr=p.CodPr
) TEMP1 ON PROD_CART.DataFact=TEMP1.DataFact
AND PROD_CART.CodPr=TEMP1.CodPr
WHERE TEMP1.DataFact IS NULL)

Soluţia 2:
SELECT DISTINCT DataFact
FROM
(SELECT F.DataFact, COUNT(DISTINCT LF.CodPr) AS Nr
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 165

FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact INNER JOIN produse p


ON lf.CodPr=p.CodPr
WHERE DenPr IN ('Produs 1', 'Produs 2')
GROUP BY F.DataFact) TEMP1
WHERE Nr = 2
Din nou, soluţia a doua contrastează evident, prin simplitate, cu prima. Tabela TEMP1
conţine, pentru fiecare dată calendaristică, numărul de produse, dintre Produs 1 şi Produs 2 (1 sau 2)
care au fost vândute în ziua respectivă.

Ce facturi conţin măcar produsele din factura 1112 ?


SELECT DISTINCT NrFact
FROM
(SELECT NrFact, COUNT(*) AS NrProd FROM liniifact WHERE CodPr IN
(SELECT CodPr FROM liniifact WHERE NrFact = 1112)
GROUP BY NrFact
) T1,
(SELECT COUNT(CodPr) AS NrP1112 FROM liniifact WHERE NrFact = 1112) T2
WHERE T1.NrProd = T2.NrP1112
T2 conţine numărul produselor din factura 1112. T1 conţine, pentru fiecare factură, câte
produse sunt comune acesteia şi facturii 1112. Prin joncţiunea T1 cu T2 prin condiţia T1.NrProd =
T2.NrP1112, se extrag acele linii din T1 care au acelaşi număr de produse prezente în factura 1112 ca
şi aceasta.

Care sunt clienţii cărora li s-au vândut cel puţin produsele vândute clientului CLIENT 4 ?
SELECT DISTINCT DenCl
FROM (SELECT DenCl, COUNT(*) AS NrProd
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.Nrfact
WHERE CodPr IN
(SELECT CodPr
FROM clienti C
INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.Nrfact
WHERE DenCl='Client 4' )
GROUP BY DenCl
) T1,
(SELECT COUNT(CodPr) AS NrProd
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.Nrfact
WHERE DenCl='Client 4'
) T2
WHERE T1.NrProd = T2.NrProd

Logica soluţiei este cât se poate se asemănătoare precedentei, doar că T2 conţine numărul
produselor vândute clientului 4, iar în T1 pe fiecare linie se găseşte un client şi numărul produselor
vândute clientului 4 care i-au fost vândute şi (dânsu)lui.
166 Baze de date I

Să se afişeze câte facturi sunt: neîncasate deloc, încasate parţial şi încasate total ?
SELECT CASE
WHEN COALESCE(Incasat,0) = 0 THEN 'Fara nici o incasare'
WHEN Facturat > COALESCE(Incasat,0) THEN 'Incasata partial'
ELSE 'INCASATA TOTAL' END AS Situatiune, COUNT(*) AS Nr
FROM (SELECT NrFact, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Facturat
FROM liniifact LF INNER JOIN produse P ON LF.CodPr = P.CodPr
GROUP BY NrFact) VINZARI
LEFT OUTER JOIN
(SELECT NrFact, SUM(Transa) AS Incasat FROM incasfact
GROUP BY NrFact) INCASARI ON VINZARI.NrFact = INCASARI.NrFact
GROUP BY CASE WHEN COALESCE(Incasat,0) = 0 THEN 'Fara nici o incasare'
WHEN Facturat > COALESCE(Incasat,0) THEN 'Incasata partial'
ELSE 'INCASATA TOTAL' END
Joncţiunea externă la stânga dintre VÎNZĂRI şi ÎNCASĂRI este completată de o structură „alternativă
multiplă” CASE.

Care angajaţi au salariul tarifar egal cu cel al ANGAJAT2 ?


SELECT NumePren FROM
(SELECT NumePren, SalTarifar FROM personal2) TEMP1,
(SELECT Saltarifar FROM personal2 WHERE NumePren='ANGAJAT 2') TEMP2
WHERE TEMP1.SalTarifar = TEMP2.SalTarifar

Care este ziua în care s-au emis cele mai multe facturi ?
Din păcate (sau, din fericire !), soluţia:
SELECT TEMP1.DataFact, TEMP1.Nr
FROM
(SELECT DataFact, COUNT(Nrfact) AS Nr FROM facturi
GROUP BY DataFact) TEMP1,
(SELECT DataFact, COUNT(Nrfact) AS Nr FROM facturi GROUP BY DataFact) TEMP2
WHERE TEMP1.Nr >= ALL (SELECT Nr FROM TEMP2)
nu funcţionează! Aceasta înseamnă că o tabelă definită ad-hoc într-o frază SELECT nu este
recunoscută în subconsultări. Se poate, totuşi, utiliza varianta:
SELECT DataFact, COUNT(*) AS Nr_Facturi FROM facturi GROUP BY DataFact
HAVING COUNT(*) = (SELECT MAX(Nr) FROM (SELECT DataFact, COUNT(NrFact) AS Nr
FROM facturi GROUP BY DataFact) TEMP1)
În subconsultare s-a definit tabela intermediară TEMP1 al cărei atribut Nr este folosit în funcţia MAX din clauza
SELECT.

Care este clientul care a cumpărat cele mai multe produse ?


SELECT DenCl, COUNT(DISTINCT CodPr) AS Nr_Produse
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.Nrfact
GROUP BY DenCl
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 167

HAVING COUNT(DISTINCT CodPr) = (SELECT MAX(Nr) FROM


(SELECT CodCl, COUNT(DISTINCT CodPr) AS Nr FROM facturi F
INNER JOIN liniifact LF ON F.NrFact=LF.Nrfact
GROUP BY CodCl)
TEMP1)

Care este judeţul în care berea s-a vândut cel mai bine ?
SELECT Judet, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari_Bere
FROM judete J INNER JOIN coduri_postale CP ON J.Jud=CP.Jud
INNER JOIN clienti C ON CP.CodPost=C.CodPost
INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
INNER JOIN produse P ON LF.CodPr=P.CodPr
WHERE Grupa='Bere'
GROUP BY Judet
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) =
(SELECT MAX(Vinzari)
FROM
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM coduri_postale CP INNER JOIN clienti C ON CP.CodPost=C.CodPost
INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
INNER JOIN produse P ON LF.CodPr=P.CodPr
WHERE Grupa='Bere'
GROUP BY Jud) TEMP1)

Care sunt clienţii cu valoarea vânzărilor peste medie ?


Soluţia 1:
SELECT DenCl, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY DenCl
HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) >=
(SELECT Vinzari / NrClienti FROM
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
) TEMP1,
(SELECT COUNT(DISTINCT CodCl) AS NrClienti FROM facturi) TEMP2)
Soluţia 2:
SELECT DenCl, VINZ_CL.Vinzari
FROM (SELECT DenCl, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM clienti C INNER JOIN facturi F ON C.CodCl=F.CodCl
INNER JOIN liniifact LF ON F.NrFact=LF.NrFact
INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY DenCl ) VINZ_CL,
168 Baze de date I

(SELECT DISTINCT Vinzari / NrClienti AS Medie_Vinz


FROM (SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
) X, (SELECT COUNT(DISTINCT CodCl) AS NrClienti FROM facturi) Y
) MEDIE_VINZ
WHERE VINZ_CL.Vinzari >= MEDIE_VINZ.Medie_Vinz

Care este factura cu cea mai mică valoare peste cea medie ?
SELECT NrFact, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS ValFact
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY NrFact HAVING SUM(Cantitate * PretUnit * (1+ProcTVA)) =
(SELECT MIN(ValFact)
FROM (SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS ValFact
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY NrFact) TEMP1
WHERE ValFact >
(SELECT Vinzari / NrFacturi AS ValMedie
FROM (SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS
Vinzari, COUNT(DISTINCT NrFact) AS NrFacturi
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
) TEMP1))

Apelăm şi la o soluţie care să afişeze toate facturile cu valoarea peste medie, în ordinea
crescătoare a acestei valori. Prima factură din rezultat – figura 7.9 - va fi răspunsul la întrebarea
formulată.
SELECT NrFact, ValFact, ValMedie
FROM (SELECT NrFact, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS ValFact
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY NrFact) TEMP1,
(SELECT ROUND(Vinzari / NrFacturi,0) AS ValMedie
FROM
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari, COUNT(DISTINCT NrFact)
AS NrFacturi FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
) MEDIE1) MEDII
WHERE ValFact > ValMedie
ORDER BY ValFact

Figura 7.9. Facturile cu valori peste medie


Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 169

Care este clientul cel mai datornic (ce are cel mai mare rest de plată) ?
Fondul problemei este asemănător celei precedente. În interogare obţinem o tabelă ad-hoc cu
diferenţa de încasat pe clienţi (TEMP1), şi o alta ce conţine cea mai mare diferenţă de încasat pentru
un client (TEMP2). Cele două tabele sunt joncţionate după diferenţă şi, în final, pentru a afla
denumirea clientului, adăugăm în joncţiune tabela CLIENŢI.
SELECT DenCl, Vinzari, Incasari, DeIncasat
FROM
(SELECT FACTURAT.CodCl, Vinzari, COALESCE(Incasari, 0) AS Incasari,
Vinzari - COALESCE(Incasari, 0) AS DeIncasat
FROM
(SELECT CodCl, SUM(Cantitate * PretUnit * (1+ProcTVA))
AS Vinzari
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY CodCl) FACTURAT LEFT OUTER JOIN
(SELECT CodCl, SUM(Transa) AS Incasari FROM facturi f INNER JOIN
Incasfact i ON i.NrFact=f.NrFact
GROUP BY CodCl) INCASAT
ON FACTURAT.CodCl=INCASAT.CodCl
) TEMP1 INNER JOIN
(SELECT MAX (Vinzari - COALESCE(Incasari, 0)) AS DifMax
FROM (SELECT CodCl, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM facturi f INNER JOIN liniifact lf ON f.NrFact=lf.NrFact
INNER JOIN produse p ON lf.CodPr=p.CodPr
GROUP BY CodCl) FACTURAT LEFT OUTER JOIN
(SELECT CodCl, SUM(Transa) AS Incasari FROM facturi f INNER JOIN
Incasfact i ON i.NrFact=f.NrFact GROUP BY CodCl) INCASAT
ON FACTURAT.CodCl=INCASAT.CodCl) TEMP2
ON TEMP1.DeIncasat=TEMP2.DifMax INNER JOIN CLIENTI
ON TEMP1.CodCl=CLIENTI.CodCl

7.4 SUBCONSULTĂRI SCALARE ÎN CLAUZA SELECT


Standardele SQL fac trimitere şi la interogările scalare ce pot fi definite ca fraze SELECT ce
obţin un rezultat alcătuit dintr-o singură linie şi o singură coloană. Utilitatea acestora este vizibilă în
expresii complexe. Deşi este prima oară când pomenim de interogări scalare, le-am folosit de multe ori
până acum în clauzele WHERE şi HAVING. Ceea ce vom parcurge în continuare se referă la
includerea unei interogări scalare în clauza SELECT a unei interogări.

Care sunt totalurile salariilor tarifare şi ale sporurilor pe luna iulie 2008 pentru întreaga
firmă ?
Soluţia „clasică” este:
SELECT SUM(SalTarifar) AS Total_Sal_Tarifar,
SUM ( COALESCE(SporVechime,0) + COALESCE(SporNoapte,0)+
170 Baze de date I

COALESCE(SporCD,0)+COALESCE(AlteSpor,0) ) AS Total_Sporuri_Iulie
FROM personal2 INNER JOIN sporuri ON personal2.Marca= sporuri.Marca
WHERE An=2008 AND Luna=7
Întrucât toate persoanele din tabela PERSONAL au lucrat în luna iulie 2008 (nu a plecat nici
un angajat din organizaţie), se poate formula şi interogarea:
SELECT SUM(SalTarifar) AS Total_Sal_Tarifar,
(SELECT SUM ( COALESCE(SporVechime,0) + COALESCE(SporNoapte,0)+
COALESCE(SporCD,0)+ COALESCE(AlteSpor,0))
FROM sporuri
WHERE An=2008 AND Luna=7) AS Total_Sporuri_Iulie
FROM PERSONAL2
A doua coloană a rezultatului este obţinută printr-o interogare scalară care operează oarecum
independent de fraza SELECT principală, furnizându-i însă o valoare.

Care sunt totalurile vânzărilor şi încasărilor ?


Formulăm o soluţie curioasă PostgreSQL:
SELECT
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari
FROM liniifact lf INNER JOIN produse p ON lf.CodPr=p.CodPr) AS Facturat,
(SELECT SUM (Transa) FROM incasfact) AS Incasat
Fraza SELECT conţine două subconsultări scalare, una care extrage totalul vânzărilor, cealaltă, totalul
încasărilor (Reamintim că în PostgreSQL clauza FROM poate lipsi).

Care este ziua în care s-au emis cele mai multe facturi ?
SELECT DataFact, COUNT(*) AS Nr_Facturi,
(SELECT MAX(NrF) FROM (
SELECT COUNT(*) AS NrF FROM facturi GROUP BY DataFact) T1 ) AS NrMax
FROM facturi
GROUP BY DataFact
HAVING COUNT(*) >= (SELECT MAX(NrF) FROM
(SELECT COUNT(*) AS NrF FROM facturi GROUP BY DataFact) T2 )
Valorile coloanelor Nr_Facturi şi NrMax ale rezultatului din figura 7.10 sunt furnizate de două
subconsultări scalare, una care calculează numărul zilnic al facturilor, iar a doua a doua numărul
maxim de facturi emise într-o zi.

Figura 7.10. Zilele în care s-au întocmit cele mai multe facturi

Care sunt localităţile în care se află sediul fiecărui client ?


Revenim la un exemplu banal, rezolvat atât de simplu prin joncţiune sau subconsultare pentru a
demonstra cât de mult ne putem complica viaţa în SQL (deşi au fost exemple mai convingătoare). Ei
bine, această problemă supărător de simplă poate fi rezolvată şi prin subconsultări scalare:
SELECT DenCl,
(SELECT Loc FROM coduri_postale WHERE CodPost=CLIENTI.CodPost) AS Loc
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 171

FROM CLIENTI ORDER BY 2


Figura 7.11 este edificatoare în ceea pe priveşte corectitudinea rezultatului interogării. Unicul
merit al exemplului este, probabil, de a demonstra că o subconsultare scalară poate fi corelată cu tabela
din clauza FROM a frazei principale.

Figura 7.11. Subconsultare scalară corelată cu tabela din fraza principală

Pentru cât la sută dintre clienţi s-au întocmit, zilnic, facturi ?


SELECT DataFact, COUNT(DISTINCT CodCl) AS Nr_Clienti,
(SELECT COUNT(*) FROM clienti) AS Nr_Total_Clienti,
(COUNT(DISTINCT CodCl) * 100) / (SELECT COUNT(*) FROM CLIENTI) AS Procent
FROM facturi
GROUP BY DataFact

Figura 7.12. Procentajul zilnic al clienţilor pentru care există facturi

Pentru a obţine procentul care interesează se împarte rezultatul calculat de funcţia COUNT
pentru fiecare grup (zi calendaristică) la valoarea extrasă de interogarea scalară.

Care este evoluţia zilnică a vânzărilor, raportat la ziua de vânzări anterioară ?


În problema de mai sus diferenţa era calculată între ziua curentă şi ziua precedentă. Astfel încât, toate
zilele de luni erau raportate la zero, deoarece, pentru cea mai mare parte a firmelor, duminica nu se
lucrează. Acum dorim ca diferenţa să fie calculată între vânzările din ziua curentă şi cele din
precedenta zi de vânzări, adică între luni şi vineri s.a.m.d., după cum se observă în figura 7.13.
172 Baze de date I

Figura 7.13. Diferenţele dintre două zile consecutive de vânzări


SELECT DataFact AS Zi,
SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Vinzari_Zi_Curenta,
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA))
FROM facturi F2 INNER JOIN liniifact LF2 ON F2.NrFact=LF2.NrFact
INNER JOIN produse P2 ON LF2.CodPr=P2.CodPr
WHERE DataFact IN (SELECT MAX(DataFact) FROM facturi F3
INNER JOIN liniifact LF3 ON F3.NrFact=LF3.NrFact
INNER JOIN produse P3 ON LF3.CodPr=P3.CodPr
WHERE DataFact < F.DataFact AND (Cantitate * PretUnit * (1+ProcTVA)) > 0)
) AS Vinzari_Zi_Precedenta,
SUM(Cantitate * PretUnit * (1+ProcTVA)) - COALESCE(
(SELECT SUM(Cantitate * PretUnit * (1+ProcTVA))
FROM facturi F2 INNER JOIN liniifact LF2 ON F2.NrFact=LF2.NrFact
INNER JOIN produse P2 ON LF2.CodPr=P2.CodPr
WHERE DataFact IN
(SELECT MAX(DataFact) FROM facturi F3
INNER JOIN liniifact LF3 ON F3.NrFact=LF3.NrFact
INNER JOIN produse P3 ON LF3.CodPr=P3.CodPr
WHERE DataFact < F.DataFact AND (Cantitate * PretUnit * (1+ProcTVA)) > 0)
),0) AS Diferenta
FROM facturi F INNER JOIN liniifact LF ON F.Nrfact=LF.NrFact
INNER JOIN produse P ON LF.CodPr=P.CodPr
GROUP BY DataFact
Artificiul necesar extragerii precendentei zi de vânzări se găseşte în subconsultările din cele
două interogări scalare. Prn joncţiunea instanţelor F3 şi LF3 ale tabelelor FACTURI şi LINIIFACT şi
condiţia DataFact < F.DataFact, vor fi extrase toate liniile din facturi întocmite înaintea datei curente
(F este instanţa principală a tabelei FACTURI – cea care indică ziua curentă). Ca măsură suplimentară
se precauţie se verifică dacă Cantitate * PretUnit * (1+ProcTVA)) > 0 (nu cumva ca să fie vreo zi în care
apare o factură în care liniile să prezinte Cantitate = 0, deşi situaţia aceasta este greu de imaginat).
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 173

Dintre zilele de vânzare ce precedă ziua curentă, cea mai apropiată (calendaristic) se extrage prin
funcţia MAX.
Nu ştiu ce ziceţi dumneavoastră, dar mie-mi place…

7.5 ACTUALIZAREA TABELELOR PRIN SUBCONSULTĂRI


Pentru o mai bună priză la public, în cele ce urmează apelăm la ceea ce se numeşte de-
normalizarea bazei de date. În practică, redundanţa datelor este, uneori, condamnată cu jumătate de
gură sau chiar apreciată de mulţi specialişti. Aceasta deoarece un atribut redundant, adăugat unei
tabele, poate duce la evitarea joncţiunilor frecvente, mari consumatoare de resurse. Aplecându-ne
asupra bazei noastre de date, dacă tot ne apucăm de treabă, în tabela FACTURI adaugăm nu mai puţin
de trei atribute:
ValTotală reprezintă valoarea totală (inclusiv TVA) a facturii;
Reduceri: într-o ţară civilizată, ca a noastră, dacă un client plăteşte o factură înainte de
termenul obişnuit (să zicem zece zile), îi putem acorda o reducere de 5% pentru că ne-am
procopsit rapid cu lichidităţi.
Penalităţi: este opusul atributului anterior. Prin contract sau prin lege, dacă un client este mai
reţinut în a plăti facturile la timp, i se pot aplica penalităţi, fără a avea însă certitudinea că le-
am putea încasa vreodată.
Practic, prin aceste noi atribute, urmărirea încasării facturilor se schimbă sensibil. Valoarea de încasat
dintr-o factură este valoarea totală plus penalităţi minus reduceri. Asta e vestea bună. Vestea proastă
ţine de faptul că atributul ValTotală n-ar trebui să se modifice decât la actualizarea tabelei
LINIIFACT, nu ? Altminteri, dacă utilizatorul ar modifica, prin UPDATE sau alt mijloc, acest atribut,
ar apărea un decalaj supărător între liniile din facturi şi valorile acestora. Actualizarea automată a unor
atribute calculate este unul din scopurile declarate ale declanşatoarelor (trigger-elor). Adăugăm cele
trei atribute tabelei FACTURI:
ALTER TABLE FACTURI ADD ValTotala DECIMAL(16,2) ;
ALTER TABLE FACTURI ADD Reduceri DECIMAL(15,2) ;
ALTER TABLE FACTURI ADD Penalizari DECIMAL(15,2) ;
Acum, dacă tot le-am creat, să le “umplem”. Astfel, pentru calculul valorii totale a unei facturi
avem nevoie de o interogare corelată după cum urmează:
UPDATE facturi
SET ValTotala = (
SELECT SUM(Cantitate * PretUnit * (1+ProcTVA))
FROM liniifact LF INNER JOIN produse P ON LF.CodPr=P.CodPr
WHERE NrFact = facturi.NrFact
)
Subconsultarea ia în calcul cantitatea, preţul unitar şi procentul TVA pentru fiecare produs
vândut. Prin corelare se vor lua în considerare numai liniile din LINIIFACT (şi PRODUSE)
corespunzătoare facturii curente (linia curentă din FACTURI).
Cât priveşte atributul Reduceri, instituim următoarea regulă: se acordă o reducere de 5%
pentru toate tranşele unei facturi încasate în termen de 15 zile de la data vânzării.
UPDATE FACTURI SET Reduceri = (
174 Baze de date I

SELECT SUM ( CASE WHEN DataInc <= DataFact + INTERVAL '15' DAY
THEN Transa * 0.05 ELSE 0 END)
FROM incasfact INCF INNER JOIN incasari I ON INCF.CodInc=I.CodInc
INNER JOIN facturi F2 ON INCF.Nrfact=F2.NrFact
WHERE F2.NrFact = facturi.NrFact
)
Complicăm un pic cazul. Acordăm 10% pentru tranşele încasate în mai puţin de 15 zile de la
data vânzării, 9% pentru 16 zile şi 8% pentru 17 zile. Soluţia este:
UPDATE facturi SET Reduceri = (
SELECT SUM ( CASE
WHEN DataInc <= DataFact + INTERVAL '15' DAY
THEN Transa * 0.1
WHEN DataInc <= DataFact + INTERVAL '16' DAY
THEN Transa * 0.09
WHEN DataInc <= DataFact + INTERVAL '17' DAY
THEN Transa * 0.08
ELSE 0 END)
FROM incasfact INCF INNER JOIN incasari I
ON INCF.CodInc=I.CodInc
INNER JOIN facturi F2 ON INCF.Nrfact=F2.NrFact
WHERE F2.NrFact = facturi.NrFact
)
Nu numai modificările pot fi operate utilizând subconsultări, corelate sau nu, ci şi ştergerile.
Spre exemplu, vrem să ştergem din tabela FACTURI liniile care nu au nici un copil în LINIIFACT:
DELETE FROM facturi WHERE NOT EXISTS
(SELECT 1 FROM liniifact WHERE liniifact.NrFact = facturi.NrFact)

O soluţie mai puţin impresionantă utilizează tot o subconsultare, dar ceva mai puţin corelată:
DELETE FROM facturi WHERE NrFact NOT IN
(SELECT DISTINCT NrFact FROM liniifact)
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 175

S-ar putea să vă placă și