Documente Academic
Documente Profesional
Documente Cultură
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
Obiective:
Rezultate aşteptate:
Nici unul
Invăţământ la distanţă – Anul II – Contabilitate şi Informatică de Gestiune - 2009/2010 3
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
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.
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 n
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.
Comenzi Comenzi
Aplicaţie Aplicaţie Aplicaţie
autonome autonome
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
Definirea structurii
interne de stocare Baza de date memorată pe disc
(Schema internă)
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.
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.
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
Î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.
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.
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.
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.
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ă.
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
Î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
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
CAPITOLUL 3
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
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
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
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
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ă.
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
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.
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.
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.
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
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
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.
22
[Saleh94], p.76. Vezi şi [Garcia-Molina s.a 01], p.89
38 Baze de date I
Î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
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ă.
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).
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).
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.
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
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.
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şă.
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.
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
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
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.
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
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).
Î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.
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
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.
Cele două relaţii obţinute sunt: SUPERVIZORI {Supervizor, ProiectNr} şi MEMBRI { Super-
vizor, Membru } reprezentate în figura 3.18.
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.
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
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.
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
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)
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.
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
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.
Î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.
39
[Wu92]
66 Baze de date 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.
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.
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
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.
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
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
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
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.
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).
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
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).
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}.
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
- 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.
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.
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.
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.
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.
Capitolul 4
Obiective:
Rezultate aşteptate:
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.
Consultare
Aplicaţii-program Apeluri-sistem Structura bazei
(interogare)
Gestionarul fişierelor
Fişier de date
Dicţionar
de
Baza de date 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.
52
Preluare din [Korth&Silberschatz88], p.19
88 Baze de date I
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.
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.
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
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.
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.
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:
Rezultate aşteptate:
Î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.
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).
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.
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
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.
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.
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.
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
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.
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.
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).
Î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 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)
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
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.
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
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.
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.
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.
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.
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.
Capitolul 6
Obiective:
Rezultate aşteptate:
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.
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
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
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.
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
Î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
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.
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'
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 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.
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%'
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%'
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
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 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.
î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.
Î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.
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
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 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
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
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 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
După cum puteţi observa, în lipsa clauzei ORDER BY zilele nu sunt dispuse în ordine
crescătoare (sau descrescătoare)
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
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
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
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
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.
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
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
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
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'
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.
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.
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
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
Pentru acest exemplu, interesează însă formatul de prezentare din figura 6.33.
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.
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.
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
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
Capitolul 7
Obiective:
Rezultate aşteptate:
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
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')
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
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'
))
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
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.
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
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
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
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)
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.
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
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
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)
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:
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
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 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 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 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
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
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 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
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ă.
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…
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