0% au considerat acest document util (0 voturi)
6 vizualizări126 pagini

07 Python Data Access and Processing

Documentul oferă o prezentare detaliată a conceptelor fundamentale legate de gestionarea bazelor de date, inclusiv tipurile de date, modelele de date și sistemele de gestionare a bazelor de date (DBMS). Se discută despre structura logică și fizică a bazelor de date, precum și despre limbajele utilizate pentru manipularea datelor, cum ar fi SQL. De asemenea, documentul abordează arhitectura DBMS și importanța schemelor de baze de date în organizarea și gestionarea eficientă a datelor.

Încărcat de

Sparrow Jack
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
6 vizualizări126 pagini

07 Python Data Access and Processing

Documentul oferă o prezentare detaliată a conceptelor fundamentale legate de gestionarea bazelor de date, inclusiv tipurile de date, modelele de date și sistemele de gestionare a bazelor de date (DBMS). Se discută despre structura logică și fizică a bazelor de date, precum și despre limbajele utilizate pentru manipularea datelor, cum ar fi SQL. De asemenea, documentul abordează arhitectura DBMS și importanța schemelor de baze de date în organizarea și gestionarea eficientă a datelor.

Încărcat de

Sparrow Jack
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

Contents

1.1. Datele şi scopul lor ........................................................................................................................... 3


Bazele de date ............................................................................................................................................ 4
Modele de date ........................................................................................................................................... 4
Sistemul de gestionare a bazelor de date (DBMS) .............................................................................. 5
Schema bazei de date................................................................................................................................ 6
Limbajele bazelor de date ....................................................................................................................... 7
Bazele de date şi programele de aplicaţie ............................................................................................ 7
1.2. Serializarea şi deserializarea ........................................................................................................... 9
Modulul pickle ......................................................................................................................................... 10
Modulul marshal ..................................................................................................................................... 13
Modulul shelve ......................................................................................................................................... 14
Modulul json ............................................................................................................................................. 15
1.3. Stocarea şi transportul de date ................................................................................................... 16
RAID ........................................................................................................................................................... 17
Organizarea fişierelor şi operaţiuni asupra acestora ...................................................................... 20
Indexarea .................................................................................................................................................. 21
Hashing-ul................................................................................................................................................. 24
Tranzacţiile .............................................................................................................................................. 27
Tranzacţiile paralele .............................................................................................................................. 29
Copia de rezervă a datelor ..................................................................................................................... 33
Recuperarea bazei de date .................................................................................................................... 34
1.4. Analiza datelor şi regresia............................................................................................................. 38
Jupyter Notebook .................................................................................................................................... 39
Biblioteca pandas .................................................................................................................................... 41
Biblioteca Matplotlib .............................................................................................................................. 53
Biblioteca NumPy .................................................................................................................................... 55
Biblioteca SciPy ....................................................................................................................................... 57
Bilblioteca scikit-learn ........................................................................................................................... 58
Regresia..................................................................................................................................................... 60
2.1. Manipularea şi salvarea conexiunilor ......................................................................................... 63
Conexiunea la baza de date în modulul sqlite3 ................................................................................. 64
Obiectul cursor ........................................................................................................................................ 65
Crearea bazei de date în modulul sqlite3 ........................................................................................... 66
Conexiunea la baza de date în modulul mysql................................................................................... 67
2.2. Manipularea datelor în baza de date relaţională .................................................................... 69
Inserarea datelor în baza de date (INSERT) ....................................................................................... 70
Selectarea datelor din baza de date (SELECT) ................................................................................... 71
Actualizarea bazei de date (UPDATE) ................................................................................................. 72
Ştergerea datelor din baza de date (DELETE).................................................................................... 73
Exerciţii ..................................................................................................................................................... 76
2.3. Lucrul cu bazele de date NoSQL .................................................................................................. 79
Modelele bazelor de date NoSQL .......................................................................................................... 81
MongoDB ................................................................................................................................................... 83
Exerciţiu .................................................................................................................................................... 86
2.4. Lucrul cu cozile de mesaje ............................................................................................................ 87
Cozile în MongoDB .................................................................................................................................. 88
Cozile în RabbitMQ .................................................................................................................................. 90
3.1. Tabele şi obiecte ............................................................................................................................. 96
Obiectele ................................................................................................................................................... 97
Atributele .................................................................................................................................................. 99
Relaţiile ................................................................................................................................................... 101
3.2. Maparea obiectual-relaţională .................................................................................................. 104
Django ORM ............................................................................................................................................ 105
3.3 Utilizarea cadrului SQLAlchemy ................................................................................................. 114
Conexiunea ............................................................................................................................................. 115
Crearea tabelelor .................................................................................................................................. 116
Execuţia comenzilor SQL...................................................................................................................... 118
SQLAlchemy ORM .................................................................................................................................. 122
Exerciţiu .................................................................................................................................................. 125

Page 2 of 126
1.1. Datele şi scopul lor

Rezumat
• Modul în care sunt stocate datele depinde de tipurile de date care sunt predefinite.
• O bază de date este o colecţie de date stocate sistematic, aranjate în scopul obţinerii
răspunsurilor mai simple la interogările de sistem.
• Proiectarea, conceperea şi implementarea bazei de date depind de modelul de date.
• Sistemul de gestionare a bazelor de date (în engleză DBMS – Database Management System),
permite afişarea fizică a bazei de date, în conformitate cu structura logică selectată, adică în
conformitate cu modelul.
• Arhitectura DBMS (ANSI/SPARC) distinge trei niveluri:

o nivelul intern - arată dispunerea fizică a datelor în memoria externă;


o nivelul conceptual - conceptul logic al bazei de date (schema de date);
o nivelul extern – generează tipuri de date locale (vizualizări).

• Schema bazei de date descrie în mod logic datele întregii baze de date, constrângerile acestora
şi relaţiile dintre ele.
• Există mai multe tipuri de limbaje pentru comunicarea cu bazele de date:

o DDL - definirea datelor (crearea schemei);


o DML - adăugarea, actualizarea şi ştergerea datelor (manipularea datelor);
o QL - căutarea interactivă în baze de date utilizând o serie de declaraţii (crearea
interogării).

• Cel mai popular limbaj integrat pentru comunicarea cu bazele de date este SQL.
• Cea mai comună arhitectură a programelor de aplicaţii este o arhitectură pe trei niveluri, care
include:
o nivelul bazei de date - o bază de date cu limbaje de interogare definite;
o nivelul de aplicaţie - server de aplicaţii cu o vizualizare abstractă a bazei de date;
o nivelul de prezentare - date sub formă de vizualizări la cererea utilizatorului.

Acest curs va explica unde şi în ce mod pot fi stocate datele pe un computer şi cum pot fi manipulate
acestea. Pe lângă familiarizarea cu analiza şi regresia datelor, cursul va include şi lucrul cu baze de date
relaţionale şi nerelaţionale, lucrul cu cozile de mesaje, precum şi lucrul cu date folosind soluţii ORM,
dintre care cea mai populară soluţie este SQLAlchemy.
Tot ceea ce reprezintă o idee, un fapt sau un eveniment într-o anumită înregistrare se numeşte dată.
Datele pot fi folosite pentru a descrie o entitate, un fapt izolat sau o observaţie făcută prin observarea
unui proces. Scopul de bază al datelor se reflectă în utilizarea lor pentru interpretare, prelucrare şi
comunicare atât din partea oamenilor, cât şi din partea maşinilor.

Aşa cum am putut observa într-unul din cursurile anterioare, limbajul de programare Python are mai
multe tipuri de date încorporate, care diferă prin notaţie şi scop:

• tip de date numerice: int, float, complex;


• tip de date text: str;
• tip de date logice: bool;
• tip de date binare: bytes, bytearray, memoryview;
• secvenţă: list, tuple, range;
• dicţionar: dict;
• set: set, frozenset.

Page 3 of 126
Tipul de date depinde de metoda de stocare a acestuia, precum şi de setul de operaţii care pot fi
efectuate pe acesta. Fiecare aplicaţie necesită strategii diferite de stocare a datelor; prin urmare, este
foarte important să se ia în considerare factori precum cantitatea de date, frecvenţa utilizării lor şi
alegerea tipului de date care le defineşte cel mai exact.

Bazele de date

Pentru o mai bună organizare şi o mai mare accesibilitate, este de dorit ca datele conexe să fie stocate
într-un singur loc. În acest scop, se utilizează bazele de date. Baza de date este o colecţie de date
stocate sistematic, aranjate în scopul trimiterii răspunsurilor mai simple la interogările de sistem. Datele
stocate în baza de date sunt prezentate sub formă de tabele în care diferenţiem rânduri şi coloane.
Aceste date sunt stocate în memoria externă a computerului şi sunt disponibile pentru diferiţi utilizatori
şi programe de aplicaţii.
Avantajul acestui tip de stocare este utilizarea unui software comun care foloseşte structura logică a
bazei de date, ceea ce înseamnă că nu este necesară cunoaşterea fizică a datelor pentru a le putea
gestiona. Structura logică a datelor se bazează pe conexiunea lor logică, nu pe stocarea lor fizică
pe medii.

Modele de date

Pentru afişarea structurii logice a datelor, se folosesc unele dintre modelele de date. Modelele de
date sunt utilizate pentru conceperea, proiectarea şi implementarea unei baze de date. Aceste modele
pot fi relaţionale, de reţea, ierarhice sau obiectuale.
• Modelul relaţional este cel mai comun model utilizat pentru modelarea problemelor reale şi
pentru gestionarea dinamică a datelor. Acest model explică relaţiile dintre datele dintr-o bază
de date, utilizând noţiunea matematică de relaţie.
• Modelul de reţea este prezentat sub forma unui grafic orientat, ale cărui noduri reprezintă tipul
de înregistrare, iar căile acestor noduri sunt relaţiile dintre ele.
• Modelul ierarhic se bazează pe relaţia superior-subordonat. Reprezentarea acestui model se
bazează pe un singur arbore sau pe un set de arbori. Acest model a provenit din cel de reţea şi
reprezintă subtipul său.
• Modelul obiectual este construit pe conceptele de programare orientată pe obiecte. O bază
de date este un set de obiecte permanente care au propriile atribute şi metode. Fiecare dintre
obiecte aparţine uneia dintre clasele care pot stabili o relaţie între moştenire, agregare şi
utilizare.

Imaginea 1.1 Modele de date1

Page 4 of 126
Sistemul de gestionare a bazelor de date (DBMS)

Sistemul de gestionare a bazelor de date - DBMS (Database Management System) este responsabil
pentru stocarea eficientă şi recuperarea datelor utilizatorilor din baza de date, respectând în acelaşi timp
anumite măsuri de securitate. Funcţia sa se reflectă în afişarea fizică a datelor în conformitate cu
structura logică sau modelul selectat. Acest sistem software elimină dezavantajele vechi, prelucrarea de
date clasică, care se baza pe un sistem de gestionare a fişierelor.

Avantajele noului DBMS se reflectă în următoarele caracteristici:

• entităţi reale - folosesc entităţi din lumea reală pentru a-şi proiecta arhitectura, inclusiv stările
şi comportamentele;
• tabele bazate pe relaţii - permite formarea tabelelor de relaţii între entităţi în sistem;
• izolarea datelor şi aplicaţiilor - bazele de date reprezintă o entitate activă în raport cu datele
în sine, care sunt pasive;
• redundanţă mai mică - acest DBMS respectă regulile de normalizare, care separă relaţia de
fiecare dată când unul dintre atribute se repetă;
• consecvenţă - se referă la consecvenţa relaţiilor dintre entităţi;
• limbajul de interogare - acest limbaj facilitează preluarea şi manipularea datelor;
• proprietăţile ACID - urmează conceptul de atomicitate, consecvenţă, izolare şi durabilitate
(atomicity, consistency, isolation and durability); aceste concepte se aplică tranzacţiilor care
manipulează datele din baza de date şi servesc la menţierea bazei de date în cazul în care mai
multe tranzacţii acţionează simultan sau în caz de eşec; aceste proprietăţi vor fi discutate în
detaliu mai târziu în curs;
• accesul multi-utilizator şi simultan - este asigurat accesul multi-utilizator, precum şi
prelucrarea simultană a datelor;
• vizualizările multiple - moduri de vizualizare multiple pentru utilizatori diferiţi - utilizatorul
poate alege să vadă doar informaţiile de care are nevoie;
• securitatea - se referă la constrângeri privind introducerea datelor şi accesul la date.

Pentru a înţelege mai bine lucrul cu bazele de date, s-a făcut o clasificare generală a DBMS-ului pe
niveluri, pe baza criteriilor de acces de utilizator. Arhitectura sistemului de gestionare a bazelor de
date (arhitectura ANSI/SPARC) distinge trei niveluri: intern, conceptual şi extern (imaginea 1.2.).

Imaginea 1.2. Arhitectura sistemului de gestionare a bazelor de date (ANSI/SPARC)

Page 5 of 126
• Nivelul intern (fizic) prezintă modul în care datele sunt aranjate în memoria externă. Datele
de la acest nivel pot fi descompuse în mai multe niveluri de abstractizare, care pot fi căi sau
fişiere specifice. Acest nivel poate fi văzut doar de programatorii care au dezvoltat DBMS.

• Nivelul conceptual este definit de schema de date conceptuală. La acest nivel este aranjat
contextul logic al întregii baze de date. Aşa arată baza de date din punctul de vedere al
programatorului, adică al administratorului de sistem.
• Nivelul extern - acest nivel defineşte toate tipurile de date locale şi relaţiile dintre ele, în
conformitate cu modelul selectat. Vizualizarea reprezintă o înregistrare a unei definiţii locale.
Acesta este nivelul la care programatorii şi alţi utilizatori folosesc aplicaţia.

Notă

O vizualizare (view) în bazele de date este o modalitate de reprezentare a datelor folosind un grup de comenzi
trimise către un server. Pe baza comenzii date, serverul afişează exact datele cerute de utilizator. Vizualizările
se mai numesc şi tabele virtuale. Definirea vizualizării creează o copie a tabelului original ale cărui date, ca şi
în cazul tabelelor originale, pot fi actualizate şi şterse.

Schema bazei de date

Schema bazei de date este o structură scheletică care reprezintă o reprezentare logică a unei baze de
date întregi. Utilizând schema, designerii prezintă organizarea datelor din baza de date şi relaţiile lor
reciproce. În cadrul schemei bazei de date, sunt definite şi constrângerile care ar trebui aplicate datelor.
Designerii de baze de date creează scheme pentru a ajuta programatorii să înţeleagă baza de date şi să
o facă utilă.
Schemele bazei de date pot fi clasificate în două categorii (imaginea 1.3.):

• schema fizică - stocarea fizică a datelor şi forma stocării respective (indexuri, fişiere);
• schema logică - defineşte tabele, vizualizări şi constrângeri de integritate - sau, cu alte cuvinte,
orice constrângeri logice care trebuie aplicate datelor stocate.

Aceste două niveluri îşi îndeplinesc funcţiile în mod izolat. Dacă am face modificări la datele din tabel,
acestea nu ar afecta datele stocate pe disc. La fel, dacă am înlocui hard disk-ul cu memoria SSD, schema
de date logice ar rămâne neschimbată.

Imaginea 1.3. Structura schemei bazei de date

Page 6 of 126
Limbajele bazelor de date

Limbajele speciale sunt utilizate pentru comunicarea între utilizator şi DBMS în aplicaţie. În funcţie de
scopul lor, putem clasifica aceste limbaje în trei categorii:

• limbajul de definire a datelor (DDL – data definition language) - serveşte la definirea


structurii logice a datelor şi a relaţiei dintre acestea; astfel de limbaje sunt utilizate de
administratori în scopuri de schemă sau de înregistrare a vizualizării; exemple de astfel de
limbaje sunt Pascal, C şi PL/I;
• limbajul de manipulare a datelor (DML – data manipulation language) - folosit pentru a
adăuga, şterge şi actualiza date în baza de date. Este deseori un sublimbaj al unui alt limbaj de
bază de date, cum ar fi SQL;
• limbajul de interogare (QL – query language) – acest limbaj este utilizat pentru căutarea
interactivă a bazelor de date de către utilizatori; aceste interogări nu sunt procedurale, adică nu
arată procedura pentru obţinerea rezultatelor, ci rezultatele în sine.

Odată cu dezvoltarea tehnologiei, a crescut nevoia utilizatorilor pentru un limbaj de bază de date
integrat care va avea toate cele trei funcţionalităţi de mai sus. Cel mai comun limbaj de baze de date
integrate este SQL (Structured Query Language). Acest limbaj poate fi utilizat interactiv, utilizând un
mediu interpretat, sau se poate încadra într-un program de aplicaţie.

Notă

SQL a fost cândva considerat un limbaj neprocedural, până la versiunea lansată în anul 1999. Introducerea
acestei versiuni acceptă tipuri de date definite de utilizator, care sunt una dintre caracteristicile programării
orientate pe obiecte.

Bazele de date şi programele de aplicaţie

Pentru a înţelege mai bine rolul bazelor de date în cadrul unui program de aplicaţie, trebuie să se
definească arhitectura din aspectul aplicaţiei. În funcţie de utilizatorul final şi de scop, distingem mai
multe arhitecturi. O arhitectură cu un singur strat constă dintr-o singură entitate şi aceasta este DBMS.
Această arhitectură este destinată dezvoltatorilor şi designerilor ca utilizatori finali ale căror activităţi
sunt reflectate direct în DBMS. O arhitectură pe două niveluri, pe lângă DBMS, trebuie să aibă şi o
aplicaţie pe care o folosim pentru a accesa DBMS. Este important de reţinut că în cadrul acestei
arhitecturi, aplicaţiile şi bazele de date funcţionează izolat.
Cea mai comună arhitectură pentru proiectarea aplicaţiilor este arhitectura pe trei niveluri. Această
arhitectură separă nivelurile unele de altele în funcţie de criteriile de complexitate a utilizatorului şi de
modul în care utilizează datele din baza de date. Pe lângă nivelul bazei de date şi nivelul de aplicaţie,
această arhitectură include şi utilizatorii finali ca entitate (imaginea 1.4.).

Page 7 of 126
Imaginea 1.4. Arhitectura pe trei niveluri a sistemului informaţional din aspectul aplicaţiei

Ca şi în alte arhitecturi, în cea pe trei niveluri, fiecare nivel îşi îndeplineşte izolat rolul său:

• nivelul bazei de date - la acest nivel, există o bază de date împreună cu limbajele de procesare
a interogărilor; acesta defineşte, de asemenea, relaţiile dintre date, precum şi constrângerile
privind datele;
• nivelul de aplicaţie - acest nivel include serverul de aplicaţii şi programele care accesează baza
de date; pentru utilizator, acest nivel este o reprezentare abstractă a bazei de date;
• nivelul de prezentare - utilizatorii finali nu sunt conştienţi de existenţa unei baze de date în
afara acestui nivel; pe acest nivel, aplicaţia poate oferi mai multe vizualizări diferite ale bazei de
date (view-uri); toate vizualizările afişate la acest nivel sunt generate în stratul inferior al
aplicaţiei.

Arhitectura DBMS poate avea, de asemenea, mai mult de trei niveluri independente legate, care pot fi
modificate, actualizate sau înlocuite separat. Astfel de arhitecturi se numesc arhitecturi pe mai multe
niveluri.

1. [Link]

Page 8 of 126
1.2. Serializarea şi deserializarea

Rezumat
• Serializarea în programare este actul de a converti datele structurate dintr-o aplicaţie într-un
formular, într-un format care permite ulterior recuperarea structurii lor originale.
• Deserializarea este un proces care permite reconstruirea obiectului original dintr-un model
serializat.

• Cele două tipuri de bază de serializare sunt plate şi imbricate; aceste tipuri diferă în funcţie de
structura de stocare.
• Cel mai popular modul de serializare este pickle, care serializează datele în format binar.
Construcţia şi deconstrucţia datelor se efectuează în conformitate cu diferite protocoale al căror
suport depinde de versiunea Python. Funcţiile de bază ale modulului pickle sunt:

o funcţiile de serializare:

▪ dump() - serializarea datelor în obiecte;


▪ dumps() - serializarea datelor într-un string;

o funcţiile de deserializare:

▪ load() - deserializarea datelor în obiecte;


▪ loads() - deserializarea datelor într-un string.

• Modulul marshal efectuează serializarea datelor în format binar, cu adăugarea referinţelor la


fiecare dintre obiecte. Există, în principal, pentru citirea şi scrierea codului pseudo-compilat
pentru modulele Python din fişierele .pyc. Foloseşte aceleaşi funcţii ca un pickle.
• Modulul shelve se utilizează pentru serializarea şi deserializarea datelor în format dicţionar.
Acesta este un instrument excelent pentru introducerea datelor într-o bază de date. Permite ca
o valoare dintr-o pereche cheie-valoare să fie un obiect, în timp ce cheia din acea pereche
trebuie să fie un string obişnuit. Are toate funcţiile ca şi un dicţionar. Manipularea datelor se
realizează printr-o cheie dată.
• Modulul json este utilizat pentru serializare şi deserializare în format text. Se caracterizează
prin lizibilitatea şi interportabilitatea umană. Foloseşte acelaşi API ca şi alte module. Poate
serializa numai tipurile de date încorporate, nu şi cele definite de utilizator.

Serializarea în programare presupune actul de convertire a datelor structurate dintr-o aplicaţie într-un
formular, într-un format care permite ulterior recuperarea structurii lor originale. Acest proces se mai
numeşte redare a datelor. Cu alte cuvinte, pornirea procesului de serializare creează un model de date
în conformitate cu o schemă dată.

În domeniul calculatoarelor şi al programării, unitatea de bază pentru stocarea informaţiilor se numeşte


nod. Nodurile reprezintă părţi individuale ale unei structuri mai mari, cum ar fi listele sau arborii, despre
care vom discuta mai detaliat în lecţia următoare. În procesul de serializare, se creează un câmp pentru
fiecare nod din schema specificată în aplicaţie. Acest proces se repetă recursiv până când sunt create
toate câmpurile. Ca rezultat al recursivităţii, se formează un arbore de câmpuri care reprezintă o schemă
mapată. Fiecare obiect de câmp, creat ca urmare a pasului anterior, are un atribut care indică cu ce nod
din schemă este asociat.

Page 9 of 126
Deserializarea este un proces opus serializării, care permite reconstruirea obiectului original. Aceasta
converteşte formularul în date afişate în aplicaţie. Procedura este identică cu procedura de serializare,
adică maparea nodurilor în câmpuri într-un mod recursiv.

Înainte de a începe procesul de serializare, este important să stabilim cum vor fi structurate datele
serializate. Conform acestui criteriu, există două tipuri de serializare:

• plană - fişiere text, fişiere CSV, şiruri NumPy;


• imbricată - pickle, JSON, XML, YAML.

Diferenţa dintre structurile acestor două tipuri este prezentată în imaginea 2.1.

Imaginea 2.1. Prezentare generală a serializării plane şi imbricate în cod

Cel mai popular modul de serializare a datelor încorporat în limbajul de programare Python se
numeşte pickle.

Modulul pickle

Caracteristica acestui modul este că este specific Python. Aceasta înseamnă că programele care nu sunt
scrise în Python pot avea o problemă la deserializarea datelor care au fost serializate folosind acest
modul. O altă caracteristică a modulului pickle este că formatul său foloseşte o reprezentare binară
relativ compactă şi are capacitatea de a comprima datele. Pentru a putea utiliza funcţiile acestui modul,
mai întâi trebuie să îl introducem în program, ceea ce facem cu comanda import pickle.

Astfel, procesul de pickling este utilizat pentru a converti obiecte într-un cod binar şi de a le readuce din
codul binar în starea lor iniţială. Această construcţie şi deconstrucţie a obiectelor Python se realizează
în conformitate cu protocoalele definite (tabelul 2.1.).

Page 10 of 126
Protocoale de serializare în modulul pickle
Protocol Descriere

versiunea 0 a protocolului protocol original „lizibil de om”

format binar vechi, compatibil cu versiunile mai vechi


versiunea 1 a protocolului
de Python

introdus în Python 2.3; permite serializarea eficientă a


versiunea 2 a protocolului
noilor clase

introdus în Python 3.0; compatibil cu alte versiuni de


versiunea 3 a protocolului
Python 3

versiunea 4 a protocolului introdus în Python 3.4; acceptă stocarea obiectelor mari

Tabelul 2.1. Protocoale de serializare a datelor în modulul pickle

Pentru verificarea protocolului superior, precum şi care este protocolul implicit utilizat de versiunea
noastră de Python, apelăm modulul încorporat al
funcţiei pickle.HIGHEST_PROTOCOL şi pickle.DEFAULT_PROTOCOL.

Exemplu de utilizare a funcţiilor pentru verificarea protocolului în modulul pickle

1import pickle
2pickle.HIGHEST_PROTOCOL
3pickle.DEFAULT_PROTOCOL

Cele două funcţii de bază efectuate de interfaţa pickle sunt:

• funcţia de serializare:
o dump() - serializarea datelor în obiecte;
o dumps() - serializarea datelor într-un string;

• funcţia de deserializare:
o load() - deserializarea datelor în obiecte;
o loads() - deserializarea datelor într-un string.

Când serializăm datele în obiecte, folosim funcţia dump(). Această funcţie are doi parametri de intrare
obligatorii. Primul parametru reprezintă datele pe care dorim să le serializăm, în timp ce al doilea
reprezintă funcţia care scrie acele date într-un fişier.

Pentru a avea o imagine realistă a modului în care are loc serializarea, vom defini un n-tuplu de date,
pe care îl vom introduce mai târziu. Folosind funcţia încorporată open(), al cărei rol este descris în
lecţiile anterioare, vom defini fişierul books_file în care datele vor fi scrise ca obiecte. În cele din urmă,

Page 11 of 126
folosind funcţia dump(), transmitem datele de tip books pe care dorim să le serializăm şi
parametrul books_pickled, care scrie obiectele în fişierul dorit.

Exemplu de utilizare a funcţiei dump() în modulul pickle

1import pickle
2
3 books = ('J.K. Rowling', 'Harry Potter',
4 ((1997, 'Harry Potter and the Philosopher\'s Stone'),
5 (1998, 'Harry Potter and the Chamber of Secrets'),
6 (1999, 'Harry Potter and the Prisoner of Azkaban'),
7 (2000, 'Harry Potter and the Goblet of Fire'),
8 (2003, 'Harry Potter and the Order of the Phoenix'),
9 (2005, 'Harry Potter and the Half-Blood Prince'),
10 (2007, 'Harry Potter and the Deathly Hallows')))
11
12with open('books_file', 'wb') as books_pickled:
[Link](books, books_pickled)

Funcţia dumps() a modulului pickle este utilizată în mod similar. Această funcţie, spre deosebire
de dump(), are un singur parametru necesar, şi anume datele de serializare. Folosind această funcţie,
datele nu sunt scrise în fişier ca obiecte, ci sunt stocate sub formă de stringuri. Pentru a afişa formularul
de stocare, vom lista rezultatul funcţiei dumps() pe ecran.

Exemplu de utilizare a funcţiei dumps()în modulul pickle

1import pickle
2
3books = ('J.K. Rowling', 'Harry Potter',
4 ((1997, 'Harry Potter and the Philosopher\'s Stone'),
5 (1998, 'Harry Potter and the Chamber of Secrets'),
6 (1999, 'Harry Potter and the Prisoner of Azkaban'),
7 (2000, 'Harry Potter and the Goblet of Fire'),
8 (2003, 'Harry Potter and the Order of the Phoenix'),
9 (2005, 'Harry Potter and the Half-Blood Prince'),
10 (2007, 'Harry Potter and the Deathly Hallows')))
11
12print([Link](books))

Imaginea 2.2. Prezentarea formei de stocare a datelor folosind funcţia dumps()

Page 12 of 126
După cum putem vedea, serializarea datelor face ca datele care fac obiectul serializării să îşi schimbe
forma în timpul stocării. Din acest motiv, atunci când vrem să deserializăm acele date, adică să le
readucem la forma iniţială, trebuie să facem acest lucru folosind funcţia load() sau loads().

Funcţia load() este utilizată pentru a încărca datele stocate pe fişiere sub formă de obiecte. Încărcăm
datele din fişier folosind funcţia open(), folosind modul de citire a înregistrării binare. Transmitem
funcţia definită în acest fel funcţiei load() sub forma unui parametru, pentru a deserializa datele
încărcate.

Exemplu de utilizare a funcţiei load() în modulul pickle

1with open('books_file', 'rb') as books_unpickled:


2 [Link](books_unpickled)

Folosim loads() pentru a deserializa datele stocate sub formă de stringuri. Această funcţie primeşte
date de tip string ca parametru într-un formular creat folosind funcţia dumps() şi le readuce la forma
lor originală.

Exemplu de utilizare a funcţiei loads() în modulul pickle

[Link](b"\x80\x04\x95i\x01\x00\x00\x00\x00\x00\x00\x8c\x0cJ.K.
1Rowling\x94\x8c\x0cHarry Potter\x94(M\xcd\x07\x8c(Harry Potter and the
2Philosopher's Stone\x94\x86\x94M\xce\x07\x8c'Harry Potter and the
3Chamber of Secrets\x94\x86\x94M\xcf\x07\x8c(Harry Potter and the
4Prisoner of Azkaban\x94\x86\x94M\xd0\x07\x8c#Harry Potter and the Goblet
5of Fire\x94\x86\x94M\xd3\x07\x8c)Harry Potter and the Order of the
6Phoenix\x94\x86\x94M\xd5\x07\x8c&Harry Potter and the Half-Blood
7Prince\x94\x86\x94M\xd7\x07\x8c$Harry Potter and the Deathly
8Hallows\x94\x86\x94t\x94\x87\x94.")

Modulul marshal

Modulul marshal este, de asemenea, utilizat pentru citirea şi scrierea datelor în format binar. Spre
deosebire de pickle, care urmăreşte obiecte serializate prin atribuirea referinţelor la ele, marshal nu
poate fi folosit pentru serializarea datelor definite de utilizator.

Acest modul există, în principal, pentru citirea şi scrierea codului pseudo-compilat pentru modulele
Python din fişierele .pyc. Pentru a-l utiliza, trebuie să introducem modulul în program, ceea ce facem
cu comanda import marshal.

Notă:

Codul pseudo-compilat este un cod scris într-un limbaj avansat, adică necesită o compilare suplimentară pentru
a fi executat.

Pentru serializarea şi deserializarea datelor în modulul marshal, folosim funcţii identice ca şi în


modulul pickle:

• funcţiile de serializare:
o dump() - serializarea datelor în obiecte;
o dumps() - serializarea datelor într-un string;

Page 13 of 126
• funcţiile de deserializare:
o load() - deserializarea datelor în obiecte;
o loads() - deserializarea datelor într-un string.

Aceste funcţii funcţionează pe acelaşi principiu şi utilizează aceiaşi parametri ca funcţiile modulului
pickle, deci, nu le vom explica în detaliu.

Modulul shelve

Modulul shelve se utilizează pentru serializarea şi deserializarea datelor în formatul de dicţionar, motiv
pentru care este un instrument excelent pentru introducerea datelor în baza de date. După cum am
putut vedea în lecţia despre tipurile de date, dicţionarul stochează date în perechi cheie-valoare. Ceea
ce permite modulul shelve este ca valoarea din acea pereche să fie un obiect, în timp ce cheia acelei
perechi trebuie să fie un string obişnuit.

Pentru a utiliza modulul shelve, mai întâi trebuie să îl introducem în program, ceea ce vom face cu
linia import shelve. Acum, când avem acces la funcţiile acestui modul, vom crea un
fişier ShelveExample, în care vom scrie câteva date cheie-valoare. Datele din acest modul sunt
introduse folosind funcţia open(), care ca parametru primeşte numele fişierului în care scriem. În cadrul
funcţiei, definim datele cheie-valoare.

Exemplu de utilizare a funcţiei open() în modulul shelve

1 import shelve
2
3with [Link]('ShelveExample') as mutd_jerseys:
4 mutd_jerseys['1'] = 'David de Gea'
5 mutd_jerseys['2'] = 'Victor Lindelof'
6 mutd_jerseys['3'] = 'Eric Bailly'
7 mutd_jerseys['4'] = 'Phil Jones'
8 mutd_jerseys['5'] = 'Harry Maguire'
9 mutd_jerseys['6'] = 'Paul Pogba'
10 mutd_jerseys['7'] = 'Alexis Sanchez'
11 mutd_jerseys['8'] = 'Juan Mata'

Fişierul creat în acest mod este o bază pentru baza de date care se află sub acesta. Ca efect secundar,
programul poate crea mai multe fişiere cu acelaşi nume şi extensii diferite. Fişierul creat pentru baza de
date poate fi utilizat pentru citirea şi scrierea datelor.

Manipulăm valorile din dicţionar prin intermediul cheilor. Dacă schimbăm valoarea în perechea cheie-
valoare, atunci când utilizăm funcţia open() pentru acelaşi fişier, noua valoare va fi introdusă în fişierul
pentru cheia dată. Cheia 7, în continuarea exemplului va primi o nouă valoare, David Bechkam, în baza
noastră de date ShelveExample.

Exemplu de schimbare a valorii cheii date în modulul shelve

1import shelve
2
3with [Link]('ShelveExample') as mutd_jerseys:
4 mutd_jerseys['7'] = 'David Beckham'

Page 14 of 126
Modulul shelve susţine toate funcţiile susţinute de dicţionar. În continuare, urmează un exemplu de
utilizare a funcţiei del ca una dintre funcţiile dicţionarului. Exemplul dat arată cum această funcţie
şterge datele din baza noastră de date folosind cheia dată.

Exemplu de utilizare a funcţiei del în modulul shelve

1import shelve
2
3mutd_jerseys = [Link]('ShelveExample')
4del mutd_jerseys['7']

Modulul json

Unul dintre cele mai frecvent utilizate module pentru serializarea şi deserializarea datelor este
modulul json. Acest modul este literal inspirat din sintaxa limbajului JavaScript, deşi nu este un subset
al acestuia.

Spre deosebire de alte module, json este utilizat pentru serializarea datelor în format text, nu în format
binar. Avantajul acestui tip de serializare este că permite lizibilitatea umană. Un alt avantaj al lui json
este interportabilitatea sa, respectiv capacitatea de a citi în alte programe, nu numai în cele scrise în
Python. Ceea ce nu acceptă json sunt clasele definite de utilizator, adică poate fi utilizat numai pentru
serializarea şi deserializarea tipurilor de date predefinite.

Modulul json utilizează acelaşi API (Application Programming Interface) ca alte module Python pentru
serializare şi deserializare. Datorită interportabilităţii sale, este foarte des utilizat. Prin urmare, utilizarea
acestuia va fi descrisă în detaliu în cursul intitulat Service Application Development.

Notă

Niciunul dintre aceste module nu este conceput pentru a oferi protecţie împotriva datelor maliţioase. Nu se
recomandă deserializarea datelor din surse nesigure şi neautorizate.

Page 15 of 126
1.3. Stocarea şi transportul de date

Rezumat
• După capacitate şi viteza de acces, memoria poate fi clasificată în trei categorii:

o primară - cea mai rapidă memorie, disponibilă direct procesorului;


o secundară - face backup pentru date pentru utilizări viitoare;
o terţiară - stochează o cantitate imensă de date cu viteză redusă de acces.

• RAID este o tehnologie pentru conectarea mai multor dispozitive de stocare secundare într-un
singur mediu.
• Organizarea fişierelor pe disc poate fi de tip: heap, secvenţială, hash şi clusterizată.
• Operaţiunile de bază pe fişiere sunt: open(), locate(), read(), write() şi close().
• Indexarea este o tehnică a structurilor de date pentru găsirea eficientă a înregistrărilor pe baza
unor atribute peste care s-a efectuat indexarea.
• Indexurile la mai multe niveluri sunt utilizate atunci când dimensiunea indexului este mai mare
decât cea care poate fi stocată în memorie.
• Hashing-ul este o tehnică eficientă pentru calcularea locaţiei directe a datelor solicitate pe disc,
fără a utiliza tehnica de indexare. Poate fi static şi dinamic.
• O tranzacţie implică un grup de sarcini în care o sarcină individuală reprezintă o unitate de
proces minimă, care nu poate fi împărţită în continuare.
• Proprietăţile ACID ale tranzacţiei sunt:

o atomicitatea (atomicity) - se efectuează toate operaţiunile sau niciuna;


o consecvenţa (consistency) - consecvenţa bazei de date înainte şi după executarea
tranzacţiei;
o izolarea (isolation) - efectele tranzacţiilor efectuate în acelaşi timp sunt izolate;
o durabilitatea (durability) - efectele tranzacţiilor sunt permanente pe baze de date.

• Seriabilitatea implică executarea mai multor tranzacţii într-un mediu cu mai multe programe,
ducând la conflicte de tranzacţii.
• Stările posibile ale tranzacţiei sunt: activă, parţial executată, eşuată, întreruptă şi executată.
• Există două moduri de a executa tranzacţii paralele, şi anume prin:

o protocoale de blocare - plasarea unui lacăt pe date înainte de executarea tranzacţiei;


o protocoale timestamp - timestamping al tranzacţiilor în ordinea executării.

• Copierea de rezervă a datelor este utilizată în caz de defecţiune a memoriei primare care nu
este suficient de stabilă. Câteva dintre tehnici sunt: introducerea punctelor de control, salvarea
automată şi marcatorii <dump>.

• Cele două operaţiuni de tranzacţii de bază sunt:

o commit() - salvează permanent toate modificările efectuate datelor din baza de date;
o rollback() - anulează toate operaţiunile începute ale tranzacţiei curente şi
returnează baza de date în starea în care se afla înainte de începerea tranzacţiei.

Bazele de date se stochează într-un format de fişier care conţine unele înregistrări. La nivel fizic, datele
sunt stocate într-un format electromagnetic pe un dispozitiv de stocare, adică într-o memorie. Memoria
variază, în general, în ceea ce priveşte capacitatea şi viteza de acces. Pe baza acestui criteriu, memoria
poate fi, în general, clasificată în trei tipuri, aşa cum se poate vedea în imaginea 3.1.

Page 16 of 126
Imaginea 3.1. Clasificarea generală a dispozitivelor de stocare

• Memoria primară se află pe placa de bază şi este accesibilă direct procesorului. Aceasta include
memoria internă (registre), memoria rapidă (cache) şi memoria principală (RAM - Random
Access Memory). Memoria primară este, de obicei, foarte mică, iar execuţia în ea este extrem
de rapidă. Memoria primară necesită o putere constantă pentru a-şi menţine starea. În caz de
pană de curent, toate datele se pierd.
• Memoria secundară stochează date pentru viitoarea utilizare sau face backup-ul datelor.
Această memorie nu include dispozitive care fac parte din procesor sau placa de bază. Memoria
secundară include discuri magnetice, discuri optice (CD-uri, DVD-uri etc.), hard diskuri, discuri
flash, benzi magnetice şi altele.
• Memoria terţiară este utilizată pentru stocarea unei cantităţi imense de date. Conform
sistemului de calcul, această memorie este memorie externă, ceea ce face ca lucrul cu datele să
fie mai lent. Această memorie utilizează în principal discuri optice şi benzi magnetice.

Sistemul de calcul are o ierarhie de memorie bine definită. Procesorul are acces direct la memoria
principală, precum şi la registre încorporate. Totuşi, timpul principal de acces la memorie este mult
mai lent decât procesorul. Problema acestei neconcordanţe este rezolvată prin introducerea memoriei
cache. Aceasta oferă cel mai rapid timp de acces şi conţine datele pe care procesorul le accesează cel
mai des. Memoria cu cel mai rapid acces este cea mai scumpă. Dispozitivele de stocare mai mari oferă
viteză redusă şi sunt mai ieftine, dar pot stoca unele cantităţi mari de date.

RAID

Hard diskul (discul magnetic) este cel mai utilizat dispozitiv pentru stocarea secundară a datelor - adică
cea mai comună memorie secundară. Se compune din discuri dure care sunt căptuşite cu material de
magnetizare şi aşezate vertical pe ax. Capul pentru citire, adică de scriere a datelor se deplasează între
discuri şi magnetizează şi demagnetizează datele pe baza înregistrărilor binare 1 şi 0 de pe discuri.

Placa de hard disk are multe circuite concentrice care sunt împărţite în sectoare, fiecare dintre acestea
conţinând, de obicei, 512 biţi. Hard diskurile sunt formatate într-o ordine bine definită pentru stocarea
eficientă a datelor. În acest scop, se utilizează tehnologia RAID.

RAID (redudant array of independent discs) este o tehnologie pentru conectarea mai multor
dispozitive de stocare secundare într-un singur mediu. Această tehnologie vă permite să conectaţi mai
multe discuri într-un şir unic, pentru a atinge diferite obiective. În funcţie de obiectivul pe care dorim să
îl atingem, distingem mai multe niveluri RAID.

RAID 0 - la acest nivel este implementat un şir descompus de discuri. Aici, datele sunt împărţite în
blocuri, iar blocurile sunt aranjate pe discuri. Fiecare disc primeşte un bloc de date pentru citire sau

Page 17 of 126
scriere paralelă. În acest fel, viteza şi performanţa dispozitivelor de stocare a datelor, adică a memoriei,
sunt îmbunătăţite. Nu există parităţi şi copii de rezervă la acest nivel.

Imaginea 3.2. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 0

Notă

Paritatea, respectiv bitul de paritate, în programare, reprezintă bitul pentru verificarea stringului binar transmis
pentru introducerea datelor. Se adaugă la începutul stringului binar şi se asigură că numărul de biţi cu valoare
1 devine par sau impar. În funcţie de ceea ce dorim să realizăm, distingem paritatea pară şi paritatea impară.

RAID 1 foloseşte tehnica oglindă. Când datele sunt trimise controlerului RAID, acesta trimite o copie a
datelor către toate celelalte discuri din şir. Acest nivel permite redundanţa sută la sută a datelor în caz
de eşec.

Imaginea 3.3. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 1

RAID 2 foloseşte un cod de corectare a erorilor (ECC – error correction code) folosind
distanţele Hamming pentru datele analizate pe diferite discuri. Aici, precum şi la nivelul zero, fiecare bit
de date este stocat pe un disc diferit şi codurile ECC sunt stocate pe un set diferit de discuri. Datorită
structurii sale complexe şi a costurilor ridicate, acest nivel nu este disponibil publicului.

Imaginea 3.4. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 2

Page 18 of 126
RAID 3 redirecţionează datele pe mai multe discuri. Bitul de paritate generat pentru fiecare cuvânt este
stocat pe un disc separat. Această tehnică depăşeşte erorile de pe un singur disc.

Imaginea 3.5. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 3

RAID 4 scrie un întreg bloc de date pe discurile de date, iar apoi paritatea este generată şi stocată pe
alt disc. Spre deosebire de al treilea nivel, care împarte datele în biţi, acest nivel împarte datele în blocuri.

Imaginea 3.6. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 4

RAID 3 şi RAID 4 necesită minimum trei discuri pentru ca implementarea lor să fie posibilă.

RAID 5 scrie blocuri întregi de date pe diferite discuri, dar biţii de paritate generaţi pentru blocurile de
date analizate, sunt distribuiţi pe toate discurile, în loc să fie stocaţi pe un singur disc dedicat.

Imaginea 3.7. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 5

RAID 6 este un upgrade al nivelului cinci. La acest nivel, două parităţi independente sunt generate şi
distribuite într-o manieră distribuită pe mai multe discuri. În acest fel, se asigură dubla intoleranţă la
erori. Pentru implementare, este necesar un minimum de patru discuri.

Page 19 of 126
Imaginea 3.8. Înscrierea datelor în memoria secundară, folosind tehnologia RAID 6

Organizarea fişierelor şi operaţiuni asupra acestora

Datele şi informaţiile relative sunt stocate împreună într-un format de fişier. În acest caz, fişierul
reprezintă o serie de înregistrări stocate în format binar. Discul de stocare a datelor este formatat în mai
multe blocuri pe care pot fi stocate înregistrări. Înregistrările din fişier sunt mapate pe aceste discuri.
Cum vor fi mapate datele - depinde de organizarea fişierelor.

Imaginea 3.9. Moduri de organizare a fişierelor în memoria secundară

Sistemul de gestionare a bazelor de date organizează fişierele mapate pe disc. Distingem patru moduri
de organizare a fişierelor:

• Organizarea Heap a fişierelor presupune că sistemul de operare atribuie o locaţie de memorie


fişierului respectiv fără calculări suplimentare. Înregistrările acestui fişier pot fi stocate oriunde
în locaţia de memorie atribuită. Responsabilitatea pentru gestionarea fişierelor aparţine
software-ului. Fişierul stocat în heap nu permite alocarea manuală a locaţiei, secvenţierea şi
indexarea.
• Organizarea secvenţială a fişierelor presupune că fiecare înregistrare de fişier conţine un
câmp de date (atribut) care reprezintă identificatorul său unic. În această organizare,
înregistrările din fişier sunt plasate într-o ordine specifică, pe baza unei chei primare sau a unei
chei de căutare. În practică, stocarea tuturor datelor într-o anumită ordine este imposibilă din
punct de vedere fizic.
• Organizarea Hash a fişierelor utilizează calculul funcţiei hash pe unele câmpuri de înregistrare.
Funcţiile output hash determină locaţia blocului de disc pe care vor fi stocate înregistrările.

Page 20 of 126
• Organizarea clusterizată a fişierelor- în acest mecanism, înregistrările asociate dintr-una sau
mai multe relaţii sunt stocate pe acelaşi bloc de disc, adică ordinea înregistrărilor nu se bazează
pe cheia primară sau pe cheia de căutare.

Operaţiunile pe fişierele bazelor de date pot fi, în general, clasificate în două categorii:

• operaţiunile de actualizare modifică valoarea datelor prin adăugare, ştergere sau modificare;
• operaţiunile de vizualizare afişează datele dorite pe baza unui criteriu dat.

Pe lângă crearea şi ştergerea fişierelor, există şi alte câteva operaţiuni de bază care pot fi efectuate pe
acestea.

• Open - fişierul poate fi deschis în modul de citire sau în modul de scriere. În modul de citire,
sistemul nu permite nimănui să modifice datele. Cu alte cuvinte, datele din acest mod pot fi
citite şi partajate numai între alte entităţi. În modul de scriere, datele pot fi citite, scrise şi
modificate, dar nu partajate.
• Locate - fiecare fişier are un indicator care oferă informaţii despre locul în care ar trebui
efectuată operaţiunea de citire sau scriere. Indicatorul poate fi ajustat corespunzător, adică
mutat înainte sau înapoi.
• Read - apelând această funcţie deschidem fişierul în modul citire, în timp ce indicatorul fişierului
indică la începutul său. În cadrul acestuia, există şi opţiuni prin care utilizatorul îi poate spune
sistemului unde doreşte să plaseze indicatorul în momentul deschiderii fişierului.
• Write - această funcţie vă permite să editaţi conţinutul fişierelor. Acestea pot fi şterse, adăugate
sau modificate. Indicatorul poate fi localizat în momentul deschiderii sau setat dacă sistemul
permite acest lucru.
• Close - cea mai importantă operaţiune din punctul de vedere al sistemului de operare. Când se
generează o cerere de închidere a fişierului, sistemul de operare salvează datele, eliberează
bufferul şi elimină toate lacătele, despre care vom vorbi mai multe în continuare.

Indexarea

Ştim că fiecare informaţie scrisă într-un fişier reprezintă o înregistrare. Pentru ca fiecare înregistrare să
fie unică, aceasta trebuie să conţină un câmp cu o valoare unică care o defineşte. Acest lucru se
realizează prin indexare.

Indexarea este o tehnică a structurilor de date pentru găsirea eficientă a înregistrărilor pe baza unor
atribute peste care a fost efectuată. Această tehnică poate fi asimilată cu paginile de marcare din cărţi.

Există mai multe tipuri de indexare.

• Indexul primar este definit în fişierul de date editate. Editarea fişierului se face prin specificarea
câmpului care va fi cheia. Cheia este legătura primară către alte date.
• Indexul secundar poate fi generat dintr-un câmp candidat pentru cheie şi care are o valoare
unică în fiecare înregistrare.
• Indexul de clusterizare este definit în fişierul de date editat. Datele sunt specificate prin
câmpuri care nu sunt o cheie.

Indexarea aranjată poate fi clasificată în două categorii.

• Indexarea densă, care oferă o înregistrare unică a indexului pentru fiecare valoare a cheii de
căutare din baza de date. Acest lucru accelerează căutarea, dar necesită mai multă memorie
pentru a stoca toate înregistrările indexului. Înregistrările index conţin valoarea cheii de căutare

Page 21 of 126
şi un indicator către înregistrarea reală de pe disc. Pentru o înţelegere mai uşoară, în următoarea
imagine, cea cu numărul 3.10., se poate vedea cum arată indexarea densă pe exemplul tabelului
din prima ligă spaniolă de fotbal (imaginea 3.10.).

Imaginea 3.10. Exemplu de indexare densă

• Indexarea rară presupune înregistrări de index care nu sunt create pentru fiecare cheie de
căutare. De asemenea, conţin o cheie de căutare şi un indicator către datele de pe disc. Pentru
a căuta o înregistrare, folosim indexuri pentru a ajunge la locaţia în care au fost stocate datele.
Dacă datele nu se află la acea locaţie, sistemul continuă căutarea secvenţială până când găseşte
datele dorite. Mai jos arătăm cum arată o indexare rară în exemplul nostru (imaginea 3.11.)

Imaginea 3.11. Exemplu de indexare rară

Indexurile pe mai multe niveluri sunt stocate pe disc împreună cu fişierele bazei de date. Dimensiunea
indexului creşte odată cu dimensiunea bazei de date. Datorită necesităţii unui acces rapid la date,
indexurile bazei de date sunt stocate în memoria principală.

Când dimensiunea unui index este mai mare decât cea care poate fi stocată în memorie, are loc accesul
pe discuri multiple şi indexul este împărţit în mai multe niveluri. Descompunerea se face până la cea mai
mică dimensiune a indexului care poate fi stocată pe un singur bloc de disc, care poate fi stocat cu
uşurinţă oriunde în memoria principală.

Page 22 of 126
Imaginea 3.12. Prezentarea structurii indexului pe mai multe niveluri

Un arbore B+ este un arbore echilibrat de căutare binară, care urmează un format de index cu mai multe
niveluri. Conceptul de arbore se bazează pe noduri care reprezintă unitatea de bază de stocare a datelor
în calculator. Nodul iniţial din arbore se numeşte rădăcină. Orice nod din arborele de căutare care nu
are noduri sub sine (copil) se numeşte frunză. Frunzele nu conţin date. În plus, nodurile sunt conectate
printr-o listă concatenată, care oferă atât posibilitatea accesului aleatoriu, cât şi accesul secvenţial.
Există mai multe reguli de structură pe care le respectă acest arbore:

• toate frunzele arborelui sunt la aceeaşi adâncime în raport cu rădăcina;


• rădăcina arborelui, dacă nu este o frunză, are cel puţin două indicatoare, unul îndreptat spre
nodul din stânga şi celălalt spre nodul din dreapta (copilul);
• fiecare nod din arbore care nu este rădăcină are cel puţin n+1 copii;
• fiecare frunză din arbore conţine un indicator către o frunză adiacentă şi formează o listă
concatenată.

Imaginea 3.13. Prezentarea structurii B+ a arborelui de căutare

Page 23 of 126
Introducerea datelor în arbore se face de jos în sus. Dacă nodul nu poate accepta toate datele, este
introdus un nou nod şi datele sunt partiţionate în locaţiile i şi i+1 şi cheia este transmisă nodului care
este părintele.

Ştergerea datelor din arbore se face de jos în sus. Dacă ştergem un nod care nu este o frunză, acesta
va fi înlocuit de următorul din stânga. Dacă nodul din stânga nu există, se ia cel din dreapta.

Hashing-ul

Cu bazele de date mari este aproape imposibil să căutaţi toate valorile indexului după nivelurile lor
pentru a afişa datele dorite. Hashing-ul este o tehnică eficientă pentru calcularea locaţiei directe a
datelor căutate pe disc, fără a utiliza tehnica de indexare. Această tehnică foloseşte funcţii hash care, cu
ajutorul unei chei de căutare ca parametru, generează o adresă de înregistrare a datelor.

În funcţie de modul de organizare, distingem două moduri de hash:

• pachetul, unde un fişier hash stochează date într-un format de pachet; fiecare pachet reprezintă
o unitate de stocare şi stochează, de obicei, un bloc de disc pe care pot fi stocate una sau mai
multe înregistrări;
• funcţia hash - funcţia h mapează întregul set de chei de căutare K la adrese la care se află date
specifice.

Când transmitem o cheie de căutare ca parametru, funcţia calculează întotdeauna aceeaşi adresă pentru
aceasta. Numărul pachetelor rămâne întotdeauna neschimbat.

Imaginea 3.14. Exemplu de hashing pentru patru locaţii de memorie

Page 24 of 126
Hashing-ul distinge câteva operaţiuni de bază:
• inserarea - introducerea statică a înregistrărilor folosind funcţia x care foloseşte cheia de
căutare a parametrilor, pentru a calcula adresa la care va fi stocată înregistrarea;
• căutarea - poate fi folosită pentru găsirea înregistrării dorite, folosind adresa sau pentru a găsi
adresa în sine;
• ştergerea - o căutare simplă care are ca rezultat ştergerea datelor.

Principala problemă a hash-ului static este aşa-numitul bucket overflow, care apare atunci când nu
există suficientă memorie în pachetul ţintă. Există două soluţii la această problemă:

• chain overflow - când un pachet este umplut, altul nou este alocat pentru acelaşi rezultat hash
şi conectat la pachetul anterior; acest mecanism se mai numeşte şi hashing închis;

Imaginea 3.15. Prezentarea hashing-ului închis – chain overflow

• sondarea liniară - când funcţia hash generează o adresă în care datele sunt deja stocate,
acesteia i se atribuie următorul pachet liber; acest mecanism este, de asemenea, numit hashing
deschis; în imaginea 3.16. se poate vedea cum arată hashing-ul deschis în exemplul alocării a
cinci locaţii de memorie.

Imaginea 3.16. Prezentarea hashing-ului deschis (sondarea liniară)

Page 25 of 126
Problema cu hashing-ul static este că nu creşte, respectiv se reduce, împreună cu creşterea sau
reducerea bazei de date. Hashing-ul dinamic oferă un mecanism în care pachetele de date sunt
adăugate şi eliminate dinamic şi la cerere. Acest hashing se mai numeşte hashing avansat. În hashing-
ul dinamic, funcţia hash este concepută pentru a calcula un număr mare de valori, dintre care doar unele
au fost utilizate iniţial.

Imaginea 3.17. Prezentarea hashing-ului dinamic (avansat)

Prefixul întregii valori hash este luat ca index hash, în timp ce o parte mică este luată pentru calcularea
adresei pachetului. Fiecare index hash are o valoare de adâncime care indică câţi biţi sunt utilizaţi pentru
calcularea funcţiei hash. Aceşti biţi pot adresa pachete 2n. Când toţi aceşti biţi sunt utilizaţi, adică când
pachetele sunt pline, valoarea adâncimii creşte liniar şi pachetele sunt atribuite de două ori.
Hashing-ul susţine mai multe operaţiuni de bază:

• interogări - folosind biţi care reprezintă valorile adâncimii indexului hash, se calculează
adresele pachetelor;
• actualizarea - permite modificarea datelor folosind interogări;
• ştergerea - găseşte date din interogare şi le şterge;
• inserarea- calculează adresele pachetelor şi le inserează conform următorului algoritm:
o dacă pachetul este deja plin:
▪ adaugă mai multe pachete;
▪ adaugă mai mulţi biţi la funcţia hash;
▪ recalculează adresa;
o în caz contrar:
▪ adaugă date la pachet;
▪ dacă toate pachetele sunt pline, hashing-ul statistic trebuie eliminat.

Hashing-ul nu este convenabil atunci când lucrăm cu datele organizate într-o anumită ordine şi când
interogările necesită date. Hash se comportă cel mai bine atunci când datele sunt discrete şi aleatorii.
Spre deosebire de indexare, hashing-ul are un algoritm mai complex şi efectuează toate operaţiunile
într-un timp constant.

Page 26 of 126
Tranzacţiile

O tranzacţie poate fi definită ca un grup de sarcini. O sarcină individuală este o unitate de procesare
minimă care nu poate fi divizată în continuare. Pentru a explica mai bine conceptul de tranzacţie, vom
arăta cum arată în exemplul de transfer de bani din contul angajatului A în contul angajatului B. Să
presupunem că programul are deja clasa Account, definită cu un câmp balance care reprezintă starea
curentă a contului utilizatorului şi funcţiile open_account() şi close_account(). Ca exemplu, vom
lua transferul de 100 dolari.

Exemplu de cont al angajatului A:

1open_account(A)
2old_balance = [Link]
3new_balance = old_balance - 100
[Link] = new_balance
5close_account(A)

Exemplu de cont al angajatului B:

1open_account(B)
2old_balance = [Link]
3new_balance = old_balance - 100
[Link] = new_balance
5close_account(B)

O tranzacţie este o unitate foarte mică a unui program, dar poate conţine mai multe sarcini de nivel
inferior. Pentru a menţine acurateţea, exhaustivitatea şi integritatea datelor, tranzacţiile monitorizează
caracteristicile ACID:

• Atomicitatea (atomicity): o tranzacţie trebuie tratată ca o valoare „atomică”, ceea ce înseamnă


- fie vor fi efectuate toate operaţiunile sale, fie deloc. Nu trebuie să existe nicio tranzacţie în
baza de date care să fie într-o stare de execuţie parţială. Stările tranzacţiei trebuie definite
înainte de executare sau după executare/respingere/eşec.
• Consecvenţa (consistency): baza de date trebuie să rămână într-o stare consecventă după
orice tranzacţie. Nicio tranzacţie nu ar trebui să aibă un efect negativ asupra datelor conţinute
în baza de date. Dacă baza de date se afla într-o stare consecventă înainte de executarea
tranzacţiei, aceasta trebuie să rămână aşa după executarea acesteia.
• Izolarea (isolation): dacă mai multe tranzacţii sunt executate în sistem în acelaşi timp, efectele
fiecărei tranzacţii individuale vor fi izolate. Nicio tranzacţie nu va afecta existenţa oricărei alte
tranzacţii.
• Durabilitatea (durability): baza de date trebuie să fie suficient de durabilă pentru toate cele
mai recente actualizări, chiar dacă sistemul se blochează sau reporneşte. Dacă o tranzacţie
aduce modificări unei părţi de date, aceste modificări ale bazei de date rămân permanente.
Dacă sistemul se blochează după tranzacţie, dar înainte de a scrie date pe disc, datele vor fi
scrise după repornirea sistemului.

Unul dintre conceptele importante atunci când vine vorba de tranzacţii este seriabilitatea. Şi anume,
atunci când sistemul de operare execută mai multe tranzacţii într-un mediu cu mai multe programe,

Page 27 of 126
există posibilitatea ca instrucţiunile unei tranzacţii să se întrepătrundă cu instrucţiunile altei, ducând la
un conflict de tranzacţii. Există două soluţi la această problemă:

• ordinea - cronologică, adică executarea secvecţială a tranzacţiilor; programul poate conţine un


număr mare de tranzacţii, fiecare dintre acestea având propria ordine de execuţie şi propriile
instrucţiuni;
• ordinea serială - tranzacţiile sunt decontate, astfel încât una să fie executată mai întâi; când
prima tranzacţie îşi execută ciclul, este rândul următoarei; tranzacţiile continuă să se execute în
serie, într-o ordine stabilită.

Într-un mediu cu mai multe tranzacţii, execuţia în serie se execută folosind valori de referinţă. Execuţia
secvenţială a instrucţiunilor în tranzacţii nu poate fi modificată, dar două tranzacţii pot executa
instrucţiuni la întâmplare. Această execuţie nu este în conflict dacă cele două tranzacţii funcţionează pe
segmente de date diferite. În cazul în care execuţia se efectuează pe acelaşi segment, rezultatele pot
varia. Aceste variaţii pot duce la neconcordanţe.

Pentru a rezolva această problemă, permitem execuţia paralelă a unui program de tranzacţii dacă
tranzacţiile din acel program sunt configurabile în serie sau au o relaţie echivalentă.

Ordinea echivalentă include mai multe tipuri:

• rezultatele echivalente - două tranzacţii sunt considerate echivalente dacă dau acelaşi rezultat
după executare; pot da acelaşi rezultat pentru o valoare şi rezultate diferite pentru un alt tip de
valoare, motiv pentru care această echivalenţă nu este în general considerată semnificativă;
• vizualizările echivalente – ordinea a două tranzacţii este considerată echivalentă dacă
tranzacţiile din ambele ordini efectuează activităţi similare într-un mod similar;
• conflictele echivalente- două ordini sunt conflictuale dacă îndeplinesc următoarele condiţii:
o ambele aparţin tranzacţiilor separate;
o ambii accesează aceleaşi date;
o cel puţin una dintre tranzacţii efectuează o operaţie de scriere.

Două ordini care au tranzacţii multiple cu conflicte de operaţiuni sunt în mod echivalent în conflict dacă
şi numai dacă:

• ambele ordini conţin acelaşi set de tranzacţii;


• ordinea de execuţie a perechilor conflictuale este menţinută în ambele ordini.

Distingem mai multe stări de tranzacţii şi anume:

• activă - în această stare, tranzacţia este executată; aceasta este starea iniţială a fiecărei
tranzacţii;
• executată parţial - tranzacţia ajunge la această stare atunci când efectuează ultima operaţiune;
• eşuată - se spune că o tranzacţie eşuează dacă orice verificare efectuată de sistemul de
recuperare a bazei de date eşuează; tranzacţia eşuată nu mai poate fi procesată;
• întreruptă - dacă orice verificare eşuează, tranzacţia eşuează; apoi gestionarea recuperării
anulează toate operaţiunile de scriere şi readuce baza de date la starea iniţială înainte de
începerea tranzacţiei; tranzacţia intră apoi într-o stare întreruptă, după care sistemul de
recuperare a bazei de date alege între două opţiuni: repornirea tranzacţiei şi „distrugerea”
acesteia;
• executată - în această stare se realizează tranzacţia care şi-a efectuat toate operaţiunile; toate
efectele sale reprezintă modificări permanente în baza de date.

Page 28 of 126
Imaginea 3.18. Fluxul stării tranzacţiilor

Tranzacţiile paralele

Într-un mediu cu mai multe programe în care mai multe tranzacţii pot fi executate în acelaşi timp, este
foarte important să le controlăm execuţia paralelă. Pentru a asigura atomicitatea, izolarea şi seriabilitatea,
folosim anumite protocoale. Protocoalele pentru controlul executării paralele a tranzacţiilor pot fi
împărţite în două categorii de bază:

• protocoale de blocare;
• protocoale timestamp.

Sistemele de baze de date, bazate pe protocoale de blocare, utilizează un mecanism prin care o
tranzacţie nu poate efectua o operaţiune de citire sau scriere înainte de blocarea datelor sau setarea
unui lacăt. Distingem două tipuri de protocoale de blocare:

• blocare binară - acceptă două stări, utilizând numărul binar 1 pentru starea blocată şi numărul
0 pentru starea deblocată;
• blocare exclusivă/partajată - în scopul înregistrării, este setat un lacăt exclusiv, deoarece
accesul unui număr mare de tranzacţii la o singură dată duce la o stare de inconsecvenţă; pe de
altă parte, blocarea partajată permite unui număr mare de tranzacţii să plaseze un lacăt pe date,
deoarece niciuna dintre tranzacţii nu intenţionează să-l schimbe.

Protocoalele simplificate permit tranzacţiilor să plaseze un lacăt pe orice date înainte de a efectua o
operaţie de scriere. După efectuarea operaţiei de scriere, tranzacţia eliberează lacătul din date.

Protocoalele de revizuire analizează cererile de tranzacţii şi creează o listă de date care vor fi blocate.
Înainte de a iniţializa executarea, tranzacţia solicită informaţii de la sistem despre toate lacătele care vor
fi plasate pe datele pe care doreşte să le acceseze. Dacă datele ţintă sunt gratuite, tranzacţia plasează
lacătele pe ea, execută scrierea şi apoi le eliberează. În cazul în care unele dintre datele țintă nu sunt
gratuite, tranzacţia aşteaptă ca toate datele ţintă să fie publicate pentru a putea fi executate.

Page 29 of 126
Imaginea 3.19. Prezentare temporală a execuţiei tranzacţiei cu blocare simplă

Protocoalele cu blocare dublă (2PL) împart etapa de execuţie a tranzacţiei în trei părţi. În prima parte,
tranzacţia necesită permisiunea de blocare necesară. A doua parte implică plasarea tuturor lacătelor pe
tranzacţii. De îndată ce tranzacţia eliberează primul lacăt, începe a treia etapă. În această etapă,
tranzacţia nu poate solicita permisiunea de a instala lacăte noi, ci le poate elibera doar pe cele vechi.

Imaginea 3.20. Prezentare temporală a execuţiei tranzacţiei cu blocare dublă (2PL)

După cum putem vedea în imaginea protocolului cu blocare dublă (imaginea 3.20.), la începutul
tranzacţiei, graficul creşte odată cu plasarea lacătului pe date. Apoi, după perioada de execuţie a
tranzacţiei, observăm o scădere pe timeline, ceea ce înseamnă eliberarea lacătului până la sfârşitul
execuţiei.
Pentru operaţiunea de introducere a datelor, mai întâi, tranzacţia trebuie să solicite dreptul de citire,
adică să plaseze lacătul partajat şi apoi să avanseze acel drept la lacătul exclusiv, adică la dreptul de
scriere.

Blocarea dublă strictă are o primă etapă identică cu protocolul 2PL. După obţinerea dreptului de a
plasa un lacăt pe date în prima etapă, tranzacţia continuă să se execute normal. Singura diferenţă constă
în eliberarea lacătului. Spre deosebire de blocarea dublă obişnuită şi strictă, aceasta eliberează toate
lacătele în acelaşi timp, după tranzacţie.

Page 30 of 126
Imaginea 3.21. Prezentare temporală a execuţiei tranzacţiei cu blocare dublă strictă

Protocoalele timestamp permit serializarea între tranzacţiile aflate în conflict şi operaţiunile lor de citire
şi scriere folosind timestamp-uri. Acest protocol are responsabilitatea de a executa perechea conflictuală
de sarcini de tranzacţie în conformitate cu timestamp-urile lor.

Timestamp-urile se realizează în următoarea notaţie:

• pentru timestamp-ul tranzacţiei Tifolosim notaţia TS(Ti);


• pentru timestamp-ul citirii unor date X folosim notaţia R(X);
• pentru timestamp-ul de introducere a unor date X folosim notaţia W(X).

În conformitate cu notaţia de mai sus, algoritmul timestamp-urilor arată astfel:

• dacă tranzacţia Ti utilizează operaţiunea de read(X):


o dacă TS (Ti) < W-timestamp(X)
operaţiunea este respinsă;

• dacă TS (Ti) > = W-timestamp(X)


operaţiunea este efectuată; toate datele sunt actualizate;

• dacă tranzacţia Ti foloseşte operaţiunea write(X):


o Dacă TS(Ti) < R-timestamp(X)
operaţiunea este respinsă;

• dacă TS(Ti) < W-timestamp(X)


operaţiunea este respinsă şi Ti se retrage; în caz contrar, operaţiunea se execută.

Un lacăt inactiv (deadlock) presupune o situaţie nedorită care apare într-un sistem de resurse partajat,
cu mai multe procese, în care un proces aşteaptă la infinit ca o resursă să fie utilizată de un alt proces.
Acest tip de conflict nu este sănătos pentru sistem şi, în acest caz, tranzacţiile sunt sau returnate, sau se
încearcă să fie executate din nou.

Ca exemplu, vom lua un set de tranzacţii {T0, T1, T2, ..., Tn}. Presupunerea este că T0 doreşte să acceseze
o resursă X care utilizează T1, iar T1 doreşte să acceseze resursa Y care utilizează T2. În acelaşi timp, T2
vrea să acceseze resursa Z care utilizează T0. Această situaţie duce la indisponibilitatea resurselor pentru
fiecare dintre aceste tranzacţii şi la apariţia unui blocaj sau a unui lacăt inactiv (deadlock).

Pentru a preveni orice timp de nefuncţionare în sistem, DBMS verifică cu atenţie toate operaţiunile pe
care trebuie să le efectueze tranzacţiile. Revizuieşte operaţiunile şi analizează opţiunile pentru

Page 31 of 126
efectuarea operaţiunilor fără conflict. Dacă se determină că poate apărea o oprire, tranzacţia nu va fi
executată niciodată.

Soluţia la această problemă sunt schemele pentru rezolvarea nefuncţionării, care utilizează un mecanism
de sincronizare a tranzacţiilor, pentru a detecta timpul de nefuncţionare în timp util. Distingem două
tipuri de scheme:

• schema wait-die
o dacă TS(Ti) < TS(Tj), adică dacă tranzacţia care necesită lacăt este mai recentă decât
tranzacţia care foloseşte datele, atunci poate aştepta ca datele să fie eliberate;
o dacă TS(Ti) > TS(Tj), adică dacă tranzacţia care necesită lacăt este mai veche decât
tranzacţia care foloseşte datele, atunci ea „moare” şi apoi încearcă să seteze lacătul din
nou la intervale aleatorii, în timp ce timestamp-ul său rămâne neschimbat.
• schema wound-wait

o dacă TS(Ti) < TS(Tj), adică dacă tranzacţia care solicită plasarea lacătului este mai
recentă decât tranzacţia care utilizează datele, aceasta forţează retragerea tranzacţiei
care a stabilit lacătul; tranzacţia retrasă la intervale selectate aleatoriu încearcă din nou
să plaseze lacătul, în timp ce timestamp-ul său rămâne neschimbat;
o dacă TS(Ti) > TS(Tj), adică dacă tranzacţia care necesită lacăt este mai veche decât
tranzacţia care utilizează datele, atunci aceasta este forţată să aştepte eliberarea datelor.

În ambele cazuri, tranzacţia care intră în etapa târzie va fi întreruptă.

Renunţarea la o tranzacţie nu este întotdeauna o abordare practică. În schimb, sunt de preferat


mecanismele de evitare a lacătului inactiv. Una dintre cele mai comune şi mai simple tehnici de detectare
a unei opriri este graficul de aşteptare. Dezavantajul acestei tehnici este posibilitatea de a o utiliza numai
pentru tranzacţii mici şi tranzacţii cu un număr mic de instanţe.

Graficul de aşteptare implică crearea unui nod pentru fiecare tranzacţie care intră în sistem. Când Ti
încearcă să plaseze un lacăt pe o resursă X deţinută de o altă tranzacţie Tj, se creează o cale direcţionată
între aceste două tranzacţii. Când Tj eliberează lacătul de pe dată, calea dintre aceste două tranzacţii
dispare şi Ti îşi setează lacătul. Sistemul menţine acest grafic de aşteptare pentru fiecare tranzacţie în
aşteptarea unor elemente de date şi verifică în mod constant pentru a vedea dacă există vreun ciclu în
grafic.

Imaginea 3.22. Ilustrarea graficului de aşteptare în legătură cu noţiunea de deadlock

Page 32 of 126
Pentru tranzacţiile în care graficul de aşteptare nu este aplicabil, se recomandă:

• interzicerea executării tranzacţiilor pe date pe care o altă tranzacţie a pus deja un lacăt;
• selectarea uneia dintre tranzacţiile care urmează să fie executate mai întâi, pentru a elimina
oprirea.

Copia de rezervă a datelor

Memoriile instabile, cum ar fi memoria RAM, stochează toate înregistrările active, bufferele de disc şi
datele conexe. În plus, aceste memorii stochează şi tranzacţiile executate în prezent. Dacă o astfel de
memorie ar fi compromisă grav, toate înregistrările şi copiile active ale bazelor de date ar dispărea. Acest
lucru face ca recuperarea să fie aproape imposibilă, deoarece tot ceea ce este necesar pentru
recuperarea datelor este pierdut.

Pentru prevenirea pierderii datelor pe memoriile instabile, sunt utilizate următoarele tehnici:

• introducerea punctelor de control în mai multe etape, pentru a asigura stocarea periodică a
bazei de date;
• stocarea periodică a bazei de date pe o memorie stabilă care poate conţine, de asemenea,
înregistrări, tranzacţii active şi buffere;
• utilizarea marcajului <dump> ca marcator, de fiecare dată când conţinutul bazei de date este
mutat din memoria instabilă în memoria stabilă.

Când sistemul se recuperează după o defecţiune, acesta poate restabili cel mai recent dump. De
asemenea, poate menţine o listă de activităţi care pot fi anulate sau repetate (undo şi redo). O altă
posibilitate de recuperare este restabilirea stării tuturor tranzacţiilor în momentul ultimului punct de
control.

O defecţiune catastrofală este o defecţiune în care un dispozitiv de stocare secundar şi stabil este
deteriorat. Cu dispozitivul de stocare, toate datele valoroase stocate pe acesta se pierd. Există două
modalităţi de recuperare după o defecţiune catastrofală:

• copiile de rezervă la distanţă - păstrarea unei copii de rezervă în locaţii la distanţă, în care
datele pot fi recuperate în caz de dezastru;
• discurile magnetice - stocarea pe discuri magnetice, de unde o copie de rezervă poate fi
transferată într-o bază de date proaspăt instalată.

Bazele de date performante sunt prea mari pentru a putea face backup de multe ori. În astfel de cazuri,
folosim tehnici în care putem actualiza baza de date doar uitându-ne la jurnalul de tranzacţii. Cu alte
cuvinte, în loc să stocăm întreaga bază de date, putem stoca istoricul jurnalelor cu activităţile efectuate,
ceea ce reduce semnificativ spaţiul de memorie.

Copierea de rezervă la distanţă oferă un sentiment de securitate în cazul în care locaţia principală la
care se află baza este distrusă. Aceste copii pot fi offline, în timp real sau online. Este important să
menţionăm că aceste copii offline necesită întreţinere manuală.

Page 33 of 126
Imaginea 3.23. Prezentarea copierii de rezervă la distanţă a bazei de date

Sistemele de securitate a reţelei sunt mai frecvent utilizate în practică şi sunt mai uşor de
utilizat. Sistemul de backup online este un mecanism în care pentru fiecare bit de date în timp real
se face o copie de rezervă simultan în două locuri, în sistem şi într-o locaţie la distanţă. De îndată ce
memoria primară a bazei de date se confruntă cu o întrerupere, sistemul de rezervă mută sistemul de
utilizator într-o locaţie de stocare la distanţă. Uneori, această execuţie este atât de rapidă încât
utilizatorii nu pot observa defecţiunea.

Recuperarea bazei de date

DBMS este un sistem extrem de complex, cu sute de tranzacţii executate în fiecare secundă. Durabilitatea
şi robusteţea unui DBMS depind de arhitectura sa complexă şi de hardware-ul şi software-ul de bază al
sistemului. Dacă eşuează sau încetează să funcţioneze în timpul execuţiei unei tranzacţii, se aşteaptă ca
sistemul să urmeze un fel de algoritm sau tehnică pentru a recupera datele pierdute. Pentru detectarea
mai uşoară a problemelor, distingem mai multe categorii de eşecuri, şi anume:

• tranzacţii eşuate;
• căderea (nefuncţionarea) sistemului;
• defecţiuni ale discului.

Tranzacţiile eşuate - o tranzacţie eşuează atunci când nu poate fi executată sau când atinge un anumit
punct de execuţie care nu este final. Motivele eşecului tranzacţiei pot fi:

• erorile logice - erori în cod şi alte erori interne;


• erorile de sistem - atunci când sistemul termină tranzacţia în sine, deoarece DBMS nu o poate
executa sau trebuie să se oprească din cauza unei alte stări a sistemului; cauza este oprirea şi
indisponibilitatea resurselor.

Page 34 of 126
Căderea sistemului presupune o întrerupere a sistemului din cauza unor factori externi care afectează
sistemul, precum, de exemplu, problema cu alimentarea.

Defecţiuni ale discului - indisponibilitatea discului, deteriorarea capului discului sau orice altă
defecţiune care duce la pierderea unei părţi sau a tuturor datelor.

Structura memoriei în funcţie de securitate poate fi clasificată în două categorii de bază:

• Memoria nevolatilă nu poate supravieţui căderii sistemului. Aceste dispozitive de stocare sunt
situate aproape de procesor. Exemplele clasice ale acestei memorii sunt memoria principală şi
memoria cache. Avantajul lor este viteza, iar dezavantajul lor este stocarea unei cantităţi mici de
date.
• Memoria permanentă este creată pentru a rezista unei căderi de sistem. Exemple de astfel de
dispozitive sunt hard disk-ul, discurile magnetice, memoria flash şi memoria RAM de securitate.
Avantajul lor este capacitatea de a stoca cantităţi mari de date, în timp ce principalul dezavantaj
este accesibilitatea lentă a datelor.

Când sistemul se blochează, puteţi să efectuaţi mai multe tranzacţii şi să deschideţi diferite fişiere de
modificare a datelor. Tranzacţiile sunt realizate din diferite operaţiuni, care sunt de natură atomică.
Conform proprietăţilor ACID ale DBMS-ului, trebuie menţinută atomicitatea tranzacţiilor în ansamblu –
adică fie se vor efectua toate operaţiunile, fie nu se va efectua niciuna.

Când DBMS se recuperează după o cădere a sistemului, trebuie să facem următoarele:

• verificarea stării tuturor tranzacţiilor care au fost efectuate;


• asigurarea atomicităţii în cazul în care oricare dintre tranzacţii este întreruptă;
• verificarea dacă tranzacţia poate fi executată sau trebuie încheiată;
• nicio tranzacţie nu poate lăsa DBMS-ul într-o stare inconsecventă.

Există două tehnici care pot ajuta DBMS-ul în procesul de recuperare şi menţinere a atomicităţii, şi
anume:

• întreţinerea jurnalelor fiecărei tranzacţii şi scrierea lor în memoria stabilă, înainte de a avea loc
modificările în baza de date;
• întreţinerea schiţelor, unde se fac modificări în memoria nesigură şi ulterior se actualizează baza
de date reală.

Recuperarea pe baza jurnalelor de tranzacţii se realizează pe baza unei serii de înregistrări care ţin
evidenţa acţiunilor efectuate asupra tranzacţiilor. Este extrem de important să păstraţi înregistrările pe
un mediu stabil, înainte de a se face modificările. Recuperarea bazată pe jurnal funcţionează în felul
următor:

• fişierul jurnalului este stocat pe un mediu de stocare stabil;


• când tranzacţia intră în sistem şi începe executarea, aceasta scrie un jurnal despre operaţiunea
respectivă: <Tn, Start>;
• când o tranzacţie modifică un element X, acesta îl înregistrează în felul următor: <Tn, Ks, V1,
V2>, unde V1 şi V2 reprezintă vechea şi noua valoare pentru X;
• când tranzacţia este executată, se va lista: <Tn, commit>.

Page 35 of 126
Există două abordări ale modificării bazei de date.

Modificarea anulată înseamnă că toate înregistrările sunt scrise într-o memorie stabilă şi că baza de
date este actualizată când începe tranzacţia.

Modificarea curentă implică faptul că fiecare înregistrare urmează o modificare reală a bazei de date.
Adică, baza de date se schimbă imediat după fiecare operaţiune.

Recuperarea tranzacţiilor paralele - când mai multe tranzacţii sunt executate în paralel, jurnalele sunt
interconectate. În momentul recuperării, ar fi dificil pentru sistemul de recuperare să retragă toate
înregistrările. Pentru a facilita această situaţie, majoritatea DBMS-urilor moderne folosesc conceptul de
puncte de control.

Punctele de control – stocarea şi menţinerea înregistrărilor în timp real şi într-un mediu real, poate
umple tot spaţiul de memorie disponibil în sistem. Punctele de control sunt un mecanism în care toate
înregistrările anterioare sunt eliminate din sistem şi stocate permanent pe un disc de memorie.

Când sistemul cu puncte de control eşuează şi se recuperează, acesta se comportă în felul următor:

• citeşte înregistrările înapoi, de la sfârşit până la ultimul punct de control;


• întreţine două liste: lista de anulare şi lista de revenire la starea anterioară (undo şi redo);
• dacă sistemul de recuperare vede înregistrarea <Tn, Start> şi <Tn, Commit> sau
doar <Tn, Commit>, acesta pune tranzacţia în lista redo;
• dacă sistemul de recuperare vede înregistrarea <Tn, Start>, dar nu se găseşte niciun jurnal
sau jurnal de întrerupere, acesta plasează tranzacţia în lista undo.

Imaginea 3.24. Prezentarea eşuării sistemului în timpul execuţiei tranzacţiei

Acum, când avem o imagine clară despre ce sunt tranzacţiile, cum sunt utilizate şi ce rol joacă în bazele
de date, vom arăta funcţionarea lor pe un exemplu concret. Să revenim la exemplul nostru de tranzacţie
de bani din contul A în contul B. Mai devreme în lecţie, am arătat cum ar arăta acest exemplu în

Page 36 of 126
programarea orientată pe obiecte. Cu toate acestea, pentru ca datele din baza de date să fie actualizate,
adică pentru ca modificările să fie permanente şi în timp real, tranzacţiile trebuie să îşi efectueze toate
operaţiunile pe baza de date în care sunt introduse datele.

Pentru început, în program vom introduce biblioteca cu care trebuie să lucrăm la baza de date, folosind
comanda import sqlite3. Apoi, facem o conexiune cu fişierul [Link] şi introducem cursor.
În fişierul nostru, am creat un tabel care stochează informaţii despre deţinătorii de conturi şi soldul
conturilor lor, în care apoi introducem informaţii despre conturile A şi B. Aceşti paşi sunt abordaţi în
detaliu în lecţia Gestionarea bazei de date din cadrul cursului Web Application Building.

Identic exemplului nostru bazat pe programarea orientată pe obiecte, vom reduce soldul contului
angajatului A cu 100 USD, iar angajatul B va avea încasată aceeaşi sumă în cont. Acum, după ce am
introdus toate comenzile de care avem nevoie pentru a modifica datele noastre în baza de date, vom
executa tranzacţia cu comanda [Link]().

Să presupunem că în momentul tranzacţiei există o întrerupere după retragerea fondurilor din contul
angajatului A. Această tranzacţie ar duce la pierderea de bani a angajatului A, ai cărui bani nu au fost
depuşi în contul angajatului B. Din aceste motive, introducem un bloc try-catch care permite executarea
tranzacţiei numai dacă toate operaţiunile sale au fost executate. În cazul în care tranzacţia nu poate fi
executată complet, o anulăm cu comanda [Link]().

Prima dată când rulăm codul în exemplul nostru, vom obţine o execuţie cu succes a tranzacţiei. Cu toate
acestea, a doua rulare a tranzacţiei va eşua. Motivul pentru aceasta se află în cheile primare definite,
care trebuie să fie unice. Cu alte cuvinte, odată cu a doua rulare, codul nostru încearcă din nou să
insereze angajaţii ale căror nume sunt A şi B şi care reprezintă cheile primare ale înregistrării şi, motiv
din cauza căruia tranzacţia nu va fi executată cu succes.

Exemplu de tranzacţie cu fonduri de pe contul angajatului A în contul angajatului B, folosind SQLite:

1import sqlite3
2
3conn = [Link]('[Link]')
4cursor = [Link]()
5
6try:
7 [Link]("CREATE TABLE IF NOT EXISTS accounts ("
8 "name TEXT NOT NULL PRIMARY KEY,balance FLOAT NOT NULL)")
9 account = [('A',100.0), ('B', 100.0)]
10 [Link]("INSERT INTO accounts VALUES (?, ?)", account)
11 [Link]("UPDATE accounts SET balance = balance - 100.0 "
12 "WHERE name = 'A'")
13 [Link]("UPDATE accounts SET balance = balance + 100.0 "
14 "WHERE name = 'B'")
15 [Link]()
16 print('Transaction committed!')
17
18exept [Link]:
19 [Link]()
20 print('Transaction rolledback!')

Page 37 of 126
1.4. Analiza datelor şi regresia

Rezumat
• Analiza datelor implică procesul de inspecţie, filtrare, transformare şi modelare a datelor pentru
a genera informaţii utile, a lua decizii de informare şi de a sprijini luarea deciziilor.
• Pentru analiza datelor, putem folosi instrumente de gestionare automată sau limbaje de
programare.
• Analiza datelor are cinci etape cheie: colectarea datelor, curăţarea/filtrarea, proiectarea, studiul
şi activităţile de date.
• Jupyter Notebook este o aplicaţie web de tip open source utilizată pentru a crea şi partaja
documente care conţin cod, ecuaţie, vizualizare şi text în timp real.
• Structura analizei datelor în Python se bazează pe biblioteca pandas. Componentele sale de
bază sunt coloanele şi cadrele de date. Oferă posibilitatea de încărcare a fişierelor CSV, JSON şi
SQL. Aceasta susţine funcţiile de găsire a valorilor inexistente - isna().sum(), eliberarea
acestor valori - [Link]() şi înlocuirea acestora - [Link](), precum şi multe
alte funcţii destinate pregătirii datelor pentru analiză, precum, de exemplu, eliminarea
duplicatelor pd.drop_duplicates().
• Matplotlib este o bibliotecă pentru vizualizarea datelor. Ea foloseşte biblioteca numerică NumPy.
Fiecare schiţă 2D din această bibliotecă trebuie să aibă o zonă de afişare - Figure, care reprezintă
superclasa bibliotecii şi o regiune de axe - Axes, care conţine două obiecte (axa x şi axa y).
Definim schiţa cu linia fig,ax=[Link](nrows, ncols).
• NumPy este o bibliotecă numerică pe care se bazează şiruri n-dimensionale - ndarray. Aceste
şiruri stochează o colecţie de date de acelaşi tip în n dimensiuni. Fiecare element din acest şir
are o dimensiune de bloc atribuită în memorie şi fiecare element din memorie reprezintă un
obiect de tip dată sau dtype.
• SciPy este o bibliotecă de calcul avansat în domeniile matematicii, ştiinţei şi ingineriei. Necesită
suport pentru biblioteca NumPy pentru a manipula şiruri n-dimensionale. Conţine module
pentru operaţiuni de grupare, constrângeri fizice şi matematice, interpolare, intrări şi ieşiri,
algebră liniară, optimizare etc.
• Cea mai populară bibliotecă pentru învăţarea automată este scikit-learn. Conţine diferiţi
algoritmi pentru clasificare, regresie, grupare etc. Are un modul încorporat pentru setul de date
de generare a datelor artificiale. Foloseşte funcţia fit() pentru a crea un model cu parametrii
doriţi şi funcţia predict() pentru prezicere.
• Regresia presupune o examinare a dependenţelor componentelor de ieşire de componentele
de intrare. Regresia poate fi simplă (cu o singură ieşire) şi multiplă (cu mai multe ieşiri). Scopul
este de a genera valori cât mai mici de deviaţii pătrate. Foloseşte scikit-learn, NumPy şi
Matplotlib ca biblioteci necesare. Aplică metoda train/test, care împarte datele în două porţiuni:
pentru construirea modelului şi pentru testarea preciziei modelului.

Analiza datelor presupune procesul de inspecţie, filtrare, transformare şi modelare a datelor, cu scopul
generării informaţiilor utile, luării deciziilor de informare şi suport în acestea. Prima etapă a procesului
de analiză a datelor necesită colectarea datelor. Apoi, datele colectate sunt modelate, adică adaptate la
sistemul informaţional în conformitate cu situaţiile din lumea reală. Din astfel de date modelate, se pot
trage concluzii şi determina anomalii. Rolul cheie al analizei datelor este de a converti datele în informaţii.

Pentru analiza datelor, putem folosi două grupuri de instrumente:

• instrumente de gestionare automată - Excel, Tableau, Looker...;


• limbaje de programare - Python, R, Julia...

Page 38 of 126
Atunci când alegem un instrument pentru analiza datelor, este extrem de important să luăm în
considerare toate dezavantajele şi avantajele pe care ni le oferă aceste instrumente (tabelul 4.1.).

Caracteristicile instrumentelor pentru analiza datelor

Instrumentele de gestionare automată Limbajele de programare


au cod închis au cod deschis
sunt scumpe sunt gratuite şi foarte ieftine
sunt limitabile sunt foarte puternice
sunt uşor de utilizat sunt mai greu de utilizat

Tabelul 4.1. Compararea caracteristicilor instrumentelor pentru analiza datelor

Datorită simplităţii, intuitivităţii, lizibilităţii şi numărului mare de biblioteci care oferă o varietate de
caracteristici, Python este cel mai bun limbaj de codare. Disponibilitatea sa publică este un alt avantaj
care îl situează la un nivel ridicat atunci când alegem un instrument de analiză.

Analiza datelor are loc în mai multe etape cheie, şi anume:

• colectarea datelor - din fişiere (CSV, JSON etc.), baze de date distribuite;
• curăţarea/filtrarea datelor - valori lipsă, date goale, tipuri şi valori nevalide;
• formatarea datelor - sortare, grupare, indexare, legare etc;
• studiul de date - cercetare, analiză statistică, ipoteze, vizualizare;
• activităţi - dezvoltarea modelelor pentru învăţarea automată, crearea de rapoarte, inginerie,
procese ETL etc.

Există mai multe biblioteci de analiză a datelor pe care le foloseşte Python. Cele mai frecvent utilizate
sunt:

• pandas - biblioteca pe care se bazează structura de analiză a datelor în Python;


• Matplotlib - biblioteca pe care se bazează vizualizarea datelor;
• NumPy - o bibliotecă numerică pe care se bazează şiruri n-dimensionale;
• SciPy - o bibliotecă pentru calcul ştiinţific care include funcţii de optimizare, algebră liniară,
procesare de imagini etc;
• scikit-learn - cea mai populară bibliotecă de învăţare automată.

În acest curs, vom parcurge toate aceste biblioteci, cu accent pe pandas, pe care se bazează toate
celelalte. În consecinţă, prin intermediul pandas, vom explica întregul proces de pregătire a datelor
pentru analiză. Vom folosi aplicaţia web Jupyter Notebook pentru prezentarea lucrului prin biblioteci.

Jupyter Notebook

Jupyter Notebook este o aplicaţie web open source utilizată pentru a crea şi partaja documente,
conţinând cod, ecuaţie, vizualizare şi text în timp real. Numele Jupyter provine din limbajele de
programare de bază pe care le acceptă: Julia, Python şi R. Această aplicaţie nu este inclusă în limbajul
de programare Python. Prin urmare, utilizarea sa necesită instalare, pe care o vom efectua apelând pip
install jupyter în linia de comandă.

Page 39 of 126
Acum că am instalat Jupyter, pentru a-l rula trebuie să creăm un folder, ceea ce facem, de asemenea, în
linia de comandă. Când creăm un folder, este important să fim atenţi la directorul în care ne găsim. Se
recomandă ca acesta să fie un director pentru stocarea documentelor, cum ar fi, de exemplu, Documents.
Când suntem în directorul dorit, cu comanda jupyter notebook în linia de comandă ne conectăm la
serverul local la linkul [Link] care se deschide în browserul nostru.

Imaginea 4.1. Prezentarea aplicaţiei web Jupyter Notebook pe serverul nostru local

În imaginea 4.1. se poate observa că unele documente Notebook există deja pe serverul nostru. Aceste
documente pot fi create folosind comanda New, care se află în colţul din dreapta deasupra tabelului cu
documentele existente. Când dăm clic pe opţiunea New, vom selecta ca opţiune Python 3. Aceasta
înseamnă că celulele din documentul nostru vor folosi nucleul Python 3, adică codul listat în celule va fi
citit în acel limbaj de programare.

Notă

Nucleul (kernelul) este inima sistemului de operare, respectiv este software-ul care gestionează accesul la
programele de utilizator, hardware-ul şi software-ul de sistem.

Documentul nou creat se numeşte iniţial Untitled. Pentru a defini documentul mai precis, îi vom schimba
numele dând clic pe titlu şi setând un nume nou, de exemplu, Hello Jupyter (imaginea 4.2.).

Imaginea 4.2. Schimbarea numelui documentului Jupyter Notebook

Pentru a testa setările noastre, în celulă vom insera o linie de cod scrisă în limbajul de programare
Python 3 şi o vom rula folosind comanda Run (Shift + Enter). Codul corect va afişa rezultatul pe ecran
după pornire (imaginea 4.3.).

Page 40 of 126
Imaginea 4.3. Prezentarea testării celulelor setate pe nucleul Python 3

Celulele din Notebook sunt numerotate şi rulate în această ordine. Au capacitatea de a partaja variabile
şi documente importate, ceea ce permite împărţirea codului în unităţi logice fără creare multiplă.
Stilizarea textului poate fi realizată şi în celule.

Pentru o navigare mai uşoară în program, vom arăta pe scurt posibilităţile oferite de meniul acestui
program:

• File – crearea unui document nou sau rularea celui existent;


• Edit - editarea celulelor (tăierea, copierea, lipirea, concatenarea, ştergerea, separarea);
• View – setarea interfeţei programului (antet/header, bara de instrumente, numerotarea liniei
de cod);
• Insert - inserarea celulelor;
• Cell – pornirea unei celule, a unui grup de celule sau a tuturor celulelor, modificarea tipului de
celulă, ştergerea output-ului;
• Kernel - schimbarea, resetarea, reconectarea, oprirea kernelului;
• Widgets - adăugarea dinamismului la cod, folosind JavaScript;
• Help - ajutor la utilizarea programului.

Pe lângă documente, Jupyter Notebook permite şi pornirea fişierelor text, a folderelor şi a


terminalelor. De asemenea, susţine fila Running şi Clusters în care putem urmări ce documente şi
terminale sunt active în prezent.

Biblioteca pandas

Biblioteca pandas este cel mai important instrument disponibil oamenilor de ştiinţă şi analiştilor de
astăzi care lucrează în Python. Deşi există astăzi instrumente mult mai puternice pentru vizualizare şi
învăţare automată, biblioteca pandas este coloana vertebrală a majorităţii proiectelor atunci când vine
vorba de lucrul cu date. Această bibliotecă a primit numele ca derivat al cuvintelor panel şi data (date
de tip „Panel”), care este un termen econometric pentru seturile de date care includ observaţii pentru
aceleaşi date pe mai multe perioade.

Biblioteca pandas nu este doar o componentă centrală a instrumentului de ştiinţă privind datele, ci este
utilizată în combinaţie cu alte biblioteci din acea colecţie. Este construită deasupra pachetului NumPy,
ceea ce înseamnă că multe structuri NumPy sunt utilizate sau reproduse în pandas. Datele în pandas
sunt, de asemenea, deseori folosite pentru afişarea analizei statistice în SciPy, desenând funcţii din
Matplotlib şi algoritmi de învăţare automată în scikit-learn.

Jupyter Notebook oferă un mediu bun pentru utilizarea bibliotecii pandas, pentru explorarea şi
modelarea datelor, deşi această bibliotecă poate fi utilizată şi în editoare de text simple. Jupyter

Page 41 of 126
Notebook ne oferă posibilitatea de a executa un cod într-o anumită celulă, în loc să rulăm întregul fişier.
Acest lucru economiseşte mult timp atunci când lucrăm cu seturi de date mari şi transformări complexe.
De asemenea, oferă o modalitate uşoară de a prezenta vizual cadrele de date şi diagramele pandas.

Pentru a utiliza biblioteca pandas, trebuie mai întâi să o instalăm. Facem acest lucru folosind
instrucţiunea pip install pandas în linia de comandă. După instalare, vom importa această
bibliotecă în Jupyter Notebook cu comanda import pandas as pd. Acum, când avem la dispoziţie
conţinutul bibliotecii pandas, să aruncăm o privire la unele dintre funcţionalităţile sale de bază.

Cele două componente de bază ale unei biblioteci pandas sunt Series (rândurile)
şi DataFrames (cadrele de date). Putem efectua un set identic de operaţiuni pe aceste componente,
cum ar fi completarea valorilor null şi calcularea valorii medii. Relaţia acestor componente este
prezentată în următoarea imagine, în cea cu numărul 4.4.

Imaginea 4.4. Prezentarea structurii componentelor de bază ale bibliotecii pandas

Există mai multe moduri de a crea un cadru de date într-o bibliotecă pandas, iar cel mai simplu mod
este de a utiliza un dicţionar de date. Imaginaţi-vă că rândurile observate sunt mesele disponibile într-
o cafenea şi că coloanele coffee şi biscuit reprezintă numărul de cafele băute şi de biscuiţi mâncaţi la o
anumită masă. Pentru a grupa comenzile pe tabele, vom introduce variabila tables în care vom stoca
cadrul nostru de date folosind un constructor simplu al bibliotecii pandas, acesta
fiind [Link](). Acest constructor primeşte dicţionarul nostru table ca parametru, pe care îl
vom defini înainte de a apela acest constructor. Pentru a afişa rezultatele acestui cadru de date, este
suficient listăm doar tables în linia de cod.
Exemplu de creare a cadrului DataFrame în biblioteca pandas:
1
2
3import pandas as pd
4
5table = {
6 'coffee': [3, 4, 5, 6],
7 'biscuit': [8, 7, 0, 2]
8}
9tables = [Link](table)
10tables

Page 42 of 126
Imaginea 4.5. Rezultatul apelării constructorului DataFrames() în mediul Jupyter Notebook

După cum se poate vedea în imaginea 4.5., constructorul DataFrame() combină date din două tabele
utilizând metoda cheie-valoare. În acest caz, cheilor li se atribuie automat numere cuprinse între 0 şi 3.
Aceste chei pot fi definite şi de către utilizator, cea ce este prezentat în continuare.

Exemplu de atribuire manuală a indexului DataFrame în cadrul datelor în biblioteca pandas:

1import pandas as pd
2
3table = {
4 'coffee': [3, 4, 5, 6],
5 'biscuit': [8, 7, 0, 2]
6}
7
8tables = [Link](table,
9index=['Table A','Table B','Table C','Table D'])
10
11tables

Imaginea 4.6. Rezultatul atribuirii manuale a indexului în constructorul DataFrames() în mediul Jupyter
Notebook

Comenzile grupate în acest mod sunt acum mai uşor de căutat. Funcţia de căutare utilizată de pandas
este loc[]; rolul său este de a returna valoarea datelor pentru cheia dată. Dacă, de exemplu, am fi vrut
să aflăm ce au comandat oaspeţii de la masa B, am putea face acest lucru cu o linie de cod simplă, şi
anume: [Link]['Table B'].

Page 43 of 126
Imaginea 4.7. Rezultatul căutării cadrului de date folosind funcţia loc[] în mediul Jupyter Notebook

Biblioteca pandas are capacitatea de a citi date din fişiere CSV, JSON şi SQL, precum şi posibilitatea de
a converti datele înapoi în acele fişiere. Citirea acestor fişiere se bazează pe acelaşi principiu - prin rularea
funcţiilor destinate citirii fişierului respectiv. Pentru o mai mare simplitate, vom vedea cum arată mai
detaliat pe exemplul unui fişier CSV.

În primul rând, vom crea un fişier CSV din care vom putea extrage date. Vom deschide un nou fişier
Microsoft Excel, şi în el vom introduce cadrul de date din exemplul nostru ca în prima imagine (imaginea
4.8.), apoi îl vom salva în format CSV, aşa cum se poate vedea în prima imagine (imaginea 4.8.). Vom
salva acest fişier CSV sub numele tables, deoarece îl vom citi ulterior sub acel nume.

Imaginea 4.8. Pregătirea cadrului de date tables pentru salvarea în format CSV

Imaginea 4.9. Salvarea cadrului tables în formatul fişierului CSV

Page 44 of 126
Vom importa apoi fişierul creat în acest mod în Jupyter Notebook, folosind butonul Upload, făcând
astfel toate pregătirile pentru citirea datelor din acest fişier. Deoarece fişierele CSV nu conţin o coloană
a indexului, trebuie să setăm valoarea coloanei index 0 atunci când apelăm funcţia de citire a acestui
fişier. În continuare, prezentăm comanda care va citi fişierul nostru CSV. Această comandă oferă
rezultatul ca în imaginea 4.6.

Exemplu de citire a datelor din fişierul [Link]:

1csv_data = pd.read_csv('[Link]', index_col=0)


2csv_data

Citirea datelor scrise în format JSON se face într-un mod similar, ceea ce se vede clar din exemplul
următor.

Exemplu de citire a datelor din fişierul [Link]:

1 json_data = pd.read_json('[Link]')
2
3 json_data

Când citim datele din baza de date, situaţia este oarecum diferită. Aici trebuie să stabilim mai întâi o
conexiune cu baza de date, pentru a transmite ulterior acea conexiune ca parametru la funcţia noastră
de citire, împreună cu interogarea SQL ca prim parametru.

Exemplu de citire a datelor din fişierul [Link]:

1import sqlite3
2
3con = [Link]('[Link]')
4 sql_data = pd.read_sql_query(SELECT * FROM tables, con)
5
6sql_data

După ce am terminat de lucrat la datele noastre, le putem salva din nou în formatul dorit. Următorul
exemplu arată cum stocăm date, adică cum le introducem în formatul dorit. Rularea funcţiilor de mai
jos creează fişiere noi, în timp ce bazele de date creează un tabel complet nou.

Exemplu de înscriere a datelor în formatul dorit (CSV, JSON, SQL):

1csv_data.to_csv('new_tables.csv')
2json_data.to_json('new_tables.json')
3sql_data.to_sql('new_tables', con)

Biblioteca pandas are un număr incredibil de posibilităţi. Pentru a acoperi majoritatea, vom avea nevoie
de o bază de date mai mare, cu date mai diverse. De la acest link vom descărca baza de date finalizată
a filmelor şi serialelor TV Netflix, create în format CSV şi apoi le vom încărca în Jupyter Notebook. Citim
fişierul în acelaşi mod ca şi până acum.

Page 45 of 126
Exemplu de citire a fişierului netflix_titles.csv descărcat de pe site-ul [Link]:

1import pandas as pd
2
3 titles = pd.read_csv('netflix_titles.csv', index_col=0)

După ce am realizat primul pas, adică după colectarea datelor, este extrem de important să avem o
imagine de ansamblu vizuală a aspectului datelor noastre. Cel mai simplu este să apelăm
funcţiile head() sau tail(), care ne oferă o perspectivă asupra primelor şi ultimelor înregistrări. Cu
aceste funcţii este, de asemenea, posibil să transmitem un parametru sub forma numărului de
înregistrări pe care dorim să le afişăm, aşa cum vom face cu comanda [Link](3).

Imaginea 4.10. Prezentarea celor trei înregistrări ale fişierului netflix_titles.csv folosind funcţia head(3) în
mediul Jupyter Notebook

Pentru o perspectivă rapidă asupra structurii bazei de date şi a tipurilor de date cu care lucrăm, folosim
funcţia info(). Deci, în cazul nostru, apelând [Link](), vom obţine informaţiile prezentate în imaginea
4.11.

Imaginea 4.11. Prezentarea structurii datelor fişierului netflix_titles.csv folosind funcţia info() în mediul
Jupyter Notebook

Page 46 of 126
O altă funcţie simplă este funcţia shape. Apelând-o, obţinem numărul de rânduri şi coloane sub forma
unui n-tuplu. În cazul nostru, [Link] va da rezultatul (6234, 11).

Următorul pas este eliminarea tuturor duplicatelor din baza de date. Concret, în prezent, există un
duplicat în baza noastră de date. Faptul că avem înregistrări repetate în baza de date afectează relevanţa
datelor care sunt prezentate ca rezultat final al unei analize. Din acest motiv, trebuie să eliminăm
duplicatele. Biblioteca pandas permite acest lucru cu funcţia drop_duplicates(). După execuţia
comenzii titles.drop_duplicates(), vom rula şi comanda [Link] pentru a confirma că
rezultatul este acum (6233, 11), respectiv 6233 rânduri şi 11 coloane.

Ca şi la duplicate, nici în tabel nu se recomandă să avem valori inexistente, deoarece astfel de valori sunt
ignorate de toate operaţiunile, cum ar fi însumarea, media, numărarea şi altele. Dacă ar fi să procesăm
astfel de date, rezultatele noastre şi-ar pierde relevanţa. Astfel de valori apar în biblioteca pandas ca
valori None şi [Link]. Există două metode pentru a rezolva această problemă, şi anume:

• ştergerea rândurilor care conţin valori inexistente;


• înlocuirea valorilor inexistente cu alte valori.

Funcţia care ne facilitează numărarea acestor valori este funcţia isnull(), pe care o vom apela cu
comanda [Link]() pentru baza noastră de date. Imaginea de mai jos ilustrează cum această
funcţie setează valorile celulei la True şi False în raport cu faptul dacă valoarea există sau nu în celulă
(imaginea 4.12.).

Imaginea 4.12. Rezultatul utilizării funcţiei isnull() asupra fişierului netflix_titles.csv în mediul Jupyter
Notebook

Această funcţie nu poate include vizual toate celulele cu valori inexistente şi este aproape imposibil să
se numere astfel de valori. Din acest motiv, a fost introdusă funcţia isnull().sum(), care rezolvă
această problemă afişând toate valorile inexistente în mod colectiv pe coloane. Rularea
comenzii [Link]().sum() returnează rezultatele prezentate în imaginea 4.13.

Page 47 of 126
Imaginea 4.13. Prezentarea sumară a valorilor inexistente folosind funcţia isnull().sum() în mediul
Jupyter Notebook

Următorul pas este de a decide dacă vom şterge valorile inexistente sau acestea vor obţine o altă valoare.
În cazul în care dorim să scăpăm de date fără valori, vom folosi funcţia dropna(), respectiv, concret
pentru fişierul nostru, aceasta va fi funcţia [Link]().

Dacă analizăm mai atent cantităţile de date lipsă, ştergerea acestor înregistrări va duce la pierderea a
cel puţin o treime din baza de date pentru care acele date, care nu au valoare, pot să nu fie cruciale. Un
alt mod de a şterge valori inexistente este de a elimina numai coloanele care conţin valori inexistente.
Acest lucru se face prin specificarea parametrului axis=1 în funcţa dropna().

Cu toate acestea, ştergerea datelor nu este în general o bună practică. O soluţie mai comună la această
problemă este de a atribui valoare datelor. Această valoare poate fi de tip mean sau median.
Valoarea mean reprezintă media tuturor datelor introduse din acea coloană, în timp ce median caută
elementul central al numărului total de elemente din coloană, ignorând valoarea sa. Apoi, valoarea
găsită este atribuită tuturor elementelor care nu au o valoare, în cazul nostru prin
funcţia [Link]().

Deoarece în tabelul nostru nu există valori numerice ale căror medie o putem atribui cuiva, trebuie să
punem datele text într-o anumită ordine. Dacă suntem atenţi la raportul rezumat al valorilor inexistente
pe coloane, vom observa că aproape o treime din înregistrări nu au informaţii despre regizor (director).

Pentru început, vom separa coloana regizorilor plasând lista de regizori în variabila directors. Vom
afişa apoi primele cinci înregistrări pentru cadrul de date nou creat, pentru a avea o perspectivă asupra
aspectului datelor noastre din acea înregistrare în prezent (imaginea 4.15.).

Exemplu de extragere a coloanei cu scopul reglării valorii NA:

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4directors = title['director']
[Link]()

Page 48 of 126
Imaginea 4.14. Prezentarea primelor cinci înregistrări din lista de regizori, pregătite pentru procesarea
datelor NA în mediul Jupyter Notebook

Ceea ce rămâne de făcut este înlocuirea valorii NA cu unele date specifice. Pentru înlocuire, folosim
funcţia fillna(), căreia îi transmitem valoarea parametrului obligatoriu, care reprezintă noua valoare
pe care dorim să o setăm; în cazul nostru va fi Unknown. Ca parametru, vom transmite şi o valoare
booleană inplace, care, pentru valoarea True, lasă modificări permanente asupra datelor. După această
execuţie vom rula din nou funcţia head() pentru a compara rezultatele.

Exemplu de schimbare a valorii director folosind funcţia fillna():

[Link](value='Unknown', inplace=True)
[Link]()

Imaginea 4.15. Schimbarea valorii NA a datelor din coloana director folosind funcţia fillna() în mediul
Jupyter Notebook

Această procedură trebuie repetată pentru toate coloanele, până când toate valorile inexistente sunt
eliminate. Fără acest pas, nu are rost să începem prelucrarea datelor.

Există şi alte beneficii pe care ni le oferă stocarea pe coloane. Iată cum, de exemplu, am împărţi
înregistrările într-o categorie de filme şi într-o categorie de seriale TV.

Exemplu de categorizare şi prezentare sumară a înregistrărilor în funcţie de scop

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4types = titles['type']
5types.value_counts()

Page 49 of 126
Imaginea 4.16. Rezultatul categorizării şi prezentării sumare a înregistrărilor folosind funcţia
value_counts() în mediul Jupyter Notebook

Atunci când analizăm date, vom întâlni deseori situaţii în care va trebui să tăiem, să selectăm sau să
extragem o parte din date pentru procesare. Pentru a prelucra date, trebuie să ştim de ce tip sunt, lucru
pe care ni-l asigură funcţia type(), care primeşte o dată ca parametru şi returnează tipul său. Dacă, de
exemplu, transmitem variabila noastră types, definită în exemplul anterior, ca parametru al acestei funcţii,
respectiv dacă executăm type(types), ca rezultat vom obţine [Link],
ceea ce indică faptul că este vorba de o coloană.

Pentru a obţine un cadru de date, este suficient să formăm înregistrările unei coloane ca listă. Să vedem
cum arată acest lucru în exemplul formării unui cadru care conectează coloanele titlului filmului şi anul
lansării release_date în variabila titles_df.

Exemplu de formare a cadrului de date DataFrame prin unirea coloanelor

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4titles_df = titles[['title','release_year']]
5type(titles_df)
6titles_df.head()

Comanda type(titles_df), de această dată, va returna [Link], deoarece


este vorba de un cadru de date. Pentru a vedea cum arată acum datele noastre,
folosim titles_df.head() pentru a afişa primele cinci înregistrări din tabelul nostru (imaginea 4.17.).

Imaginea 4.17. Prezentarea cadrului DataFrame prin unirea coloanelor title şi release_year în formă de
listă în mediul Jupyter Notebook

Page 50 of 126
Pentru a lucra cu părţi din listă, putem folosi tăierea datelor. Să presupunem că vrem să luăm primele
trei valori din tabelul de mai sus (imaginea 4.17.). Funcţia iloc() ne permite să facem acest lucru luând
indexurile înregistrărilor din tabel ca parametru şi returnând partea tăiată a tabelului. Să vedem cum
arată primele trei înregistrări pe exemplul tabelului nostru nou format.

Exemplu de tăiere a listei de date folosind funcţia iloc():

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4titles_df = titles[['title','release_year']]
5sliced_titles = titles_df.iloc[0:3]
6sliced_titles

Imaginea 4.18. Tăierea listei de date folosind funcţia iloc() în mediul Jupyter Notebook

De asemenea, putem să utilizăm condiţii pentru selectarea unei părţi a datelor. În exemplul următor,
vom arăta cum să selectăm doar filmele regizate de Christopher Nolan şi Quentin Tarantino dintr-un
tabel care conţine peste 6000 de filme şi seriale TV.

Exemplu de utilizare a condiţiilor pentru selecţia datelor din listă:

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4
5titles_df = titles[['director','title','release_year']]
6
7titles_df[(titles_df['director']=='Christopher Nolan') |
8 (titles_df['director']=='Quentin Tarantino')].head()

Această condiţie ar putea fi scrisă în modul următor, în timp ce rezultatul rămâne neschimbat:

titles_df[titles_df['director'].isin(['Christopher Nolan', 'Quentin Tarantino'])].head()

Page 51 of 126
Imaginea 4.19. Prezentarea selecţiei de date folosind condiţii în mediul Jupyter Notebook

Funcţiile pot fi, de asemenea, aplicate pentru afişarea datelor în biblioteca pandas. Ceea ce trebuie să
facem este să definim o funcţie care va selecta datele aşa cum dorim şi apoi să folosim
funcţia apply() a pachetului pandas pentru a o aplica la datele dorite.
În exemplul nostru, putem vedea cum aplicăm funcţia de categorizare category_function() peste
datele release_year. Funcţia pe care o aplicăm acestor date se comportă în felul următor: ia o dată din
coloană şi stabileşte valoarea sa ca parametru, astfel încât parametrul transmis să fie comparat cu
valoarea din funcţie. Această procedură se repetă pentru fiecare dată din lista pe care executăm funcţia.
În continuare, prezentăm cum arată rezultatele acestei funcţii în primele trei înregistrări ale tabelului
nostru.

Exemplu de utilizare a funcţiei pentru selecţia datelor:

1import pandas as pd
2
3titles = pd.read_csv('netflix_titles.csv', index_col=0)
4
5def category_function(x):
6 if x >= 2015:
7 return "new movie"
8 else:
9 return "old movie"
10
11df = titles[['title','release_year']]
12df['release_category'] = df['release_year'].apply(category_function)
[Link](3)

Imaginea 4.20. Prezentarea rezultatelor de aplicare a funcţiei definite de utilizator category_function()


asupra listei de date în mediul Jupyter Notebook

Page 52 of 126
Biblioteca Matplotlib

Matpolib este una dintre cele mai populare biblioteci de vizualizare a datelor Python. Această bibliotecă
este de tip multi-platformă, adică nu este specifică Python. Este folosită pentru crearea schiţelor 2D pe
baza datelor stocate în şiruri, motiv pentru care foloseşte biblioteca numerică NumPy Python, care va fi
discutată mai detaliat în continuare.

Pentru a instala această bibliotecă, folosim instrucţiunea pip install matplotlib în linia de
comandă, aşa cum am făcut pentru alte instalări. Condiţiile preliminare pentru utilizarea acestei
biblioteci sunt posesia versiunii 2.7 a Python-ului sau o versiune ulterioară, precum şi posesia bibliotecii
NumPy, care vine odată cu instalarea limbajului de programare Python. Mai jos vom arăta cum este
utilizată vizualizarea în Python pe exemplul unei schiţe simple.

Înainte de a începe lucrul, trebuie să se introducă biblioteca Matplotlib sub pseudonimul plt ca o
convenţie de scriere. Apoi vom introduce biblioteca NumPy, sub pseudonimul np, deoarece avem
nevoie de un şir de numere pentru a face schiţa.

Pentru introducerea unui şir n-dimensional, folosim funcţia arrange() a bibliotecii NumPy, ai cărei
primi doi parametri reprezintă intervalul şirului, iar al treilea creşte valorile din acest interval, la fel ca şi
funcţia range(), pe care am abordat-o într-una dintre lecţiile anterioare. Elementelor dispuse în acest
fel pe axa x, le vom adăuga valorile sinusoide ale unghiurilor folosind funcţia sin() şi le vom atribui
pe axa y pentru o reprezentare mai realistă pe graficul nostru. Folosim funcţia plot() a bibliotecii
matplotlib pentru a schiţa graficul nostru. Pentru schiţarea graficului, este posibilă definirea numelor
axelor x şi y, precum şi numele curbei funcţiei definite. Cu datele definite în acest fel, se poate realiza o
reprezentare, ceea ce facem cu comanda show(), care are rolul de a apela o fereastră care va afişa
grafic această schiţă. Să vedem cum arată în cod.

Exemplu de creare a unei schiţe simple, folosind bibliotecile Matplotlib şi NumPy:

1from matplotlib import pyplot as plt


2import numpy as np
3import math
4
5x = [Link](0, [Link]*2, 0.05)
6y = [Link](x)
7
[Link](x,y)
[Link]("ndarray")
[Link]("sin")
[Link]("wave function")
[Link]()

Page 53 of 126
Imaginea 4.21. Prezentarea graficului creat folosind biblioteca Matplotlib în mediul Jupyter Notebook

Există mai multe moduri de a vizualiza date, cum ar fi o histogramă sau o diagramă circulară (pie). Pentru
o vizualizare bidimensională a schiţei, în program, trebuie definită cel puţin o zonă de afişare şi cel puţin
o axă care conţine două obiecte (axa x şi axa y) când vine vorba de vizualizarea 2D. Vizualizarea 3D
trebuie să aibă o axă cu minimum trei obiecte sau trei axe.

Zona de afişare din biblioteca Matplotlib este conţinută în clasa Figure, care este o superclasă a
tuturor claselor de schiţă. Fiecare zonă de afişare poate avea mai multe axe, în timp ce o axă poate fi
conţinută într-o singură zonă. Un obiect al axei sau Axes este o zonă a axei, utilizată pentru afişarea
datelor. Când creaţi schiţe bidimensionale, trebuie să definiţi subschiţe care conţin atât zona de
afişare, cât şi obiectul axei, ceea ce se poate face în felul următor:

fig,ax = [Link](subplot(nrows, ncols, index))

Pentru a ne familiariza mai bine cu materia şi pentru a o încadra în cunoştinţele anterioare din biblioteca
pandas, vom arăta cum Matplotlib poate servi pe exemplul listelor noastre de filme şi seriale TV Netflix.
Să vedem cum ar arăta o histogramă a numărului de înregistrări pe an de lansare.

Exemplu de utilizare a bibliotecii Matplotlib pentru afişarea histogramei de date:

1import pandas as pd
2from matplotlib import pyplot as plt
3import numpy as np
4
5titles = pd.read_csv('netflix_titles.csv', index_col=0)
6values = titles['release_year']
7
8fig,ax = [Link](1,1)
9
[Link](values, bins=[Link](2010,2021,1))
11
12ax.set_title("Release year")
13
14ax.set_xticks([Link](2010,2021,1))
15ax.set_yticks([Link](200, 1200, 200))

Page 54 of 126
16
17ax.set_xlabel('year')
18ax.set_ylabel('number of records')
19
[Link]()

Imaginea 4.22. Prezentarea histogramei create folosind biblioteca Matplotlib pe exemplul


netflix_titles.csv a datelor în mediul Jupyter Notebook

Biblioteca NumPy

Biblioteca NumPy este derivată din cuvântul numerical Python. Această bibliotecă constă din şiruri
multidimensionale de obiecte şi o colecţie de funcţii care sunt efectuate pe aceste şiruri. Cu ajutorul
bibliotecii NumPy, putem efectua operaţii matematice şi logice, manipularea formei, operaţii de algebră
liniară şi generarea valorilor aleatorii.

Combinaţia acestei biblioteci cu Matplotlib şi Scipy, care va fi discutată mai detaliat în continuare, este
deseori utilizată ca înlocuitor pentru limbajul de calcul MatLab. Instalarea acestei biblioteci nu este
necesară, deoarece vine împreună cu instalarea Python.

Cel mai important obiect din biblioteca NumPy este şirul n-dimensional sau ndarray. Acest şir stochează
o colecţie de date de același tip în n dimensiuni. Elementele din această colecţie pot fi accesate folosind
numere de index, începând cu numărul 0. Fiecare element din acest şir are o dimensiune de bloc
atribuită în memorie. Fiecare element din memorie este un obiect al tipului de date sau dtype.
Următoarea imagine arată relaţia dintre un şir n-dimensional şi un obiect de tip dată.

Page 55 of 126
Imaginea 4.23. Relaţia dintre şirul n-dimensional şi obiectul de tip dată

Există mai multe moduri de a crea o instanţă a unei clase a şirului n-dimensional. Comanda de bază
pentru executarea creării este [Link]. Această funcţie poate primi mai mulţi parametri:

[Link](object, dtype = None, copy = True,


order = None, subok = False, ndmin = 0)

Primul parametru se referă la obiectul pe care îl convertim într-un şir. Alţi parametri sunt opţionali şi se
referă la definirea tipului de date, efectuarea copiilor, sortarea, acordarea de permisiuni subclaselor şi
determinarea numărului minim de dimensiuni ale şirului.

Exemplu de creare a şirului n-dimensional folosind biblioteca NumPy:

import numpy as np

a = [Link]([[0, 1], [2, 3]])

Un obiect de tip dată descrie interpretarea unui bloc fix de memorie care corespunde unui câmp, în
funcţie de următoarele aspecte:

• tip de date - poate fi int, float sau Python Object;


• dimensiunea datelor;
• ordinea de octeţi – big endian sau little endian;
• dacă datele sunt structurate - numele câmpurilor, tipul de date şi locaţia memoriei pentru
fiecare câmp;
• dacă datele sunt subşiruri – forma şi tipul de date.

Notă:

Ordinea de octeţi este determinată de prefixul > sau <, care indică modul în care sunt aranjaţi octeţii din
memorie. Folosim semnul > pentru big endian, care funcţionează pe principiul stocării celor mai importanţi
octeţi la cea mai mică adresă, în timp ce small endian foloseşte prefixul < pentru a stoca cel mai puţin importanţi
octeţi la cea mai mică adresă.

Obiectul de tip dată foloseşte structura [Link](object, align, copy), unde avem
parametrul object, care reprezintă tipul de date pe care dorim să îl atribuim; align, a cărui
valoare True stochează totul în aceeaşi locaţie de memorie şi copy, a cărei valoare False indică faptul că
datele vor folosi referinţe la obiecte predefinite.

Page 56 of 126
În continuare, vom arăta un exemplu de obiect de tip dată definit. Acest obiect primeşte date structurate
ca parametru care include o valoare int, o valoare de tip String şi o valoare float. Obiectul de tip dată
este definit în variabila employee.

Exemplu de creare a obiectului de tip dată folosind bibliotecile NumPy:

1import numpy as np
2
3employee = [Link]([('id', 'i1'), ('name','S20'), ('salary', 'f4')])
4
5a = [Link]([(100, 'Jack', 2500.0),(101, 'Johnnie', 1500.0)],
6dtype = employee)

Biblioteca NumPy oferă diverse posibilităţi în ceea ce priveşte operaţiile aritmetice avansate care nu sunt
posibile fără introducerea acestui pachet. Acestea includ crearea matricilor, operaţii matematice,
statistice şi aritmetice, funcţii de stringuri, indexare şi altele. Pentru un exemplu specific de utilizare a
acestei biblioteci, ne putem referi la [Link](start, stop, step, dtype), a cărui aplicare
practică am arătat-o în exemplul nostru cu prelucrarea datelor netflix_titles.csv, în combinaţie cu
biblioteca Matplotlib (imaginea 4.22.).

Biblioteca SciPy

Biblioteca SciPy oferă posibilităţi avansate de calcul în domeniile matematicii, ştiinţei şi ingineriei.
Această bibliotecă depinde de NumPy, care oferă o manipulare convenabilă şi rapidă a şirurilor n-
dimensionale. Este concepută pentru a îmbunătăţi integrarea numerică şi optimizarea acestor şiruri
pentru o mai mare eficienţă şi simplitate în utilizare.

Această bibliotecă vine odată cu instalarea limbajului de programare Python. O opţiune alternativă este
instalarea popularei biblioteci pandas care conţine SciPy, apelând comanda acum cunoscută în linia
noastră de comandă: pip install scipy.

Introducerea bibliotecii SciPy în programul nostru nu necesită introducerea explicită a lui NumPy.
Obiectul principal al NumPy este un şir multidimensional omogen care conţine elemente de acelaşi tip,
indexate de numere întregi pozitive. Dimensiunile acestor şiruri sunt marcate cu axe, în timp ce numărul
de axe reprezintă rangul. Cum funcţionează aceste şiruri cu datele - am avut ocazia să vedem în
segmentul dedicat bibliotecii NumPy şi nu vom explica aici din nou.

SciPy susţine un număr incredibil de posibilităţi şi module care ne permit să efectuăm operaţiuni de
clusterizare, constrângeri fizice şi matematice, interpolare, intrări şi ieşiri, algebră liniară, optimizare şi
multe altele. Modul în care această bibliotecă afectează îmbunătăţirea şirurilor multidimensionale îl vom
verifica în exemplul clusterizării.

K-means clustering este o metodă pentru determinarea grupurilor de date (clustere) şi a centrelor
acestora într-un set de date neetichetate. Un cluster poate fi privit ca un set de date (puncte) a căror
distanţă este mică în comparaţie cu distanţa de la punctele din afara clusterului. K-means repetă
următorii doi paşi în procesul de clusterizare:

• pentru fiecare centru este identificat un set de puncte care sunt mai aproape de el decât de
orice alt centru;
• în fiecare cluster se calculează valoarea medie a caracteristicii de date, care devine noul centru.

Page 57 of 126
Aceşti paşi se repetă până când centrele nu se mai mişcă sau datele nu se mai schimbă. SciPy are o bună
implementare a k-means, pe care o vom testa pe exemplul nostru cu date din fişierul netflix_titles.csv.
Pentru început, vom introduce funcţiile kmeans, vq şi whiten din biblioteca noastră care funcţionează
cu clustere şi apoi vom explica ce înseamnă fiecare dintre ele.

Ca date de lucru, vom prelua lista creată anterior a coloanei release_year din fişierul nostru CSV standard
pentru filme şi seriale TV. Folosind funcţia whiten(), fiecare dintre date este împărţită la abaterea
standard, pentru a obţine variaţia pe unitate. Funcţia kmeans() grupează elementele conform
procedurii explicate mai sus, până când ajunge la numărul de clustere specificat sub forma unui
parametru împreună cu datele transmise pentru clusterizare. Aceste grupuri se numesc centroizi. În cele
din urmă, funcţia vq() compară fiecare vector cu centroizii şi atribuie observaţia celui mai apropiat
cluster. Pentru a ne face o idee despre cum arată aceste date, le vom afişa pe ecran.

Exemplu de clusterizare a datelor folosind biblioteca SciPy:

1import pandas as pd
2from [Link] import kmeans,vq,whiten
3
4titles = pd.read_csv('netflix_titles.csv', index_col=0)
5values = titles['release_year']
6
7values = whiten(values)
8Centroids,_ = kmeans(values,5)
9clx,_ = vq(values,centroids)
10
11print(centroids)
12print(clx)

Rezultatul listării:

[228.69777413 229.03154631 228.08294991 223.96282228 226.87700184]


[1 0 0 ... 0 0 4]

Bilblioteca scikit-learn

Biblioteca scikit-learn este cea mai cunoscută bibliotecă gratuită de învăţare automată Python, care
conţine diverşi algoritmi pentru clasificare, regresie, grupare etc. Învăţarea automată este o ramură a
informaticii care studiază proiectarea algoritmilor care au capacitatea de a învăţa. Sarcinile tipice sunt
învăţarea conceptuală, învăţarea funcţională sau modelarea predictivă, gruparea şi găsirea modelelor
predictibile. Aceste sarcini sunt învăţate prin intermediul datelor disponibile, care sunt observate prin
experienţe şi instrucţiuni.

Deoarece această bibliotecă este construită pe biblioteca SciPy, ea necesită următoarele pachete pentru
a funcţiona: NumPy, SciPy, Matplotlib, iPython, SymPy, pandas. În plus, desigur, trebuie să pornim
instalarea bibliotecii scikit-learn cu comanda pip install sklearn.

După instalare, ca şi în cazul altor biblioteci, începem să lucrăm prin introducerea bibliotecii în program
cu comanda import sklearn. Pentru a arăta cum funcţionează această bibliotecă cu datele, trebuie

Page 58 of 126
mai întâi să le definim. Această bibliotecă are un modul datasets, care generează automat un set de
date artificiale. Următorul exemplu arată cum generăm date pentru a lucra cu funcţia load_digits().

Exemplu de generare a setului de date artificial folosind biblioteca scikit-learn:

1from sklearn import datasets


2
3digits = datasets.load_digits()
4print(digits)

Când rulaţi acest cod, veţi observa că un set de date stocate în dicţionar a apărut în mediul vostru Jupyter
Notebook, ceea ce înseamnă că conţine câteva perechi de cheie-valoare în structura sa. Pentru o
navigare mai uşoară prin acest set de date, vom verifica ce chei din acest set avem, folosind
comanda print([Link](). Comanda dată ne afişează următoarele:

dict_keys(['data', 'target', 'frame', 'feature_names', 'target_names',


'images', 'DESCR']

Acest tip de revizuire a datelor facilitează deja căutarea. Să vedem, de exemplu, ce structură de date
conţin cheile data şi target. Prima cheie reprezintă datele observate şi colectate, în timp ce a doua
reprezintă setul de date ţintă pe care dorim să le înţelegem mai bine. Le vom verifica structura folosind
funcţia deja utilizată, conţinută în biblioteca pandas, care este shape().

Exemplu de testare a structurii de date în biblioteca scikit-learn:

1from sklearn import datasets


2
3digits = datasets.load_digits()
4
5digits_data = [Link]
6print(digits_data.shape)
7
8digits_target = [Link]
9print(digits_target.shape)

Ca rezultat, obţinem (1797, 64) şi (1797,). Aceasta înseamnă că datele sunt stocate în 1797 de rânduri şi
64 de coloane şi că avem 1797 de valori ţintă.

Aşa cum am menţionat deja, biblioteca scikit-learn este complet adaptată învăţării automate. Modulele
sale sunt foarte uşor de utilizat, ceea ce reprezintă un motiv suplimentar pentru aplicarea lor la scară
largă. Mai jos prezentăm o aplicare specifică a acestei biblioteci pe exemplul unui arbore de clasificare
şi regresie.

Începem prin a introduce module pentru seturi de date, valori şi arbori de decizie. Ca set de date, vom
folosi setul încorporat Python cu numele Iris, peste care vom aplica algoritmul CART (Classification and
Regression Trees). Modelul de date pe care îl vom folosi este DecisionTreeClassifier(). Vom transmite
datele generate de sistem şi valorile ţintă către acesta, utilizând funcţia încorporată fit(). Ca date
aşteptate, vom defini datele ţintă, iar pentru datele prezise – predicted, vom utiliza funcţia

Page 59 of 126
încorporată predict(), căreia îi vom transmite datele. În cele din urmă, listăm rezultatele clasificării
(dacă sunt precise sau nu) şi a matricei de confuzie.

Exemplu de clasificare a arborelui şi regresiei, folosind biblioteca scikit-learn:

1from sklearn import datasets


2from sklearn import metrics
3from [Link] import DecisionTreeClassifier
4
5dataset = datasets.load_iris()
6model = DecisionTreeClassifier()
[Link]([Link], [Link])
8
9expected = [Link]
10predicted = [Link]([Link])
11print(metrics.classification_report(expected, predicted))
12print(metrics.confusion_matrix(expected, predicted))

Imaginea 4.24. Rezultatele preciziei de clasificare şi a matricei de confuzie în mediul Jupyter Notebook

Regresia

Ştiinţa privind datele şi învăţarea automată, este stimulată de decizii în sectoarele financiare şi energetice,
rezultă progrese în medicină, creşterea utilizatorilor de reţele sociale şi multe altele. Regresia liniară este
una dintre tehnicile de bază ale învăţării statistice şi automate. Fie că doriţi să faceţi statistici, fie învăţare
automată sau calcul ştiinţific, sunt foarte mari şansele să întâlniţi regresia liniară în acest proces.

Regresia caută relaţii între variabile. De exemplu, putem încerca să determinăm dependenţa matematică
a preţurilor caselor pe baza suprafeţei lor, numărului de dormitoare, distanţa de centrul oraşului şi altele.
În general, în analiza de regresie luăm, de obicei, în considerare un fenomen care ne interesează şi facem
observaţii. Fiecare observaţie are două sau mai multe caracteristici. O altă presupunere este că cel puţin
una dintre aceste caracteristici depinde de celelalte. Scopul regresiei este de a încerca să stabilească o
dependenţă între aceste caracteristici.

Cu alte cuvinte, avem nevoie de o funcţie care să mapeze destul de bine unele caracteristici sau variabile
cu altele. Această funcţie are intrări ca variabile independente care sunt, de asemenea,
numite predictori şi ieşiri ca variabile dependente, adică output-uri. Variabilele independente x pot fi
continue, discrete şi categorice (sex, naţionalitate etc.), în timp ce variabilele dependente y sunt, de

Page 60 of 126
obicei, alcătuite dintr-o variabilă care este continuă. Dacă există două sau mai multe variabile
independente, acestea se notează ca vectorul x=(x1,...xn), unde n este numărul de vectori.

Regresia liniară, în funcţie de numărul de ieşiri, poate fi clasificată în regresie simplă, adică cu o singură
ieşire şi multiplă, adică o regresie care are mai multe ieşiri. În cele ce urmează, ne vom baza explicaţiile
pe exemplul regresiei simple.

Dacă notăm coeficientul de corelaţie de intrare şi ieşire cu variabila b0 şi panta liniei de regresie cu
variabila b1, formula care determină funcţia de regresie liniară va arăta astfel:

y = b0 + b1*x

Să presupunem că y se referă la o caracteristică a greutăţii corporale şi vrem să examinăm dacă se


schimbă odată cu modificările înălţimii x. Deci, în acest caz, greutatea ar fi o variabilă dependentă, iar
înălţimea ar fi independentă. În exemplul nostru, vom lucra la un astfel de set de date. Scopul este de a
crea un model de regresie care va avea ca rezultat cât mai multe valori aparţinând dreptei liniare şi cât
mai puţine valori cu deviaţii pătrate.

Ca set de date pentru lucru, de la acest link vom prelua date disponibile publicului. Este vorba de un
fişier [Link], care conţine date despre înălţimea şi greutatea persoanelor selectate aleatoriu. Încărcăm
fişierul folosind biblioteca pandas, aşa cum am explicat mai devreme în această lecţie.

Notă:

Este foarte important să încărcaţi fişierul în Jupyter Notebook, astfel încât datele să poată fi încărcate. În caz
contrar, va apărea o eroare privind un fişier inexistent.

Apoi stocăm datele din fişierul încărcat în variabilele x şi y, luând valori din locaţiile indexate folosind
funcţia iloc[], care colectează toate datele din coloana dată. Înainte de a începe crearea modelului,
trebuie să împărţim datele din coloanele x şi y în date de testare şi instruire (test and training data).
Datele de testare sunt o parte a datelor care sunt testate, în timp ce setul de training este utilizat pentru
a construi un model, adică ajustăm modelul pe baza acestor date. Ca parametru al
funcţiei train_test_split, care separă variabilele, introducem două variabile noi, în care stocăm
datele şi dimensiunea porţiunii datelor care urmează să fie testate.

Următorul pas presupune încadrarea regresiei liniare în setul training şi apoi prezicerea ieşirii y pe baza
valorii parametrului x, aşa cum am făcut în exemplul cu arborele de decizie folosind biblioteca scikit-
learn. În cele din urmă, folosind biblioteca Matplotlib, vizualizăm datele obţinute, aşa cum am făcut mai
devreme în această lecţie.

Exemplu de regresie liniară folosind biblioteca pentru analiza datelor:

1import numpy as np
2import [Link] as plt
3import pandas as pd
4from sklearn.model_selection import train_test_split
5from sklearn.linear_model import LinearRegression

Page 61 of 126
6
7dataset = pd.read_csv('[Link]')
8x = [Link][:, :-1].values
9y = [Link][:, -1].values
10
11x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2)
12
13model = LinearRegression()
[Link](x_train, y_train)
15y_pred = [Link](x_test)
16
[Link](x_train, y_train, color = 'red')
[Link](x_train, [Link](x_train), color = 'green')
[Link]('Height vs weight')
[Link]('Height')
[Link]('Weight')
[Link]()

Imaginea 4.25. Rezultatul analizei de date folosind modelul regresiei liniare în mediul Jupyter Notebook

Page 62 of 126
2.1. Manipularea şi salvarea conexiunilor

Rezumat
• Limbajul de programare Python acceptă un număr mare de DBMS-uri, cum ar fi MySQL, Oracle
SQL, Sybase, PostgreSQL, NoSQL, SQLite şi multe altele.
• Toate aceste sisteme sunt utilizate pentru gestionarea bazelor de date relaţionale, cu excepţia
NoSQL, care este un model non-relaţional.
• SQLite este singurul care nu foloseşte o arhitectură client-server. Acest modul este încorporat
în program şi nu necesită o conexiune la server.
• Pentru a ne conecta la baza de date, folosim metoda connect() a clasei Connect, care returnează
obiectul de conexiune. Această clasă acceptă următoarele metode de manipulare:

o close() - închide conexiunea;


o commit() – execută/comite tranzacţia;
o rollback() - anulează tranzacţia;
o cursor() - un indicator folosit pentru generarea cozilor de date.

• Metodele de bază acceptate de clasa Cursor sunt:

o close() - închide cursorul;


o execute() - execută interogarea;
o executemany() - execută o interogare care returnează mai multe rezultate;
o fetchone() - returnează următorul rând al tabelului;
o fetchmany() - returnează mai multe rânduri ale tabelului;
o fetchall() - returnează toate rândurile din tabel.

• În modulul sqlite3, este suficient să transmitem sub forma unui parametru numele bazei de date
la care dorim să ne conectăm la metoda de conectare. Dacă baza de date nu există, va fi creată
una nouă cu acel nume.
• În modulul mysql, trebuie să transmitem username şi database ca parametri obligatorii de
conexiune. Dacă ceilalţi parametri nu sunt transmişi, se presupune că parola utilizatorului nu
există şi că conexiunea se va face pe localhost, pe portul 3306, care este portul destinat MySQL.
• Crearea unui nou tabel în toate modulele se face folosind funcţia execute() şi cuvintele-cheie
de interogare CREATE TABLE. Declaraţia însoţitoare IF NOT EXISTS nu este obligatorie, dar este
de dorit. La definirea unui tabel, în sintaxa NAME DATA_TYPE CONSTRAINT, acestuia trebuie să
îi specificaţi atributele.

Limbajul de programare Python are instrumente foarte puternice pentru manipularea bazelor de date.
Acesta are suport pentru un număr mare de DBMS-uri, precum MySQL, Oracle SQL, Sybase, PostgreSQL,
NoSQL, SQLite şi multe altele. Mai jos vom prezenta o scurtă descriere şi istoricul acestor soluţii.
• MySQL este un sistem de gestionare a bazelor de date relaţionale şi este open source. A fost
creat în anul 1995 de David Axmark şi Michael Windenius în cadrul MySQL AB, iar astăzi,
corporaţia Oracle deţine toate drepturile asupra acestuia. Prefixul my din nume este inspirat de
numele fiicei co-fondatorului Michael. Acest limbaj foloseşte o arhitectură client-server şi este
utilizat, în principal, pentru aplicaţii web. Este potrivit pentru stocarea informaţiilor de
autentificare, a datelor de comerţ electronic etc.

• Oracle SQL este primul DBMS conceput pentru a efectua activităţi în reţea, în cadrul unei
companii. Este cel mai eficient şi cel mai ieftin mod de a gestiona informaţiile şi aplicaţiile. A
fost creat în anul 1977 de Larry Ellison, Boba Miner şi Eda Outs, ca parte a Software Development
Laboratories (SDL). Numele de Oracle provine de la numele de cod din proiectul CIA la care a
lucrat Ellison.

Page 63 of 126
• Sybase era o companie de software care gestiona şi analiza datele din bazele de date
relaţionale. Dezvoltarea iniţială a Sybase a fost iniţiată de Robert Epstein, Mark Hoffman, Jane
Doughty şi Tom Haggin, în anul 1984 la Berkeley. Interesant este faptul că negocierile firmei cu
Microsoft privind licenţierea serverelor de date au dus la construirea Microsoft SQL Server 1.0.
Acest DBMS este cel mai frecvent utilizat în industria finanţelor. Din anul 2010, SAP deţine toate
drepturile de utilizare.
• PostgreSQL este sistemul implicit de gestionare a bazelor de date pe serverele macOS, dar
poate fi utilizat şi pe alte sisteme de operare. Este derivat din cuvântul Ingres, care este numele
unei baze de date relaţionale destinate scopurilor comerciale şi guvernamentale. Fondatorul
Michael Stonebraker a decis în anul 1985 să continue proiectul, ceea ce a dus la apariţia
PostgreSQL. Este utilizat pentru multe aplicaţii web, mobile, geospaţiale şi analitice.
• NoSQL, aşa cum ne sugerează prefixul No din numele acestui DBMS, se referă la baze de date
nerelaţionale. Foloseşte un mecanism de stocare şi recuperare a datelor care este modelat
diferit de relaţiile tabelare. A fost dezvoltat în anul 1998 de Carlo Strozzi. Este cel mai frecvent
utilizat pentru a gestiona baze de date mari şi pentru aplicaţii care stochează şi utilizează date
în timp real.
• SQLite este un sistem de gestionare a bazelor de date relaţionale, încorporat în program. Nu
utilizează o arhitectură client-server şi nu necesită instalare prealabilă pentru utilizare. A fost
creat de D. Richard Hipp în anul 2000, cu scopul controlului daunelor în marina americană. Este
potrivit pentru baze de date mici şi testarea aplicaţiilor simple.

Pentru fiecare bază de date Python pe care o selectăm, folosim interfaţa corespunzătoare. Majoritatea
DBMS-urilor pe care Python le acceptă utilizează interfaţa Python DB-API standard şi funcţionează în
conformitate cu standardele definite.

Pentru o explicaţie mai simplă a modulului, vom folosi mai întâi modulul sqlite3 încorporat în limbajul
de programare Python şi apoi vom arăta în continuare cum arată aceleaşi funcţionalităţi în modulul
mysql. Modulul sqlite3 nu necesită instalare prealabilă sau configurare suplimentară şi nici conexiune la
server înainte de utilizare, ci oferă acces direct la baza de date. Un alt avantaj al său este că are un cod
open source, care este interportabil pe diverse platforme şi sisteme de operare. Cuvântul Lite în numele
acestui DBMS în sine ne spune că este o bază de date mică, care nu consumă o mulţime de resurse şi
nu are cerinţe mari de întreţinere.
Deci, tot ceea ce trebuie să facem pentru a avea acces la acest modul încorporat este să îl introducem
în programul nostru. Ca şi până acum, comanda import sqlite3 ne permite să accesăm funcţionalitatea
acestui modul. Pentru lucrări suplimentare cu datele, avem nevoie de o bază de date.

Conexiunea la baza de date în modulul sqlite3

Accesul la baza de date este asigurat printr-o conexiune. Metoda de bază pe care o folosim pentru a
stabili o conexiune este connect(). Această metodă bazată pe parametrul de intrare transmis, care
reprezintă baza noastră de date, returnează obiectul de conexiune, adică clasa Connection. Obiectul
de conexiune creat trebuie salvat într-o variabilă, care în exemplul nostru va fi numită conn. Vom
transmite metoda de conectare la baza de date [Link], care nu a fost creată încă. Când
transmitem o bază de date inexistentă către metoda de conectare, se creează o nouă bază de date sub
acel nume, ceea ce se va întâmpla în cazul nostru.

Exemplu de conexiune a bazei de date folosind modulul sqlite3:

1import sqlite3
2
3conn = [Link]('[Link]')

Page 64 of 126
Obiectul conn creat, acum reprezintă conexiunea noastră cu baza de date şi o putem manipula în
continuare. De asemenea, atunci când baza de date care, transmisă la metoda connection(), este
deja existentă în memorie, nu va fi creată una nouă, dar operaţiunile vor fi efectuate pe baza de date
existentă.

Modulul sqlite3 oferă, de asemenea, posibilitatea de a stoca baza de date pe memoria principală (RAM).
După cum am avut ocazia să vedem în lecţia Stocarea şi transportul de date, această memorie permite
accesul cel mai rapid la date, ceea ce permite testarea neîntreruptă a funcţionalităţilor. Cu toate acestea,
trebuie avut în vedere faptul că această memorie nu este permanentă, ceea ce înseamnă că odată cu
sfârşitul programului nostru, toate datele din baza noastră de date se pierd. Comanda care permite o
conexiune la o bază de date stocată temporar în RAM este conn = [Link](:memory:).

Obiectele de conexiune susţin următoarele metode de manipulare:

• close() - această metodă împiedică utilizarea în continuare a conexiunii. Dacă se încearcă o


operaţiune de bază, va apărea o eroare. Acelaşi lucru este valabil pentru toate obiectele cursor
care încearcă să utilizeze conexiunea. Închiderea conexiunii înainte de efectuarea tuturor
operaţiunilor pentru care este responsabilă funcţia commit(), va face ca baza de date să revină
la starea în care se afla înainte de începerea tranzacţiei apelând rollback();
• commit() - execută toate operaţiunile specificate în cadrul tranzacţiei. Dacă baza de date
acceptă funcţia auto-commit, este de dorit să o setaţi iniţial la off şi să introduceţi o metodă
care va avea un rol de manipulare. Modulele bazei de date care nu acceptă tranzacţii ar trebui
să introducă această metodă fără valoare returnată.
• rollback() - permite bazei de date să revină la începutul oricărei tranzacţii în aşteptare.
Închiderea conexiunii fără a executa mai întâi toate operaţiunile va apela această metodă.
Interesant este că această metodă nu este, de fapt, obligatorie, deoarece nu sunt susţinute toate
modulele de tranzacţie.
• cursor() – asigură generarea unei date sau un set de cozi de date şi execuţia unei logică
complexe peste ele. Dacă folosim un modul care nu susţine cursorul, modulul va trebui să
imite indicatorii folosind alte mijloace în conformitate cu specificaţia conform căreia este scris.

Obiectul cursor

Cursorul reprezintă un obiect al clasei [Link]. Această clasă permite programului nostru să
gestioneze tabele şi date din acestea. Un obiect din această clasă este creat apelând
metoda cursor() a obiectului de conexiune, care trebuie plasată într-o variabilă. În exemplul nostru,
apelarea cursorului din obiectul de conexiune creat va arăta astfel:

cursor = [Link]()

Obiectul cursor acceptă mai multe metode de manipulare, printre care vom evidenţia unele dintre cel
mai frecvent utilizate:

• close() este metoda responsabilă de terminarea manuală a obiectului cursor. Toate


operaţiunile care sunt încercate după apelarea acestei funcţii vor primi un mesaj de eroare.
• execute(a[,b]) primeşte operaţiunea ca parametru a şi datele în forma parametrului b.
Parametrul b este opţional şi poate transmite o secvenţă de date sau să le mapeze. Aceasta este
cea mai eficientă metodă pentru operaţiuni repetitive, deoarece cursorul păstrează referinţa la
operaţiune, în cazul în care acelaşi obiect al operaţiunii este redirecţionat.
• executemany(a, b) primeşte operaţiunea sub forma parametrului a şi secvenţa de date
obligatorie sub forma parametrului b. Această metodă execută o interogare din

Page 65 of 126
parametrul a peste un set de n-tupluri din parametrul b şi dă unul sau mai multe rezultate.
Valoarea returnată a metodei nu este definită.
• fetchone() returnează următorul rând al setului de rezultate ale interogării sau null dacă
următorul rând nu există. Metoda întâmpină o eroare atunci când metoda execute() care o
precedă nu este definită sau nu returnează rezultate pe baza interogării transmise.
• fetchmany(x) returnează următorul set de rânduri de rezultate ale interogării şi afişează o
listă de n-tupluri. Numărul de rânduri de descărcare este specificat de parametrul x. În caz
contrar, matricea indicatorului în sine determină numărul de rânduri care trebuie returnate.
Dacă parametrul x este mai mare decât numărul de rânduri din tabel, cele care există vor fi
returnate. Metoda raportează o eroare în aceleaşi cazuri ca fetchone().
• fetchall() returnează toate rândurile care corespund condiţiei date din interogare. Metoda
raportează o eroare în aceleaşi cazuri ca şi cele două metode anterioare.

Crearea bazei de date în modulul sqlite3

Pentru a crea un tabel de date, folosim metoda execute(), pe care o apelăm folosind
obiectul cursor definit anterior. Ca parametru al acestei metode, vom transmite o interogare SQL care
ne va asigura că un tabel cu atribute şi constrângeri pe care le definim este creat în baza de date. Mai
jos vom prezenta crearea unui tabel pe exemplul listei de stocuri de produse dintr-un magazin de jucării.

Exemplu de creare a unui tabel folosind metoda execute() în modulul sqlite3:

[Link]("""CREATE TABLE IF NOT EXISTS Toy_Store_Products(


2toy_id INT PRIMARY KEY,
3product_name TEXT,
4price FLOAT,
5quantity INT);
6""")
7
[Link]()

Pentru o înţelegere mai uşoară, vom împărţi interogarea transmisă în câteva părţi:

• CREATE TABLE - comandă obligatorie care indică crearea tabelului;


• IF NOT EXISTS - o condiţie care interzice recrearea unui tabel dacă acesta există deja; această
condiţie este opţională, totuşi, omiterea acesteia va cauza o eroare: ’[Link]:
table Toy_Store_Products already exists’;
• Toy_Store_Products - numele tabelului pe care dorim să îl creăm.

În continuarea interogării, sunt transmise atributele, adică coloanele pe care dorim să le aibă tabelul.
Fiecare dintre atribute este definit în formatul NAME DATA_TYPE CONSTRAINT, conform convenţiei
de scriere a interogării. Toate tipurile de date acceptate de SQLite se pot vedea la acest link. În ceea ce
priveşte constrângerile din tabelul nostru, am plasat constrângerea PRIMARY_KEY pe atributul codului
produsului, care asigură faptul că toate valorile acelui atribut sunt unice.

După definirea tabelului de date, vom efectua o tranzacţie pe baza de date, executând
comanda [Link](). După cum am menţionat mai devreme, această comandă face modificări
permanente în baza de date şi nu permite execuţia parţială a operaţiunilor pe baza de date.

Page 66 of 126
Conexiunea la baza de date în modulul mysql

În exemplul anterior, am văzut cum o bază de date dintr-un modul sqlite3 poate fi conectată şi creată
utilizând un cursor. Conectarea la bazele de date MySQL are o rutină puţin mai diferită. Şi anume, pentru
a stabili o conexiune cu baza de date din acest modul, trebuie să ne conectăm la serverul MySQL.

Înainte de a începe să lucrăm în acest modul, trebuie să efectuăm instalarea acestuia. Facem acest lucru
folosind comanda pip install mysql-connector-python. Vom importa apoi modulul instalat
în program cu comanda import [Link], astfel încât să-l putem folosi. Cu un mediu
pregătit astfel, putem începe conexiunea la server.

Obţinem obiectul de conexiune apelând metoda connect() din modulul nostru [Link].
Deoarece ne conectăm la server, trebuie să transmitem parametrii către funcţia care va direcţiona
conexiunea acolo unde este nevoie. Cei doi parametri de conexiune necesari
sunt username şi database. În aceşti parametri transmitem numele de utilizator cu care dorim să
accesăm baza de date şi numele bazei de date pe care o accesăm. În cazul nostru, ca utilizator, vom
transmite root, care reprezintă superutilizatorul sau utilizatorul care are toate privilegiile din baza de
date. Vom lua baza de date [Link] ca nume al bazei de date, deoarece vom prezenta lucrările din
acest modul pe acelaşi exemplu pe care l-am folosit în modulul sqlite3.

Funcţia connect() acceptă un număr mare de parametri opţionali. Toţi parametrii care pot fi transmişi
către funcţie pot fi găsiţi la acest link. În lista de parametri, observăm că pentru conexiune trebuie să
introducem parola, precum şi serverul şi portul pentru conexiune. Cu toate acestea, vom observa că
aceşti parametri au valori implicite care vor fi atribuite automat dacă nu le definim, ceea ce se întâmplă
în cazul nostru.

Cu alte cuvinte, funcţia connect() va seta un string gol pentru parolă (password); pentru serverul la
care ne conectăm, adică pentru host, acesta va atribui adresa implicită [Link], care reprezintă serverul
nostru local, iar valoarea portului va fi setată automat la 3306, care este, de fapt, portul implicit pentru
MySQL. Vom salva apoi conexiunea la variabila conn, aşa cum se poate vedea în exemplul de mai jos.

Exemplu de creare a conexiunii bazei de date folosind modulul mysql:

import [Link]

conn = [Link](user='root', database='example')

Rezultatul unor astfel de comenzi va fi o conexiune la baza de date example pe serverul nostru
local localhost. Baza de date pe care am accesat-o este în prezent goală.

Pentru a introduce date în baza de date, avem nevoie de un tabel pe care îl vom crea printr-o interogare,
în acelaşi mod ca în exemplul anterior cu modulul sqlite3. Să nu uităm că mai întâi trebuie să creăm un
cursor care să ne permită să apelăm metoda execute(), de care avem nevoie pentru execuţia
interogării. În cele din urmă, comanda commit() va face modificări permanente în baza de date.

Exemplul de creare a tabelului folosind metoda execute() a modulului mysql:

1cursor = [Link]()
2
[Link]("""CREATE TABLE IF NOT EXISTS Toy_Store_Products(

Page 67 of 126
4toy_id INT PRIMARY KEY,
5product_name TEXT,
6price FLOAT,
7quantity INT);
8""")
9
[Link]()

Vom observa că procedura de creare a tabelelor în bazele de date relaţionale este aceeaşi, indiferent
de modulul pe care îl folosim. Interogarea transmisă metodei execute() returnează acelaşi rezultat
ca cel transmis la baza de date din modulul sqlite3. Ca urmare a execuţiei tranzacţiei, vom obţine
tabelul creat pe serverul nostru local, care se poate vedea în imaginea 5.1.

Imaginea 5.1. Prezentarea bazei de date create în modulul mysql din mediul phpMyAdmin

Page 68 of 126
2.2. Manipularea datelor în baza de date relaţională

Rezumat
• DML (data manipulation language) se utilizează pentru manipularea datelor din bazele de date.
• Cele patru operaţiuni de bază pentru manipulare sunt: inserarea, afişarea, actualizarea şi
ştergerea, pe care le numim operaţiuni CRUD.
• Operaţiunea de inserare foloseşte cuvintele-cheie INSERT INTO, care urmează numele tabelului
în care dorim să inserăm datele.
• Cea de-a doua parte a interogării pentru inserarea datelor fără parametri defineşte porţiunea
rezervată pentru transmiterea n-tuplului de date, care în modulul sqlite3 este denumită
VALUES(?, ?, ?, ?), iar în modulul mysql VALUES( % s, % s, % s, % s). Un alt mod în modulul mysql
este de a seta parametrul cursorului prepared la True.
• Inserarea parametrizată a datelor este utilizată pentru a insera datele definite în dicţionar.
Porţiunea rezervată pentru transmiterea n-tuplului de date, în acest caz, stabileşte cheile de
date VALUES(:key, :key, :key, :key) pentru placeholdere.
• Operaţiunea de afişare utilizează cuvântul-cheie SELECT urmat de numele atributului, iar apoi
cuvântul-cheie FROM indică tabelul din care vor fi citite datele.
• Operaţiunea de actualizare utilizează cuvântul-cheie UPDATE, urmat de numele tabelului, apoi
cuvântul-cheie SET determină noua valoare a atributului şi, în cele din urmă, condiţia WHERE,
determină circumstanţele în care se vor aplica anumite modificări.
• Operaţiunea de ştergere utilizează cuvintele-cheie DELETE FROM, care sunt urmate de numele
tabelului, iar apoi folosim condiţia WHERE pentru a defini circumstanţele în care va fi efectuată
ştergerea.

Cele patru operaţiuni de bază pentru manipularea datelor în bazele de date relaţionale sunt inserarea,
afişarea, actualizarea şi ştergerea. În programare, aceste operaţiuni se numesc operaţiuni CRUD.
Acronimul CRUD (Create, Read, Update, Delete) se referă la principalele funcţii care sunt implementate
în aplicaţiile de baze de date relaţionale. Aceste operaţiuni aparţin limbajului DML (limbaj de
manipulare a datelor/data manipulation language) pentru comunicarea cu bazele de date, care face
parte din limbajul de interogare SQL integrat. În continuare, vom arăta comenzile utilizate în SQL pentru
execuţia acestor operaţiuni (tabelul 6.1.).

Comenzi pentru operaţiunile CRUD în limbajul de interogare SQL


Operaţiune Comandă SQL Funcţie
Create INSERT inserarea datelor
Read (Retrieve) SELECT afişarea/selectarea datelor
Update (Modify) UPDATE actualizarea datelor
Delete (Destroy) DELETE ştergerea datelor

Tabelul 6.1. Comenzi pentru operaţiunile CRUD în limbajul de interogare SQL

Pentru prezentarea utilizării acestor operaţiuni ale bazei de date, vom lua ca exemplu baza de date pe
care am creat-o în lecţia anterioară folosind modulul sqlite3. În continuare, putem vedea codul pe care
l-am folosit pentru crearea bazei de date.

Exemplu de creare a unei baze de date folosind metoda execute() a modulului sqlite3:

1import sqlite3
2conn = [Link]('[Link]')
3cursor = [Link]()
4

Page 69 of 126
[Link]("""CREATE TABLE IF NOT EXISTS Toy_Store_Products(
6toy_id INT PRIMARY KEY,
7product_name TEXT,
8price FLOAT,
9quantity INT);
10""")
11
[Link]()

Inserarea datelor în baza de date (INSERT)

Pentru inserarea datelor în baza de date, folosim, de asemenea, obiectul cursor şi metoda
sa execute(). Interogarea, adică comanda pe care o transmitem ca parametru pentru inserarea
datelor, trebuie să fie o listă de n-tupluri, un string sau dicţionar, în funcţie de faptul dacă adăugarea
datelor este sau nu parametrizată. Mai jos vom arăta adăugarea neparametrizată a produselor în tabelul
listei de stocuri din magazinul de jucării, folosind interogarea SQL.

Exemplu de inserare neparametrizată a unui nou rând într-o bază de date utilizând comanda INSERT
şi metoda execute() din modulul sqlite3:

1toy = ('100', 'Teddy Bear', 25.0, 40)


[Link]("INSERT INTO Toy_Store_Products VALUES (?, ?, ?, ?)", toy)
3
[Link]()

Vom împărţi din nou interogarea în mai multe secţiuni pentru o mai bună vizibilitate:

• INSERT INTO - comandă obligatorie care indică inserarea în tabel;


• Toy_Store_Products - numele tabelului în care efectuăm inserarea;
• VALUES (?, ?, ?, ?) - porţiune rezervată pentru transmiterea datelor din n-tuplu; fiecare
semn al întrebării (?) reprezintă un spaţiu rezervat (placeholder) pentru fiecare atribut din n-
tuplu care trebuie transmis; dacă numărul de atribute nu se potriveşte cu numărul de
placeholdere, va apărea o eroare.

Adăugarea parametrizată a datelor este utilizată atunci când se lucrează cu datele stocate în dicţionar.
În continuare, vom arăta cum ar arăta parametrizarea adăugării de date pe un exemplu identic de
inserare a unui produs în stocul unui magazin de jucării.

Exemplul de adăugare parametrizată a unui nou rând la o bază de date folosind comanda INSERT şi
metoda execute() a modulului sqlite3:

1toy = {"toy_id":'100', 'product_name':'Teddy Bear',


2"price":'25.0', "quantity":'40'}
3
[Link]("INSERT INTO Toy_Store_Products VALUES (:toy_id,
5:product_name, :price, :quantity)", toy)
6
[Link]()
Spre deosebire de metoda anterioară de adăugare, în parametrizare, atributele sunt stocate în dicţionar
în perechi de cheie-valoare. Pentru a transmite o valoare de atribut, trebuie să specificăm cheia sa cu
două puncte (:) ca prefix.

Page 70 of 126
Pentru interogările SQL din modulul sqlite3, este acceptată o metodă care permite execuţia mai multor
comenzi. Folosind executemany(), vom arăta cum adăugăm două produse noi în lista noastră de
produse din magazinul de jucării. Ceea ce este important de reţinut este că în această metodă, datele
care trebuie transmise trebuie să fie o listă de n-tupluri sau o listă de dicţionare.

Exemplu de adăugare neparametrizată a două rânduri la baza de date folosind comanda INSERT şi
metoda executemany() a modulului sqlite3:

1toy = [('101', 'Bicycle', 100.0, 15), ('102', 'Doll', 20.0, 35)]


[Link]("INSERT INTO Toy_Store_Products VALUES (?, ?, ?, ?)",
3toy)
4
[Link]()

Selectarea datelor din baza de date (SELECT)

În metoda execute() a modulului sqlite3, putem transmite şi o interogare care va citi datele din baza
noastră de date în funcţie de condiţia dată. Cu toate acestea, pentru ca metoda noastră să ia toate
rândurile pe care le cuprinde condiţia de afişare, trebuie să apelăm metoda fetchall() a obiectului
cursor. Să vedem în continuare ce se află în lista de stocuri a magazinului nostru de jucării.

Exemplu de vizualizare a tuturor rândurilor unui tabel folosind comanda SELECT şi metodele
executemany() şi fetchall() ale modulului sqlite3:

[Link]("SELECT * FROM Toy_Store_Products;")


2product_list = [Link]()
3
4print(product_list)

Despărţirea interogării în mai multe părţi ar arăta astfel:

• SELECT - comandă obligatorie pentru afişarea datelor;


• - dată sau set de date pe care dorim să le afişăm. Caracterul asterisc (*) este un marcaj care
înlocuieşte toate rândurile din tabel;

• FROM - numele tabelului din care dorim să afişăm date.

Page 71 of 126
Imaginea 6.1. Afişarea tabelului creat cu datele bazei de date [Link] în mediul Jupyter Notebook

Actualizarea bazei de date (UPDATE)

Dacă analizăm magazinul nostru de jucării din lumea reală, vom concluziona că lista sa de stocuri trebuie
să poată fi modificată în ceea ce priveşte actualizarea datelor. Limbajele SQL acceptă, de asemenea,
această funcţionalitate. Să presupunem că vrem să vindem cât mai multe biciclete posibil înainte de
sosirea iernii. În consecinţă, le vom reduce preţul la 80 dolari. Iată cum arată acesta în modulul sqlite3.

Exemplu de actualizare a unei baze de date utilizând comanda UPDATE şi metoda execute() din
modulul sqlite3:

[Link]("""UPDATE Toy_Store_Products SET price = 80.0


2WHERE product_name = 'Bicycle'""")
3
[Link]()

Să vedem cum arată interogarea noastră în ansamblu:

• UPDATE - comandă obligatorie care indică actualizarea tabelului;


• SET – cuvânt-cheie pentru setarea unei noi valori;
• WHERE – cuvânt-cheie pentru setarea condiţiilor în care va fi setată noua valoare.

Page 72 of 126
Ştergerea datelor din baza de date (DELETE)

O altă funcţie pe care o acceptă limbajele SQL este funcţia de ştergere. Dacă am dori să ştergem păpuşile
din gama de produse, am face aceasta în felul următor:
Exemplu de ştergere a datelor din baza de date folosind comanda DELETE şi metoda execute() a
modulului sqlite3:

[Link]("""DELETE FROM Toy_Store_Products


2WHERE product_name = 'Doll'""")
3
[Link]()
Pentru o mai bună claritate, şi de această dată vom despărţi interogarea în mai multe părţi:

• DELETE FROM - comandă obligatorie care indică ştergerea datelor;


• WHERE – cuvânt-cheie pentru setarea condiţiilor în care va fi ştearsă valoarea.

Să vedem cum arată baza noastră acum, după aceste modificări. Revizuim folosind comanda SELECT
*, aşa cum am explicat mai devreme în lecţie.

Imaginea 6.2. Prezentarea actualizării tabelului cu date în baza de date [Link] în mediul Jupyter
Notebook

Page 73 of 126
Operaţiunile CRUD în modulul mysql

Operaţiunile CRUD din modulul mysql sunt aplicate în acelaşi mod ca şi în alte module de baze de date
relaţionale. Vom arăta doar câteva dintre aceste operaţiuni pe un exemplu bine cunoscut de magazin
de jucării. Să ne reamintim cum ne-am conectat la baza de date şi cum am creat tabelul.

Exemplu de creare a unui tabel folosind metoda execute() a modulului mysql:

1import [Link]
2
3conn = [Link](user='root', database='example')
4
5cursor = [Link]()
[Link]("""CREATE TABLE IF NOT EXISTS Toy_Store_Products(
7toy_id INT PRIMARY KEY,
8product_name TEXT,
9price FLOAT,
10quantity INT);
11""")
12
[Link]()
Tabelul pe care l-am creat este gol în prezent. Vom adăuga simultan toate produsele care există în
exemplul nostru din modulul sqlite3. Folosind o procedură identică, transmitem interogarea la
metoda execute() folosind cuvinte-cheie pentru operaţiunea de inserare INSERT INTO. Cu toate
acestea, există o mică diferenţă în acest modul, care se referă la rezervarea unei porţiuni pentru
transmiterea datelor din n-tuplu. Acest modul nu acceptă semnul ? ca un caracter care reprezintă un
placeholder, în schimb, semnul %s este folosit ca un placehoder pentru datele rezervate, care sunt
întotdeauna transmise sub formă de stringuri într-o interogare MySQL. Următorul este codul care va
executa comanda de inserare.
Exemplu de adăugare neparametrizată a mai multor rânduri la o bază de date folosind comanda
INSERT şi metoda executemany() a modulului mysql:

1toy = [('100', 'Teddy Bear', 25.0, 40),


2 ('101', 'Bicycle', 100.0, 15),
3 ('102', 'Doll', 20.0, 35)]
4
[Link]("INSERT INTO Toy_Store_Products VALUES (?, ?, ?, ?)",
6toy)
7
[Link]()

Notă

Există o altă modalitate de a insera date în baza de date fără a specifica tipurile de date rezervate în porţiunea
de transmitere a n-tuplului. Acest lucru poate fi realizat prin setarea parametrului cursorului la valoarea True,
ceea ce ar arăta astfel:

cursor = [Link](prepared=True)

Acest parametru permite ca aceeaşi interogare să fie executată eficient de mai multe ori prin pregătirea unui
formular de interogare care repetă fiecare interogare.

Page 74 of 126
Executând codul ataşat la ultimul exemplu, am introdus trei elemente în tabel. Pentru a vizualiza aceste
elemente, putem să utilizăm operaţiunea CRUD cu numele SELECT, pentru a afişa datele sau, pur şi
simplu, pentru a accesa fizic clientul MySQL. Mai jos vă arătăm o prezentare generală a acestor date
folosind ambele abordări.

Imaginea 6.3. Prezentarea tabelului toy_store_products actualizat

Exemplu de vizualizare a tuturor rândurilor unui tabel folosind comanda SELECT şi metodele
executemany() şi fetchall() ale modulului mysql:

[Link]("SELECT * FROM Toy_Store_Products;")


2product_list = [Link]()
3
4print(product_list)

Imaginea 6.4. Rezultatul afişării tuturor datelor din tabelul [Link] în mediul Jupyter Notebook

Page 75 of 126
Imaginea 6.5. Rezultatul afişării tuturor datelor din tabelul [Link] în mediul phpMyAdmin

Exerciţii

Exerciţiul 1

Trebuie creată o bază de date mdb în care trebuie executat următorul cod SQL:

DROP TABLE IF EXISTS `movie`;


CREATE TABLE IF NOT EXISTS `movie` (
`movie_id` int(10) NOT NULL AUTO_INCREMENT,
1 `title` varchar(1000) DEFAULT NULL,
2 `budget` int(10) DEFAULT NULL,
3 `homepage` varchar(1000) DEFAULT NULL,
4 `popularity` decimal(12,6) DEFAULT NULL,
5 `release_date` date DEFAULT NULL,
6 `revenue` bigint(20) DEFAULT NULL,
7 `runtime` int(5) DEFAULT NULL,
8 PRIMARY KEY (`movie_id`)
9) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
10
11INSERT INTO `movie` (`movie_id`, `title`, `budget`, `homepage`, `popularity`,
12`release_date`, `revenue`, `runtime`) VALUES
13(1, 'Minions', 74000000, '[Link] '875.581305', '2015-06-
1417', 1156730962, 91),
15(2, 'Interstellar', 165000000, '[Link]
16'724.247784', '2014-11-05', 675120017, 169),
17(3, 'Deadpool', 58000000, '[Link]
18'514.569956', '2016-02-09', 783112979, 108),
19(4, 'Guardians of the Galaxy', 170000000, '[Link]
20'481.098624', '2014-07-30', 773328629, 121),
21(5, 'Mad Max: Fury Road', 150000000, '[Link]
22'434.278564', '2015-05-13', 378858340, 120),
23(6, 'Jurassic World', 150000000, '[Link] '418.708552',
24'2015-06-09', 1513528810, 124),

Page 76 of 126
(7, 'Pirates of the Caribbean: The Curse of the Black Pearl', 140000000,
'[Link]
'271.972889', '2003-07-09', 655011224, 143),
(8, 'Dawn of the Planet of the Apes', 170000000, '[Link]
'243.791743', '2014-06-26', 710644566, 130),
(9, 'The Hunger Games: Mockingjay - Part 1', 125000000,
'[Link] '206.227151', '2014-11-18', 752100229, 123),
(10, 'Big Hero 6', 165000000, '[Link]
'203.734590', '2014-10-24', 652105443, 102);<strong><br></strong>

Când tabelul movie este realizat şi umplut cu filme, trebuie să ne conectăm la baza de date şi să listăm
toate filmele într-un format convenabil în linia de comandă.

Soluţie:

import [Link]
1conn = [Link](user='root', database='mdb')
2cursor = [Link]()
3
[Link]("SELECT * FROM movie;")
5movie_list = [Link]()
6
7for m in movie_list:
8 print("Movie: {} | Budget: {}$ | Release date: {} | Revenue: {}$ |
9Runtime(min): ".format(m[1], m[2], m[5], m[6], m[7]))
Explicaţie:
Primul pas este să pornim modulul [Link] necesar, să creăm o conexiune şi cursorul obiectului.
După aceea, trebuie să efectuăm o interogare select în care vom transmite numele tabelului. La final,
parcurgem bucla prin toate rezultatele listei în mod individual şi le listăm într-un format adecvat,
folosind funcţia format.

Exerciţiul 2
Utilizatorul trebuie să aibă posibilitatea să introducă separat CNP-ul, prenumele, numele de familie şi
adresa, folosind linia de comandă. Datele introduse trebuie stocate în baza de date SQLite. După stocare,
utilizatorul trebuie să listeze toate datele din tabel.
Soluţie:

1import sqlite3
2conn = [Link]('[Link]')
3cursor = [Link]()
4
[Link]("""CREATE TABLE IF NOT EXISTS user(
6 user_id INT PRIMARY KEY,
7 name TEXT,
8 last_name TEXT,
9 address TEXT);
10 """)
[Link]()
12
13user_id = input("Enter your ID:")
14name = input("Enter your name:")
15last_name = input("Enter your last name:")

Page 77 of 126
16address = input("Enter your address:")
17
18user = (user_id, name, last_name, address)
[Link]("INSERT INTO user VALUES (?, ?, ?, ?)", user)
20
[Link]()
22
[Link]("SELECT * FROM user;")
24product_list = [Link]()
25for p in product_list:
26 print("ID: {} | Name: {} | Last name: {} | Address: {}".format(p[0], p[1],
p[2], p[3]))
Explicaţie:

Primul pas presupune includerea modulului sqlite3, necesar şi la crearea conexiunii şi a obiectului cursor.
După aceea, trebuie creat un tabel folosind metoda execute a cursorului obiect. Apoi, folosind funcţia
input(), putem cere utilizatorului să introducă date şi să le salveze. Când toate datele sunt completate,
creăm un n-tuplu al utilizatorului pe care îl vom folosi la crearea interogării insert. La final, furnizăm
toate datele din baza de date cu o interogare SELECT şi le afişăm utilizatorului.

Page 78 of 126
2.3. Lucrul cu bazele de date NoSQL

Rezumat
• NoSQL este un sistem atipic de gestionare a bazelor de date care oferă un mecanism de stocare
şi descărcare pe modele care nu sunt bazate pe tabel.
• Avantajele NoSQL în raport cu modelul relaţional sunt următoarele: gestionează o cantitate
mare de date, nu necesită o structură de bază de date predefinită, are scalabilitate orizontală.
• Modelele susţinute de NoSQL sunt: modelul cheie-valoare, modelul orientat pe coloane,
modelul grafic şi modelul orientat pe documente.
• Modelul cheie-valoare foloseşte cheile ca identificatori unici de date (OracleBerkleyDB, Riak,
Redis) pentru a manipula valorile.
• Modelul orientat pe coloane implică faptul că datele null nu sunt stocate în acest model, din
cauza ocupării mai mici de memorie. Fiecare element care stochează valori în perechi cheie-
valoare are şi propria sa cheie primară (cheie de coadă) pentru identificarea unică (Cassandra).
• Modelul de grafic utilizează o structură grafică cu noduri care au atribute şi ramuri direcţionate
care arată activităţi între aceste noduri. Nu necesită indexuri pentru structurare, dar le foloseşte
pentru căutare (Neo4J).
• Modelul orientat pe document tratează documentul ca un întreg şi evită separarea acestuia în
perechi de cheie-valoare. Foloseşte indexarea atât pentru documente, cât şi pentru atribute
(MongoDB, CouchDB).
• MongoDB este cea mai comună bază de date NoSQL de tip open source, care foloseşte un
model orientat pe documente şi stochează date în format JSON în două moduri (încorporat şi
normalizat).
• Pentru utilizarea bazei de date MongoDB în Python, se foloseşte biblioteca pymongo.
• Pentru manipularea datelor, MongoDB foloseşte operaţiuni CRUD, cum ar fi:

o insert() - transmite date pentru inserarea în format JSON;


o update() - actualizează datele pe baza condiţiei setate şi face modificări în baza de
date folosind operatorul $set;
o find() - afişează date pe baza unei condiţii date;
o delete() - şterge datele care îndeplinesc condiţia setată.

NoSQL este un sistem atipic de gestionare a bazelor de date care oferă un mecanism de stocare şi
descărcare. În timp ce bazele de date SQL relaţionale urmează conceptul de relaţii între tabele şi
concatenarea acestora, bazele de date NoSQL sunt modelate diferit. Chiar numele acestui sistem de
gestionare din prefixul său conţine cuvântul No ca o indicaţie că în acesta nu sunt utilizate relaţiile.

Acest DBMS cuprinde o gamă largă de tehnologii diferite de baze de date dezvoltate ca soluţii la
cantitatea mare de date care trebuie stocate în aplicaţii, cum ar fi datele şi obiectele de utilizator, precum
şi frecvenţa accesului la aceste date, viteza de acces, scalarea şi prelucrarea. Structura acestor sisteme
urmează mai multe concepte de modelare, cum ar fi perechile de cheie-valoare, graficele, date orientate
pe documente şi datele orientate pe coloane.

De-a lungul multor ani de dezvoltare software, s-au folosit baze de date SQL (Structured Query
Language) bazate pe modelul tabelelor concatenate. În ultimii ani, odată cu creşterea utilizării aplicaţiilor
internet şi web 2.0, bazele de date au început să depăşească dimensiunea câtorva mii de terabyţi.
Apariţia aplicaţiilor precum Google, Amazon, Facebook, Instagram şi altele au creat o nouă eră de
gestionare a bazelor de date care urmează conceptul de proiectare simplă şi acces şi gestionare rapidă
a datelor de mare viteză. Astfel de baze de date sunt potrivite pentru aplicaţii care stochează o cantitate
mare de date, oferă interacţiuni în timp real şi necesită capacitatea de a analiza datele de care dispun.

Page 79 of 126
Notă

Web 2.0., cunoscut sub numele de web participativ sau social, se referă la site-urile care evidenţiază conţinutul
generat de utilizator. Acestea se caracterizează prin uşurinţa utilizării, cultură participativă
şi interoperabilitate pentru utilizatorii finali. Un exemplu clasic de aplicaţii web 2.0. sunt reţelele sociale.
Vom lua ca exemplu aplicaţia Instagram, care stochează date despre utilizatorii săi într-o bază de date.
Dacă ar fi să încercăm să introducem unele funcţionalităţi, cum ar fi comentarea fotografiilor într-o bază
de date simplă a aplicaţiei, utilizarea unui sistem tipic de gestionare a bazelor de date relaţionale ar
necesita o revizuire completă a proiectului bazei noastre de date existente. Pe de altă parte, NoSQL
permite înlocuirea uşoară a acestei structuri, deoarece definirea structurii acestor DBMS-uri nu necesită
coloane predefinite (atribute), ci datele pot fi scrise direct în baza de date.

Bazele de date NoSQL au fost dezvoltate ca soluţie la următoarele probleme de baze de date relaţionale:

• date structurate şi uniforme;


• schema de date predefinită sub formă de coloane şi tipuri de date stocate în ele, ceea ce
contrazice metodologia agilă a dezvoltării rapide;
• creşterea verticală a înregistrărilor în tabele cu fiecare adăugare de date, care necesită spaţiu
suplimentar pe server.

Pentru o înţelegere mai uşoară, mai jos vom arăta diferenţele de bază dintre aceste două tipuri de
sisteme de gestionare a bazelor de date (tabelul 7.1.).

Caracteristicile sistemului SQL şi NoSQL pentru gestionarea bazelor de date


[Link] Caracteristici SQL NoSQL
1. model baze de date relaţionale baze de date nerelaţionale
utilizează operaţiuni CRUD pentru schemă dinamică pentru date
stocarea datelor într-o formă nestructurate stocate sub formă de
2. limbaj
structurată pentru a efectua documente, grafice sau valori cheie-
operaţiuni complexe valoare sau pe coloane
scalabilitate verticală - extinderea
scalabilitate orizontală - extinderea
memoriei pentru stocarea datelor
3. scalabilitate memoriei pentru stocarea datelor prin
prin adăugarea de noi componente
instalarea de noi servere
de memorie
se bazează pe teorema CAP a lui
se bazează pe proprietăţi ACID:
Brewer, care include proprietăţile de:
4. implementare atomicitate, consecvenţă, izolare şi
consecvenţă, disponibilitate, toleranţă
durabilitate
la erori
potrivite pentru execuţia potrivite pentru modelarea unor baze
5. utilizare
interogărilor complexe de date imense
de tip open source: Postgres,
MySQL exclusiv de tip open source:
MongoDB, BigTable, Redis,
6. programe
RavenDB, Cassandra, Hbase, Neo4j,
comerciale: CouchDB
Oracle, SQLite

Tabelul 7.1. Compararea caracteristicilor sistemului SQL şi NoSQL pentru gestionarea bazelor de date

Page 80 of 126
Modelele bazelor de date NoSQL

Bazele de date NoSQL sunt clasificate în principal în patru tipuri: model cheie-valoare, model orientat
pe coloane, model grafic şi model orientat pe documente. Fiecare categorie are propriile sale atribute
şi limitări unice. Niciuna dintre aceste baze de date nu este mai bună decât cealaltă pentru rezolvarea
tuturor problemelor.

• Modelul cheie-valoare: cheile unei perechi definite în acest mod sunt unice, ceea ce permite
accesul uşor la date. Perechile cheie-valoare pot fi de diferite tipuri: unele stochează date în
memoria principală, iar altele oferă posibilitatea de a păstra date pe hard disk. Un instrument
simplu, dar puternic, care foloseşte modelul de perechi cheie-valoare este OracleBerkeleyDB.
Încă unele folosite des sunt Riak şi Redis.

Imaginea 7.1. Exemplu de model cheie–valoare al bazei de date NoSQL

• Modelul orientat pe coloane: evită ocuparea inutilă a spaţiului în baza de date. Dacă o dată
are o valoare null, ea nu va fi stocată în acest model. Fiecare unitate de date poate fi considerată
drept un set de perechi cheie-valoare, unitatea însăşi fiind identificată printr-un identificator
special, deseori numit cheie primară. Această cheie primară este deseori numită cheie de coadă
(queue key). Un exemplu tipic este Cassandra.

Imaginea 7.2. Exemplu de model orientat pe coloane al bazei de date NoSQL

• Modelul de grafic: o bază de date utilizează o structură grafică cu noduri, ramuri direcţionate
care reprezintă activităţi între aceste noduri şi atribute pentru a descrie şi stoca date. Prin
definiţie, o bază de date a unui grafic este orice sistem de stocare care permite conectarea

Page 81 of 126
nodurilor fără index. Aceasta înseamnă că fiecare element conţine un indicator direct către unul
adiacent. Bazele de date generale ale graficelor, în care poate fi stocat orice grafic, diferă de
bazele de date specializate de acest tip, cum ar fi bazele de date pentru stocarea triplă a valorilor
(subiect, predicat, obiect) şi bazele de date de reţea. Indexurile sunt utilizate doar pentru
căutarea graficelor. Cel mai cunoscut model de grafic este Neo4J.

Imaginea 7.3. Modelui de grafic al bazei de date NoSQL, prezentat pe exemplul reţelei sociale Instagram

• Modelul orientat pe documente: tratează documentul în ansamblu şi evită separarea acestuia


în perechi cheie-valoare. La nivelul colecţiei, acest lucru vă permite să concatenaţi un set divers
de documente într-un singur grup. Bazele de date ale documentelor permit indexarea
documentelor nu numai pe baza identificatorului său principal, ci şi pe baza atributelor sale.
Diverse baze de date open source sunt disponibile astăzi, cele mai cunoscute fiind MongoDB şi
CouchDB. De fapt, MongoDB a devenit una dintre cele mai populare baze de date NoSQL
utilizate vreodată.

Imaginea 7.4. Modelul de date orientat pe documente al bazei de date NoSQL

Page 82 of 126
MongoDB

Pe măsură ce mai multe date devin disponibile într-un format nestructurat şi parţial construit, creşte
nevoia de a le gestiona folosind o bază de date NoSQL. Într-un mod similar în care comunică cu baze
de date SQL relaţionale, Python poate comunica şi cu baze de date NoSQL nerelaţionale.

MongoDB este cea mai comună bază de date de tip open source NoSQL. Această bază de date
utilizează un model orientat pe documente. Documentele gestionate de MongoDB sunt stocate în
format JSON. Există două modele de date pe care le foloseşte MongoDB: încorporat şi normalizat.
Înainte de a pregăti documentul, trebuie să alegem unul dintre aceste două modele pe baza cerinţelor
bazei de date pe care dorim să le îndeplinim.

• Modelul de date încorporat (denormalizat) presupune că datele sunt obţinute în documente


separate şi tinde să le „încorporeze” pe toate într-un singur document.
• Modelul normalizat stochează datele în documente separate, referindu-le la original.

Pentru o explicaţie mai uşoară, mai jos vom oferi o prezentare generală a modelului încorporat şi
neîncorporat pe exemplul stocării datelor despre un student.

Exemplu de model de date încorporat în MongoDB:

1 {
2 _id: ,
3 student_id: "S0000001"
4 Personal_details:{
5 first_name: "Lucia",
6 last_name: "Giovanni",
7 birth_date: "1997-10-21"
8 },
9 Contact_details: {
10 e-mail: "[Link]@[Link]",
11 phone_number: "00124357772368"
12 }
13}

Exemplu de model de date normalizat în MongoDB:

1 #Obiectele prezentate se stochează în documente separate


2
3 #Student
4{
5 _id: <ObjectId101>,
6 student_id: "S0000001"
7}
8
9#Personal_details
10{
11 _id: <ObjectId102>,
12 studentDocID: "ObjectId101",
13 first_name: "Lucia",
14 last_name: "Giovanni",

Page 83 of 126
15 birth_date: "1997-10-21"
16}
17
18#Contact_details
19{
20 _id: <ObjectId103>,
21 studentDocID: " ObjectId101",
22 e-mail: "[Link]@[Link]",
23 phone_number: "00124357772368"
24}

Exemplul dat ne arată datele despre student. Atributele pe care le are studentul sunt student_id,
Personal_details şi Contact_details, care sunt prezentate în documentul JSON ca obiecte.
Atributul _id reprezintă cheia primară a documentului. În MongoDB, atributul _id oferă un număr unic
de identificare a colectării datelor în document. Dacă un document omite acest atribut, MongoDB se
asigură că obiectul său este creat automat. Obiectele create în acest mod au chei unice care pot fi
utilizate pentru referenţierea din diferite documente, aşa cum se poate vedea în exemplul modelului
normalizat.

Pentru a utiliza MongoDB în programul Python, trebuie să instalăm modulul pymongo, care se face
prin linia de comandă cu comanda pip install pymongo. Un alt pas necesar este instalarea
serverului MongoDB, care poate fi descărcat de pe pagina oficială (imaginea 7.5.). Fişierul descărcat
trebuie apoi rulat pentru ca instalarea să poată fi efectuată.

Imaginea 7.5. Descărcarea instalării de pe pagina oficială MongoDB

Pentru a utiliza un modul într-un program Python, trebuie să îl introducem în program folosind
funcţia import. Următorul pas este conectarea la bază. Pentru a avea acces la baza de date, trebuie să
creăm o instanţă client care se va conecta la baza de date. Pentru o afişare mai simplă, vom folosi o
bază de date test în loc de conectarea clasică la server.

Exemplu de conectare la baza de date MongoDB cu numele test folosind instanţa client:

1from pymongo import MongoClient


2
3client = MongoClient()
4db = [Link]

Page 84 of 126
Inserarea datelor într-un tabel în MongoDB se face folosind funcţia insert(). Ne vom imagina că
dorim să creăm un contact nou şi să îl salvăm în agenda telefonică. Agenda telefonică este o colecţie
de date pe care le creăm apelând din baza de date db, folosind comanda [Link]. În cadrul
variabilei contact, vom defini datele despre noul contact în format JSON şi apoi, folosind
funcţia insertone() pentru introducerea unui obiect în baza de date, vom introduce acest contact.

Exemplu de inserare a datelor folosind funcţia insert() în MongoDB:

1from pymongo import MongoClient


2client = MongoClient()
3db=[Link]
4
5contacts = [Link]
6contact_details = {
7 'Name': 'Brian Simpson',
8 'Phone': '00442561145886'
9}
10
11contacts.insert_one(contact_details)
Metoda de actualizare a datelor este utilizată în mod identic. În acest scop, folosim funcţia update().
În cadrul parametrului, funcţiei trebuie să-i transmitem condiţia în care se va efectua actualizarea,
precum şi informaţii noi despre obiectul care face obiectul actualizării. Modificările aduse bazei de date
se fac folosind operatorul $set, care este aliasul operatorului $addFields.
Exemplu de actualizare a datelor folosind funcţia update() în MongoDB:

[Link].update_one(
2 {"Name":'Brian Simpson'},
3 {
4 "$set": {
5 "Name":"Homer Simpson",
6 "Phone":'00442561145800'
7 }
8 }
9 )
Dacă am dori să vizualizăm unele date într-un tabel, am folosi funcţia de căutare. MongoDB foloseşte
funcţia find() în acest scop. Scopul acestei funcţii este de a returna valoarea datelor pe care le
solicităm pe baza condiţilor pe care le transmitem ca parametru. Pentru afişarea vizuală, vom salva
această interogare în variabila Queryresult, apoi o vom afişa pe ecran folosind funcţia pprint, care
trebuie introdusă în program în prealabil.
Exemplu de prezentare a datelor folosind funcţia find() în MongoDB:

1import pprint from pprint


2
3Queryresult = contacts.find_one({'Name':'Homer Simpson'})
4
5pprint(Queryresult)
Codul executat astfel va afişa următorul rezultat:

{'Name': 'Homer Simpson',


'Phone': '00442561145800',
'_id': ObjectId('5f4bd767e3e63301dce33200')}

Page 85 of 126
O altă metodă susţinută de MongoDB este delete(). Pentru execuţia sa, trebuie, de asemenea, să
setăm condiţii sub forma parametrilor funcţiei. Dacă ar fi să listăm datele din baza de date după
ştergerea singurului nostru contact, rezultatul afişării ar fi None.

Exemplu de ştergere a datelor folosind funcţia delete() în MongoDB:

[Link].delete_one({"Name":'Homer Simpson'})
2
3Queryresult = contacts.find_one({'Name':'Homer Simpson'})
4
5pprint(Queryresult)

Exerciţiu

Trebuie să-i permitem utilizatorului să introducă numele cărţii sale preferate şi genul acesteia prin linia
de comandă. Datele trebuie stocate într-o bază de date nerelaţională MongoDB. După introducerea cu
succes, în linia de comandă utilizatorul trebuie să obţină toate datele din colecţie.

Soluţie:

1from pymongo import MongoClient


2client = MongoClient()
3db=[Link]
4
5name = input("Enter book name: ")
6genre = input("Enter book genre: ")
7
8books = [Link]
9book_details = {
10 'name': name,
11 'genre': genre
12}
13
14books.insert_one(book_details)
15
16
17result = [Link]()
18
19for r in result:
20 print("Book: {} | Genre: {}".format(r['name'], r['genre']))

Explicaţie:

Primul pas este să includem modulul pymongo necesar, să creăm un client Mongo şi să selectăm o bază
de date; în cazul nostru, este vorba de baza data de date test. După aceea, creăm colecţia books şi o
plasăm în variabila books. Apoi, folosind funcţia input(), putem cere utilizatorului să introducă datele
despre carte şi să le salveze. Când toate datele sunt completate, creăm un obiect cu detaliile cărţii, pe
care ulterior îl transmitem ca parametru de intrare la metoda insert_one, care salvează cartea în
colecţia books. La final, deasupra colecţiei books apelăm metoda find, care returnează toate rezultatele.
Folosind bucla for, parcurgem rezultatele individuale şi le afişăm într-un format convenabil.

Page 86 of 126
2.4. Lucrul cu cozile de mesaje

Rezumat
• Coada de mesaje este o formă de comunicare asincronă utilizată în arhitecturi fără server şi
microservice. Mesajele sunt păstrate la coadă până la procesarea şi ştergerea lor.
• Pentru a trimite un mesaj, o componentă numită publisher adaugă mesajul la coadă. Mesajul
este păstrat în coadă până când cealaltă componentă, abonatul (subscriber), descarcă mesajul
şi face ceva cu el.
• MongoDB acceptă, de asemenea, mai multe cozi, pe care le stochează ca o colecţie ale cărei
elemente sunt perechi de cheie-valoare.
• Primul pas necesar pentru a lucra cu cozile este conectarea clientului la server apelând metoda
MongoClient(). Această metodă va primi serverul şi portul ca parametri pentru instrucţiunile de
conectare.
• Inserarea elementelor într-un rând se face folosind funcţia insert_one() din coada dorită, care
transmite elementul sub forma unei perechi de cheie-valoare.
• Metoda de eliminare a elementelor este find_and_modify(). Această metodă, sub forma
parametrului remove, care reprezintă datele cheie-valoare, foloseşte funcţia pop pentru a
arunca ultimul element din rând, setând valoarea acestuia la -1.
• RabbitMQ este un software de mesagerie de tip open source, scris în limbajul funcţional Erlang.
Utilizarea acestuia necesită instalarea limbajului Erlang, a programului RabbitMQ şi a modulului
pika.
• ConnectionParameters() este utilizat pentru conectarea la baza de date, care ca parametru
primeşte serverul la care se va face conexiunea.
• Metoda channel() oferă un wrapper pentru interacţiunea cu RabbitMQ prin implementarea
metodelor şi comportamentelor.
• Coada în RabbitMQ se creează folosind metoda queue_declare(), care declară numele cozii ca
parametru.
• Expeditorul mesajului este definit folosind metoda basic_publish(). Această metodă primeşte
exchange ca parametru, care reprezintă modul de schimb al mesajelor, routing_key, adică coada
în care dorim să trimitem mesajul şi conţinutul acesteia – corpul, adică body.
• Metoda de schimb poate fi definită folosind metoda exchange_declare(). Dacă modul de schimb
nu este definit, valoarea sa implicită este un string gol şi mesajul va fi trimis la coada definită de
parametrul routing_key.
• Când este definit modul de schimb, coada ţintă sau routing_key va fi un string gol.
• Destinatarul este definit folosind metoda basic_consume(). În cadrul destinatarului, parametrul
on_message_callback ar trebui să primească funcţia callback(), care va fi executată la primirea
mesajului. Această funcţie trebuie definită.
• În cele din urmă, trebuie să se definească o buclă infinită care aşteaptă date şi rulează o funcţie
callback() definită anterior, ori de câte ori datele ajung la coadă.

Coada de mesaje este o formă de comunicare asincronă utilizată în arhitecturi fără server şi
microservice. Mesajele sunt păstrate la coadă, până când sunt procesate şi şterse. Fiecare mesaj care
ajunge la coadă este procesat o singură dată, de către un singur utilizator. În arhitecturile cloud moderne,
aplicaţiile sunt separate în blocuri de construcţie mai mici şi independente, care sunt mai uşor de
dezvoltat, implementat şi întreţinut. Cozile de mesaje oferă comunicare şi coordonare pentru aceste
aplicaţii distribuite. Ele pot simplifica semnificativ codificarea aplicaţiilor fără conexiune, îmbunătăţind
în acelaşi timp performanţa, fiabilitatea şi scalabilitatea.

Coada de mesaje conţine un buffer, care stochează temporar mesaje şi puncte finale, care permit
componentelor software să se conecteze la coadă pentru a trimite şi primi mesaje. Mesajele sunt, de
obicei, mici şi pot fi lucruri precum cereri, răspunsuri, mesaje de eroare sau doar informaţii simple. Pentru

Page 87 of 126
a trimite un mesaj, o componentă numită publisher adaugă mesajul la coadă. Mesajul rămâne la coadă
până când cealaltă componentă, adică abonatul (subscriber), descarcă mesajul şi face ceva cu el.

În arhitectura software, publish-subscribe este o formă de mesagerie în care expeditorii nu


programează mesajele care urmează să fie trimise direct către anumiţi destinatari, ci clasifică mesajele
publicate în clase fără să ştie ce abonaţi le caută, dacă există. În mod similar, destinatarii îşi exprimă
interesul pentru una sau mai multe clase şi primesc doar mesajele de care sunt interesaţi, neştiind de la
ce expeditori provin, dacă există.

Imaginea 8.1. Prezentarea şablonului publish–subscribe pe exemplul unei cozi de mesaje

Cozile în MongoDB

Pentru a înţelege mai bine cum funcţionează cozile, vom arăta mai întâi un exemplu simplu folosind
MongoDB. Cozile din MongoDB funcţionează la fel ca şi cozile din lumea reală, conform principiului
FIFO. Cu alte cuvinte, elementele care sosesc sunt adăugate la sfârşitul cozii, în timp ce elementele care
sunt eliminate din coadă sunt luate de la început.
MongoDB acceptă, de asemenea, mai multe cozi. În continuarea lecţiei vom arăta cum se pot stoca
aceste cozi în MongoDB sub forma unei colecţii ale cărei elemente sunt perechi de cheie-valoare. Pentru
început, trebuie să introducem modulul pymongo în programul nostru, astfel încât să putem utiliza
toate funcţiile MongoDB, ceea ce facem cu comanda import pymongo.

După ce am introdus acest modul în program, primul pas necesar este conectarea clientului la server
apelând metoda [Link](). Această metodă va primi serverul şi portul ca parametri
pentru instrucţiunile de conectare, care, în cazul nostru, va fi serverul local şi portul implicit MongoDB.
Stocăm clientul creat astfel într-o variabilă şi îl conectăm la baza de date test, aşa cum putem vedea în
exemplul de mai jos.

Exemplu de conectare a clientului la baza de date în MongoDB:

1import pymongo
2
3c = [Link]('localhost', 27017)
4db = c['test']

Page 88 of 126
Vom introduce apoi două documente, coada q1 şi coada q2, în tabelul nostru test, care este o colecţie
de baze de date. Inserarea elementelor într-o coadă se face folosind funcţia insert_one() în coada
dorită, care transmite elementul cozii sub forma unei perechi de cheie-valoare. În continuare putem
vedea inserarea elementelor în două cozi.

Exemplu de inserare a elementelor în cozi în MongoDB:

1db.q1.insert_one({'val': 'first message'})


2db.q1.insert_one({'val': 'second message'})
3db.q1.insert_one({'val': 'third message'})
4
5db.q2.insert_one({'val': 'queue2 first message'})
6db.q2.insert_one({'val': 'queue2 second message'})
7
8for row in [Link]():
9 print (row)
10for row in [Link]():
11 print (row)

Colecţia noastră de date are acum două cozi de date, coada q1 şi coada q2. Apelând metoda find() în
cozi, este posibilă revizuirea tuturor elementelor care se află în acea coadă, aşa cum se poate vedea în
imaginea 8.2.

Imaginea 8.2. Prezentarea elementelor din cozile q1 şi q2 folosind funcţia find()

Eliminarea elementelor din coadă se face într-un mod puţin mai diferit. Metoda utilizată în acest scop
este find_and_modify() care, sub forma parametrului remove, care reprezintă datele cheie-valoare,
foloseşte funcţia pop pentru a elimina ultimul element din coadă, setând valoarea acestuia la -1, aşa
cum putem vedea în exemplul de mai jos. Parametrul full_response, care este setat la True, indică
faptul că la sfârşitul metodei dorim să vizualizăm întregul element care a fost eliminat, mai degrabă
decât cheia acestuia.

Exemplu de eliminare a datelor din cozi în MongoDB:

1asd = db.q1.find_and_modify(remove={"$pop": {'$val': -1}},


2full_response= True)
3
4print(asd['value'])
Metoda de eliminare a elementelor din coadă prin execuţia comenzii definite mai sus, va elimina primul
element inserat, adică cel mai vechi prim mesaj (first message). Pentru a ne asigura de acest lucru pentru
colecţia de baze de date q1, vom enumera toate elementele stocate, aşa cum putem vedea mai jos.

Page 89 of 126
Exemplu de afişare a cozii de date în MongoDB:

1for row in [Link]():


2 print(row)

Imaginea 8.3. Elementele cozii q1 după eliminarea primei cozi

Cozile în RabbitMQ

RabbitMQ este un software de mesagerie de tip open source. Deoarece este scris în limbajul funcţional
Erlang, utilizarea acestuia necesită instalarea acestui limbaj, care poate fi găsit pe site-ul oficial. La
începutul acestei pagini, există diferite versiuni ale instalării (imaginea 8.4.). În exemplul nostru, vom
selecta versiunea Windows Binary File care se potriveşte arhitecturii computerului nostru. După
descărcarea fişierului, îl vom instala, rulându-l.

Imaginea 8.4. Pagina oficială pentru descărcarea instalării limbajului funcţional Erlang

Următorul pas necesar este instalarea software-ului RabbitMQ, care poate fi găsit la acest link oficial. În
cadrul paginii vom găsi descărcări directe de instalare şi vom selecta versiunea recomandată, setată de
GitHub (imaginea 8.5.). După descărcarea acestui fişier, vom efectua instalarea, executându-l.

Imaginea 8.5. Descărcări directe ale instalării în cadrul paginii oficiale a software-ului RabbitMQ

Page 90 of 126
Al treilea element necesar pentru funcţionarea acestui software este clientul, pe care îl obţinem prin
instalarea modulului pika prin emiterea comenzii python -m pip install pika --upgrade în
linia de comandă a computerului.

Notă

Există un număr mare de clienţi RabbitMQ în multe limbaje diferite. În exemplul nostru vom folosi modulul Pika
1.0.0, care este un client Python recomandat de echipa RabbitMQ.

Pentru crearea conexiunii, folosim un modul pika, pe care îl introducem mai întâi în program folosind
linia de cod import pika. În continuare prezentăm modul în care acest modul poate fi utilizat pentru a
ne conecta la un server local.

Exemplu de conexiune la server folosind modulul pika în RabbitMQ:

1import pika
2
3connection = [Link]([Link]('localhost'))
4channel = [Link]()

Metoda [Link]() creează un strat în partea de sus a vârfului de bază asincron,


oferind metode care se vor bloca până când răspunsul lor aşteptat este returnat. Pentru conexiunea la
baza de date, se foloseşte [Link](), care, ca parametru, primeşte serverul
la care se va face conexiunea. Dacă am vrea să ne conectăm la o altă maşină, am pune adresa IP a maşinii
respective în loc de localhost. Metoda [Link]() oferă un wrapper pentru
interacţiunea cu RabbitMQ prin implementarea metodelor şi atributelor.

Următorul pas este crearea cozii în care vor fi trimise mesajele. Dacă nu există coadă de destinatar,
RabbitMQ va renunţa la mesaj. O coadă se poate crea folosind metoda queue_declare(), care
declară numele cozii ca parametru. O vom crea în cadrul programului [Link].

Exemplu de declarare a cozii de trimitere a mesajelor în RabbitMQ:

channel.queue_declare(queue='hello')

Acum când avem o coadă definită, putem trimite mesajul folosind metoda basic_publish(). Această
metodă primeşte exchange ca parametru, care este un mod de schimb de mesaje; routing_key, adică
linia în care dorim să trimitem mesajul şi conţinutul acestuia – corpul (body).

Exemplu de trimitere a unui mesaj în coada hello folosind RabbitMQ:

1channel.basic_publish(exchange='',
2 routing_key='hello',
3 body='Hello World!')
4
5print(" [x] Sent 'Hello World!'")

În exemplul de mai sus este trimis mesajul Hello World! în coada declarată ca hello.
Parametrul exchange din acest exemplu este un string gol, deoarece este vorba despre trimiterea unui
mesaj direct la coadă. Cu toate acestea, în practică, mesajele dintr-o coadă nu vor fi trimise niciodată
direct.

Page 91 of 126
Vom lua ca exemplu metoda de schimb fanout, care joacă rolul de a transmite mesaje în toate cozile
cunoscute. Metoda de schimb poate fi definită folosind metoda exchange_declare(). Când numele
acestui schimb este declarat, acesta poate fi apelat ulterior prin acest nume ca parte a metodei de
trimitere. În acest caz, coada în care sunt trimise mesajele nu poate fi definită în mod specific.

Exemplu de trimitere a unui mesaj către toate cozile cunoscute folosind RabbitMQ:

1<br>channel.exchange_declare(exchange='msgs',
2 exchange_type='fanout')
3
4 channel.basic_publish(exchange='msgs',
5 routing_key='',
6 body='Message')

După trimiterea mesajelor, se recomandă golirea bufferului şi închiderea conexiunii, aşa cum se face în
exemplul de mai jos.

Exemplu de închidere a conexiunii în RabbitMQ:

[Link]()

Celălalt program al nostru, [Link], va primi mesaje din coadă şi le va lista pe ecran. Pentru a primi
mesaje, trebuie să ne conectăm la server în acelaşi mod ca atunci când le trimitem. Pentru a ne asigura
că comanda există, o vom declara din nou. Indiferent de câte ori vom emite o comandă pentru a crea
un rând, va fi creată doar una.

Exemplu de declarare a cozii pentru primirea mesajelor în RabbitMQ:

channel.queue_declare(queue='hello')

Primirea mesajelor dintr-o coadă este puţin mai complicată şi se face folosind metoda callback().
Ori de câte ori o coadă primeşte un mesaj, biblioteca pika apelează această funcţie. În cazul nostru,
această funcţie va lista conţinutul mesajului pe ecran.

Exemplu de declarare a cozii pentru primirea mesajelor în RabbitMQ:

1def callback(ch, method, properties, body):


2 print(" [x] Received %r" % body)
3
4channel.basic_consume(queue='hello',
5 auto_ack=True,
6 on_message_callback=callback)

Metoda basic_consume() defineşte modul în care vor fi recuperate mesajele din coadă. Primeşte ca
parametru coada - queue, de la care vor fi preluate datele. Parametrul auto_ack setat la True indică
confirmarea automată a mesajelor primite cu succes în coadă. Parametrul on_message_callback ar
trebui să specifice funcţia care va fi executată la primirea mesajului, care în cazul nostru este o listare
definită utilizând metoda callback.

Page 92 of 126
În cele din urmă, definim o buclă infinită care aşteaptă datele şi porneşte funcţia callback() ori de
câte ori datele ajung la coadă. În caz de întrerupere a programului, este definită şi
funcţia KeyboardInterrupt().

Exemplu de declarare a buclei infinite pentru primirea datelor în coada din RabbitMQ:

1print(' [*] Waiting for messages. To exit press CTRL+C')


2channel.start_consuming()
3
4if __name__ == '__main__':
5 try:
6 main()
7 except KeyboardInterrupt:
8 print('Interrupted')
9 try:
10 [Link](0)
11 except SystemExit:
12 os._exit(0)

Să vedem mai jos cum arată acum programele noastre [Link] şi [Link].

Exemplu de program [Link] pentru primirea mesajelor în coada din RabbitMQ:

1import pika
2
3connection = [Link](
4 [Link](host='localhost'))
5
6channel = [Link]()
7
8channel.queue_declare(queue='hello')
9
10channel.basic_publish(exchange='',
11 routing_key='hello',
12 body='Hello World!')
13
14print(" [x] Sent 'Hello World!'")
15
[Link]()

Exemplu de program [Link] pentru primirea mesajelor în coada din RabbitMQ:

1import pika, sys, os


2
3def main():
4 connection = [Link](
5 [Link](host='localhost'))
6
7 channel = [Link]()
8

Page 93 of 126
9 channel.queue_declare(queue='hello')
10
11 def callback(ch, method, properties, body):
12 print(" [x] Received %r" % body)
13
14 channel.basic_consume(queue='hello',
15 on_message_callback=callback,
16 auto_ack=True)
17
18 print(' [*] Waiting for messages. To exit press CTRL+C')
19
20 channel.start_consuming()
21
22if __name__ == '__main__':
23 try:
24 main()
25 except KeyboardInterrupt:
26 print('Interrupted')
27 try:
28 [Link](0)
29 except SystemExit:
30 os._exit(0)
Pentru a avea o imagine realistă a modului în care funcţionează aceste mesaje, vom deschide programul
destinatarului în linia de comandă, poziţionându-ne în directorul în care se află fişierul şi atribuind
comanda python [Link]. În fereastra de comandă vom primi informaţii despre aşteptarea
unui mesaj ca în imaginea 8.6.

Imaginea 8.6. Rularea destinatarului de mesaje [Link]

Vom rula apoi programul expeditorului pentru a trimite mesaje într-o coadă. În imaginea 8.7. putem
vedea cum expeditorul trimite trei mesaje la coadă, în cadrul programului [Link].

Page 94 of 126
Imaginea 8.7. Rularea destinatarului de mesaje [Link]

Mesajele trimise vor face acum modificări la coada definită. Ca urmare a acestor trei rulări ale
programului [Link], informaţiile despre mesajele primite vor fi afişate în fereastra de comandă a
destinatarului, care se poate vedea în imaginea 8.8.

Imaginea 8.8. Prezentarea programului destinatarului după primirea mesajelor în coada hello

Page 95 of 126
3.1. Tabele şi obiecte

Rezumat
• Modelul obiectual-relaţional implică o schiţă a structurii bazei de date care va fi implementată
în sistemul informaţional în etapele ulterioare.
• Cele trei componente de bază ale modelului OR sunt obiectele, atributele şi relaţiile.
• Un obiect poate fi orice entitate din lumea reală uşor de identificat.
• Relaţia ierarhică a obiectelor distinge trei concepte:

o generalizare - gruparea mai multor entităţi care au aceleaşi caracteristici într-un singur
set de entităţi reprezentate de o nouă entitate;
o specializare - un proces opus generalizării, care împarte un grup de entităţi în
subgrupuri pe baza caracteristicilor lor;
o moştenire - permite entităţilor de nivel inferior să moştenească atributele entităţilor de
nivel superior.

• Un obiect slab este un obiect care nu poate fi identificat în mod unic prin propriile sale atribute
şi se bazează pe o relaţie cu un alt obiect.
• Atributele sunt proprietăţi care descriu obiecte folosind o valoare.
• Distingem patru tipuri de atribute:

o simplu - un atribut a cărui valoare este atomică, adică nu poate fi descompus în mai
multe atribute;
o complex - un atribut ale cărui valori nu sunt atomice, adică pot fi descompuse în
continuare;
o cu valori multiple - un atribut care poate conţine mai multe valori de acelaşi tip;
o derivat - un atribut care poate fi derivat dintr-un atribut existent.

• Cheile sunt atribute care identifică în mod unic entităţile.


• Există trei tipuri de chei:

o cheie primară - orice atribut care defineşte complet toate celelalte atribute ale entităţii;
o cheie complexă - atribute multiple care identifică colectiv o entitate;
o candidatul pentru cheie - orice atribut unic în comparaţie cu atributele altor entităţi.

• Relaţiile reprezintă modul în care obiectele sunt relaţionate într-un model.


• Cardinalităţile se referă la marcarea numărului de instanţe de obiecte care pot participa la o
relaţie.
• Relaţiile binare sunt relaţii între două obiecte. Distingem următoarele relaţii:

o unu-la-unu (1:1) - o instanţă a unei entităţi poate fi relaţionată de o singură instanţă a


unui alt obiect;
o unu-la-mai-mulţi (1:N) - o instanţă a unei entităţi poate fi relaţionată de un număr infinit
de instanţe ale altei entităţi, în timp ce, pe de altă parte, aceste instanţe pot fi relaţionate
numai de aceasta;
o mai-mulţi-la-unu (N:1) - instanţele multiple ale unei entităţi pot fi relaţionate de cel
mult o instanţă a altei entităţi, în timp ce acea instanţă poate fi relaţionată de un număr
infinit de altele;
o mai-mulţi-la-mai-mulţi (N:N) – un număr infinit de instanţe ale ambelor entităţi pot
participa la relaţie.

Page 96 of 126
• Agregarea este abstractizarea obiectelor conectate într-un singur obiect la un nivel mai ridicat
de abstractizare.

Pentru proiectarea sistemelor informaţionale, este extrem de important să proiectăm baza de date,
astfel încât funcţionalităţile pe care le furnizează să fie în conformitate cu cerinţele sistemului. În aceste
scopuri, se foloseşte modelul obiectual-relaţional ER (Entity-Relation) sau, altfel spus, modelul
obiecte-relaţii, care este o schiţă a structurii bazei de date care va fi implementată în sistemul
informaţional ţintă într-o etapă ulterioară.

Modelul obiectual-relaţional se bazează pe entităţi din lumea reală care au anumite caracteristici şi
interacţiuni cu alte entităţi. Din conceptul dat, se face o clasificare în trei componente de bază:

• obiecte - o entitate din lumea reală;


• atribute - caracteristicile obiectelor;
• relaţii - interacţiuni cu alte entităţi.

Imaginea 9.1. Componentele de bază ale modelului obiectual-relaţional

Obiectele

Un obiect poate fi orice entitate din lumea reală uşor de identificat. Dacă, de exemplu, ne uităm la o
facultate, entităţile sale sunt studentul, examenul, profesorul, disciplina, departamentul şi aşa mai
departe. Fiecare dintre aceste entităţi are propriile sale atribute care o descriu mai detaliat, adică îi
conferă o identitate. O colecţie de obiecte multiple de un tip similar se numeşte set de entităţi.

Modelul obiectual-relaţional are puterea de exprimare a entităţilor bazei de date într-un mod ierarhic
conceptual. Pe măsură ce ierarhia creşte, ea generează o vedere asupra entităţilor şi, pe măsură ce
mergem mai adânc în ierarhie, ne oferă detaliile fiecărei entităţi implicate. În ierarhie, distingem trei
concepte de organizare: generalizare, specializare şi moştenire.

• Generalizarea este gruparea mai multor entităţi care au aceleaşi caracteristici într-un singur set
de entităţi reprezentate de o nouă entitate.

Page 97 of 126
Imaginea 9.2. Exemplu de generalizare a alimentelor în modelul obiectual-relaţional

• Specializarea este opusul generalizării, care împarte un grup de entităţi în subgrupuri pe baza
caracteristicilor lor.

Imaginea 9.3. Exemplu de specializare a turistului în modelul obiectual-relaţional

• Moştenirea este o proprietate importantă de generalizare şi specializare. Permite entităţilor de


nivel inferior să moştenească atributele entităţilor de nivel superior. Conceptul de moştenire
este utilizat în programarea orientată pe obiecte pentru crearea claselor ale căror atribute nu
vor fi vizibile pentru utilizatori. Acest concept se mai numeşte şi abstractizare. Următorul
exemplu arată cum un turist, fie că este unul local sau străin, pe lângă numărul cărţii de
identitate sau numărul paşaportului, moşteneşte şi CNP-ul care îl determină în mod unic
(imaginea 9.4.).

Imaginea 9.4. Exemplu de moştenire în modelul obiectual-relaţional

Page 98 of 126
Un obiect care nu poate fi identificat în mod unic prin propriile sale atribute şi care se bazează pe o
relaţie cu un alt obiect, se numeşte obiect slab. Obiectul slab este reprezentat de un dreptunghi dublu.
De exemplu, un cont bancar nu poate fi identificat în mod unic fără a cunoaşte banca căreia îi aparţine
contul, deci, un cont bancar este o entitate slabă.

Imaginea 9.5. Exemplu de obiect slab în modelul obiectual-relaţional

Atributele

Obiectele sunt reprezentate de proprietăţi pe care le numim atribute. Toate atributele au valori, cum ar
fi, de exemplu, obiectul turist din exemplul nostru anterior (imaginea 9.4.) are atributele CNP, numele şi
data naşterii. Există un domeniu sau o gamă de valori care pot fi atribuite atributelor. Astfel, de exemplu,
putem pune o constrângere asupra CNP-ului că trebuie să fie unic sau că numele turistului nu poate fi
o valoare numerică. Distingem patru tipuri de atribute: simple, complexe, cu valori multiple şi derivate.

• Atributul simplu este un atribut a cărui valoare este atomică, adică nu poate fi descompus în
mai multe atribute. În exemplul nostru, acesta este atributul CNP (imaginea 9.6.). Valoarea
acestui atribut este subliniată, deoarece reprezintă identificatorul entităţii, care va fi discutat mai
detaliat în continuare.

Imaginea 9.6. Exemplu de atribut simplu în modelul obiectual-relaţional

• Atributul complex este un atribut ale cărui valori nu sunt atomice, adică pot fi descompuse în
continuare. Descompunerea atributului foloseşte structura arborelui, ca în exemplul nostru de
separare a atributului name în valorile atomice prenume şi nume (imaginea 9.7.).

Imaginea 9.7. Exemplu de atribut complex în modelul obiectual-relaţional

Page 99 of 126
• Atributul cu valori multiple este un atribut care poate conţine mai multe valori de acelaşi tip.
În exemplul nostru de turist, vom presupune că acesta are mai multe numere de telefon. Acest
atribut este marcat cu o elipsă dublă.

Imaginea 9.8. Exemplu de atribut cu valori multiple în modelul obiectual-relaţional

• Atributul derivat este un atribut care poate fi derivat dintr-un atribut deja existent. De exemplu,
din data naşterii putem vedea vârsta turistului. Aceste atribute sunt marcate printr-o elipsă
punctată.

Imaginea 9.9. Exemplu de atribut moştenit în modelul obiectual-relaţional

Atributele care identifică în mod unic entităţile sunt numite chei. Distingem trei tipuri diferite de chei:
• Cheia primară este orice atribut care defineşte complet toate celelalte atribute ale entităţii,
deoarece, de exemplu, CNP determină complet turistul în cauză.
• Cheia complexă implică mai multe atribute care identifică colectiv entitatea, la fel cum am lua,
pe lângă CNP, şi numărul paşaportului unui turist străin ca identificator.
• Candidatul pentru o cheie este orice atribut unic în comparaţie cu atributele altor entităţi.

Page 100 of 126


Relaţiile

Termenul de relaţie din bazele de date ne permite să descriem cum sunt relaţionate tabelele de date în
baza noastră de date. Pentru prezentarea relaţiei se foloseşte un romb, iar liniile care provin de la aceasta
relaţionează entităţile implicate în relaţie. Relaţiile pot fi definite între două, trei sau mai multe obiecte.

Relaţiile dintre două obiecte se numesc relaţii binare. Indicarea numărului de instanţe de obiecte care
pot participa la o relaţie cu un singur nume se numeşte cardinalitate. Relaţiile binare pot fi în oricare
dintre următoarele relaţii:

• Unu-la-unu (1:1) - o instanţă a unei entităţi poate fi relaţionată de o singură instanţă a unui alt
obiect.
• Unu-la-mai-mulţi (1:N) - o instanţă a unei entităţi poate fi relaţionată de un număr infinit de
instanţe ale altei entităţi, în timp ce, pe de altă parte, aceste instanţe pot fi relaţionate numai de
aceasta. Această instanţă, cu alte cuvinte, conţine o listă de obiecte instantanee la care este
asociată.
• Mai-mulţi-la-unu (N:1) - instanţele multiple ale unei entităţi pot fi relaţionate de cel mult o
instanţă a altei entităţi, în timp ce acea instanţă poate fi relaţionată de un număr infinit de altele.
În acest caz, mai multe instanţe au un atribut care reprezintă o referinţă la entitatea cu care sunt
relaţionate.
• Mai-mulţi-la-mai-mulţi (N:N) – un număr infinit de instanţe ale ambelor entităţi pot participa
la relaţie. Acest tip de relaţie implică agregarea obligatorie, care va fi discutată mai detaliat în
continuare.

Notă

Relaţiile explicate arată doar limita superioară a cardinalităţii entităţii, cu presupunerea că limita inferioară a
cardinalităţii este 1, adică în fiecare relaţie pentru fiecare entitate există cel puţin o instanţă care participă la
aceasta. Cu alte cuvinte, notaţia 1 din relaţie reprezintă cardinalitatea (1,1), în timp ce notaţia N reprezintă
notaţia cardinalităţii (1, N).

Cardinalităţile referitoare la o entitate sunt scrise chiar lângă acea entitate, pe linia care merge la relaţie
(romb). Pentru a înţelege mai bine conceptul de relaţii, vom arăta cum arată într-un exemplu specific
(imaginea 9.10.).

Imaginea 9.10. Prezentarea cardinalităţii pe exemplul modelului de susţinere a examenului la facultate

Page 101 of 126


Modelul nostru obiectual-relaţional arată cum ar trebui să arate un sistem informaţional pentru
susţinerea unui examen. Pentru început, avem un obiect puternic, facultatea, care poate avea mai multe
specializări. Specializarea, pe de altă parte, nu poate exista fără facultate şi reprezintă obiectul ei slab. O
specializare la facultate poate fi urmată de mai mulţi studenţi, în timp ce un student poate urma exact
o specializare. Să presupunem că studenţii vin la facultate exclusiv cu maşina şi că aceste două obiecte
sunt relaţionate prin parcare. Un student vine cu o singură maşină şi numai acel student poate parca
maşina respectivă. În cele din urmă, atunci când vine vorba de susţinerea examenului, un student poate
susţine examenul de mai multe ori, iar examenul poate fi susţinut de mai mulţi studenţi. O astfel de
relaţie în modelul obiectual-relaţional implică agregarea obligatorie.

Agregarea în modelul obiectual-relaţional reprezintă abstractizarea obiectelor relaţionate într-un


singur obiect la un nivel de abstractizare mai ridicat. Acest obiect se numeşte obiect agregat şi poate
avea propriile sale atribute sau poate lua atribute de la obiectele componente. Obiectele agregate sunt
reprezentate printr-un romb încadrat şi săgeţi, pornind de la entitate şi arătând spre ea şi denotă
cardinalitatea 1. În exemplul nostru de susţinere a examenului, vom arăta cum relaţia de susţinere a
examenului devine un obiect agregat (imaginea 9.11.).

Imaginea 9.11. Prezentarea obiectului agregat pe exemplul modelului de susţinere a examenului la


facultate

În imaginea 9.11., un model complet obiectual-relaţional este prezentat pe exemplul susţinerii


examenului. În bazele de date relaţionale, fiecare obiect este afişat ca un tabel. Coloanele tabelului
reprezintă atributele unui obiect dat, în timp ce instanţele de obiecte sunt introduse în rândurile
tabelului. O schiţă a structurii şi constrângerilor tabelelor, precum şi a relaţiei dintre acestea, este
reprezentată de o schemă de date.

Mai jos, vă prezentăm schema de date pentru exemplul nostru de susţinere a examenului (imaginea
9.12.). Pentru această prezentare, vom folosi programul MySQL Workbench, a cărui instalare şi utilizare
am explicat-o în lecţia cu acelaşi titlu din cursul MySQL Programming and Administration.

Page 102 of 126


Imaginea 9.12. Prezentarea modelului obiectual-relaţional de susţinere a examenului în mediul MySQL
Workbench

Page 103 of 126


3.2. Maparea obiectual-relaţională

Rezumat
• Maparea obiectual-relaţională (ORM) este o bibliotecă de coduri care automatizează transferul
de date stocate în tabelele de baze de date relaţionale către obiectele utilizate în codul aplicaţiei.
• Cele două cadre cel mai utilizate în Python sunt Flask şi Django, care folosesc SQLAlchemy şi
Django ORM pentru ORM.
• SQLAlchemy este „mai inteligent” pentru lucrul cu structuri de date extrem de complexe.
• Django este potrivit pentru procesarea interogărilor de complexitate mică şi medie. Acceptă
funcţia de migrare care face parte din Django ORM. Este singurul cadru complet în sine.
• Utilizarea cadrului Django în bazele de date MySQL necesită instalarea:

o software-ului Django;
o serverului MySQL;
o adaptorului mysqlclient;
o instrumentului Microsoft Visual C ++ Build Tools.

• Crearea unui proiect în cadrul Django se face cu comanda django-admin startproject


project_name.
• Crearea unei aplicaţii în cadrul Django se face cu comanda python [Link] starttapp
app_name.
• Pentru a utiliza aplicaţia, este obligatoriu să o adăugaţi la lista de aplicaţii din fişierul [Link]
al proiectului.
• Verificarea instalării se efectuează cu comanda: python [Link] runserver [Link]:8000.
• În fişierul [Link], trebuie să setaţi parametrii bazei de date: adaptorul, numele bazei de date,
serverul şi portul.
• Definirea clasei se face în fişierul [Link]. Modulul modules conţine următoarele funcţii de
bază:

o definirea tabelului - Module;


o definirea coloanei - CharType (constraint);
o definirea relaţiei - ManyToMany (object).

• Migraţiile aplică modificări bazei de date:

o python [Link] migrate - aplicarea fişierului [Link], acesta este un fişier de


înlocuire a administratorului Django;
o python [Link] makemigrations app_name - aplicarea modificărilor la baza de date
denumită.

• Panoul de administrare Django este o interfaţă Django care permite manipularea datelor din
baza de date Django. Este localizat la adresa localhost: 8000/admin.

Maparea obiectual-relaţională (ORM) presupune o bibliotecă de coduri care automatizează transferul


de date stocate în tabelele de baze de date relaţionale către obiectele utilizate în codul aplicaţiei. ORM-
urile oferă o abstracţie la nivel înalt pe o bază de date relaţională, care permite unui programator să
scrie cod Python în loc de cod SQL, pentru a insera, citi, actualiza şi şterge date şi scheme. Programatorii
pot folosi un limbaj de programare care le convine, pentru a lucra cu baza de date, în loc să scrie
interogări SQL sau proceduri stocate.

Page 104 of 126


Imaginea 10.1. Prezentarea obiectului Python în tabel

Maparea obiectual-relaţională, aşa cum ne sugerează şi numele său, mapează atributele obiectelor la
câmpurile corespunzătoare din tabel şi le poate prelua în acelaşi mod. Avantajele acestei abordări, în
comparaţie cu cele tradiţionale, se reflectă în dezvoltarea rapidă, portabilitatea bazelor de date şi
uşurinţa aplicării. În trecut, programatorii de aplicaţii web trebuiau să cunoască şi limbajele bazelor de
date. Cunoaşterea SQL nu este, de asemenea, suficientă, dat fiind faptul că implementările SQL sunt
puţin mai diferite una de cealaltă în diferite baze de date. Acesta a devenit un proces dificil şi consumator
de timp, iar cadrele web au fost create pentru conceptul ORM. Cele două cadre cel mai utilizate în
Python sunt Flask şi Django, care folosesc SQLAlchemy şi Django ORM pentru ORM. În imaginea 10.2.
vedem ce este necesar pentru implementarea lor.

Imaginea 10.2. Prezentarea instrumentelor pentru lucru în cadrele Flask şi Django

Alegerea ORM-ului depinde în totalitate de natura şi scopul proiectului. În majoritatea cazurilor,


programatorii preferă SQLAlchemy. Motivul este că SQLAlchemy este „mai inteligent” pentru a lucra cu
structuri de date extrem de complexe. Django ORM este puternic în felul său, deoarece oferă funcţii
integrate în Django care sunt mai importante decât îmbunătăţirea performanţei în sine.

Deoarece întreaga lecţie următoare va fi dedicată cadrului SQLAlchemy, vom prezenta mai jos un
exemplu practic de Django ORM.

Django ORM

Django vine cu propriul ORM. Este vorba de un ORM foarte eficient, care este strâns legat de cadrul
Django. Django ORM este potrivit pentru procesarea interogărilor de complexitate mică şi medie, deşi
unii consideră că SQLAlchemy ORM este o opţiune mai bună. O altă funcţie încorporată pe care o
acceptă Django este funcţia de migrare, care face parte, de asemenea, din Django ORM. Django este
singurul cadru complet în sine.

Django ORM ne oferă seturi de interogări pe care le putem folosi pentru a prelua date din baza de date.
Un set de interogări este o listă de obiecte model care sunt utilizate pentru filtrarea şi sortarea datelor.

Page 105 of 126


În continuare, prezentăm cum Django ORM înlocuieşte crearea clasică a tabelelor în limbajul bazei de
date SQL (imaginea 10.3.).

Imaginea 10.3. Crearea tabelului cu Django ORM

Notă

Django are un sistem pentru atribuirea automată a identificatorilor de înregistrări şi, pe lângă atributele listate,
adică coloane, o coloană id va fi adăugată automat în tabel, care identifică în mod unic rândurile de înregistrare
şi reprezintă cheia lor primară.

Biblioteca Django ORM nu este încorporată în limbajul de programare Python. Prin urmare, trebuie să
o instalăm înainte de a o utiliza. Efectuăm instalarea prin tastarea comenzii pip install django în linia
noastră de comandă.

Vom arăta utilizarea Django ORM pe exemplul sistemului informaţional pentru susţinerea examenului
din lecţia anterioară. Vom folosi MySQL ca bază de date, precum şi ca server al acesteia. Spre deosebire
de exemplele anterioare, când am folosit adaptorul [Link] pentru a lucra cu aceste baze de
date, acum vom folosi mysqlclient. Motivul pentru aceasta este versiunea Django 3, care este cea mai
recentă versiune care există la momentul realizării acestui curs, care nu acceptă conexiuni folosind
biblioteca conectorilor. În prezent, se lucrează la upgrade-ul adaptorului [Link], care va fi
susţinut în viitor în modulul Django 3. Modulul mysqlclient se instalează utilizând comanda pip
install mysqlclient.

Pentru a utiliza modulul mysqlclient, sunt necesare instrumente suplimentare pentru execuţia acestuia.
Această problemă poate fi rezolvată instalând Microsoft Visual C++ Build Tools. Instalarea acestor
instrumente vine împreună cu Microsoft Visual Studio, care poate fi obţinut prin acest link. În timpul
instalării, trebuie să bifăm aceste instrumente ca suplimentul dorit cu Microsoft Visual Studio (imaginea
10.4.).

Page 106 of 126


Imaginea 10.4. Prezentarea instalării instrumentelor Microsoft Visual C++ Build Tools, obligatorii pentru
utilizarea modulului mysqlconnector

Notă

Pentru utilizarea cadrului Django, arhitectura Python trebuie să fie compatibilă cu arhitectura computerului.
Deoarece în lecţiile anterioare am folosit arhitectura Python pe 32 de biţi, trebuie să instalăm un program
Python pe 64 de biţi dacă computerul nostru foloseşte arhitectura respectivă.
Odată ce am instalat cu succes Django şi Microsoft Visual C++ Build Tools, creăm proiectul utilizând
aceeaşi procedură ca în lecţia Mediul şi ciclul de viaţă din cursul Web Application Building. În continuare,
este prezentat procesul de creare a unei noi aplicaţii my_first_app în linia de comandă.
Procesul de creare a aplicaţiei my_first_app în linia de comandă:

1django-admin startproject first_project


2cd first_project
3python [Link] startapp my_first_app
În cadrul proiectului creat, vom seta anumite configuraţii în fişierul de setări creat automat, astfel încât
să corespundă aplicaţiei noastre. Folosind Python IDLE, vom deschide [Link] pentru a afişa aplicaţia
noastră în lista de aplicaţii utilizate de proiect. Noile setări sunt prezentate în imaginea 10.5.

Imaginea 10.5. Introducerea unei noi aplicaţii în fişierul [Link] pentru setări

Page 107 of 126


După crearea acestei aplicaţii, vom testa succesul instalării programului Django. Pentru a testa instalarea,
vom rula următoarea comandă în directorul proiectului în linia de comandă:

python [Link] runserver [Link]:8000

În acest fel, pornim serverul nostru local la adresa dată lângă portul cu numărul 8000, care este folosit
ca server de dezvoltare Django. Pentru a verifica serverul care rulează, îi vom redirecţiona calea
[Link]:8000 către browserul nostru. Dacă conexiunea la serverul web Django s-a stabilit cu succes, pe
ecran va apărea o imagine cu o rachetă (imaginea 10.6.).

Imaginea 10.6. Prezentarea conectării reuşite la serverul web Django [Link]:8000

Odată ce am stabilit că programul nostru Django a fost instalat cu succes, următorul pas este să ne
conectăm la baza de date. Pentru început, vom crea baza de date la care dorim să ne conectăm în cadrul
clientului nostru MySQL. În cazul nostru, vom crea o bază de date example.
Mai mult, această bază de date trebuie să fie legată de proiect, ceea ce facem în cadrul setărilor fişierului
[Link] aflat în proiectul nostru. În cadrul obiectului DATABASE, vom seta parametrii, astfel încât
aplicaţia să ştie cum să utilizeze adaptorul mysql şi să se conecteze la baza de date example de pe portul
8000 al serverului local. Setările ar trebui să arate acum ca în imaginea 10.7.

Imaginea 10.7. Setarea parametrilor bazei de date în cadrul fişierului [Link] pentru setarea
proiectului

Page 108 of 126


Odată cu configurarea acestui mediu, putem începe definirea claselor. Declararea claselor în Django se
face în fişierul [Link] al aplicaţiei. Pentru a utiliza modelul, în program trebuie să introducem
modulul models. Acest model vă permite definirea tabelelor, atributelor şi relaţiilor apelând clasele
încorporate corespunzătoare.

Clasa Model reprezintă o sursă unică şi finită de informaţii despre date. În general, fiecare obiect Model
este mapat la un tabel de baze de date. Pentru maparea unui tabel pe baza unei clase declarate, ca
parametru, trebuie să transmitem modelul.

Declararea atributului care este, de fapt, o coloană de tabele, utilizează clase încorporate de tipuri de
date care acceptă constrângeri ca parametru. La acest link puteţi vedea o listă a tuturor tipurilor
susţinute.

Relaţionarea tabelelor se face, de asemenea, apelând clasele încorporate ale modulelor models care
sunt destinate acestui lucru. Aceste clase iau obiectul tabelului la care se leagă ca parametru obligatoriu,
în timp ce alţi parametri sunt opţionali. Parametrul on_delete, în cazul nostru, dictează că dacă
instanţa student, la care maşina are o referinţă, este ştearsă, ar trebui ştearsă şi maşina. În caz contrar,
maşina nu ar avea un obiect la care să se refere şi existenţa sa nu ar avea sens.
Parametrul related_name reprezintă numele coloanei de atribute care va fi referinţa la obiectele
student. La acest link puteţi vedea toate clasele posibile de relaţii în Django.

În continuare este prezentat un exemplu de declarare a claselor student şi maşină.

Exemplu de declarare a claselor în cadrul fişierului de aplicaţie [Link]:

1from [Link] import models


2
3class Student([Link]):
4 student_id = [Link](max_length=10)
5 name = [Link](max_length=50)
6 address = [Link](max_length=50)
7
8class Car([Link]):
9 brend = [Link](max_length=45)
10 student = [Link](
11 Student,
12 on_delete=[Link],
13 related_name='car'
14 )

Modelele create în acest mod trebuie să fie înregistrate în fişierul [Link] al aplicaţiei. Mai jos putem
vedea prezentarea lor în fişier, precum şi înregistrarea lor.

Exemplu de înregistrare a claselor în cadrul fişierului de aplicaţie [Link]:

1from [Link] import admin


2from .models import Student,Car
3
[Link](Car)
[Link](Student)

Page 109 of 126


Clasele noastre sunt acum gata de scris în baza de date. Folosim funcţia de migrare pentru a introduce
date în baza de date. Migraţiile în Django sunt responsabile pentru aplicarea modificărilor la modele.
Pentru început, vom aplica un fişier [Link] la baza de date, un fişier de înlocuire pentru
administratorul Django care configurează şi un DJANGO_SETTINGS_MODULE care are o referinţă la
fişierul [Link] al proiectului. Comanda python [Link] migrate este utilizată în acest
scop. Rezultatele acestei migraţii sunt prezentate în imaginea 10.8.

Imaginea 10.8. Prezentarea rezultatelor migrării fişierului de proiect [Link] în baza de date example
în clientul phpMyAdmin

Ultimul pas al acestui proces este aplicarea modificărilor la baza de date, respectiv crearea tabelelor pe
care le-am definit în aplicaţie. Aplicăm modificările făcute în aplicaţie folosind comanda
python [Link] makemigrations my_first_app. După aceasta, trebuie să rulăm
python [Link] migrate din nou, pentru ca aceste modificări să fie aplicate bazei de date.
Rezultatul final al întregului proces este prezentat în imaginea 10.9.

Page 110 of 126


Imaginea 10.9. Prezentarea rezultatelor de aplicare a modificărilor executate în aplicaţie asupra bazei de
date în clientul phpMyAdmin

Pentru a afişa baza de date, pe lângă clientul bazei de date, Django foloseşte şi propriul său panou de
administrare (admin) pe serverul său web, pe care îl putem accesa folosind următoarea cale:

localhost:8000/admin

Prin deschiderea acestei căi, ajungem la pagina de autentificare a administratorului Django (imaginea
10.10.).

Imaginea 10.10. Pagina de Log in (autentificare) în Django administration

Pentru a accesa această pagină, trebuie să creăm un superuser, adică un utilizator care va avea toate
privilegiile legate de manipularea datelor din baza de date. Acest utilizator este creat folosind
comanda python [Link] createsuperuser din directorul proiectului. Comanda dată în
acest mod ne va cere informaţii despre numele de utilizator dorit, adresa de e-mail şi parolă. Odată
introduse, informaţiile vor crea superutilizatorul nostru în baza de date a utilizatorilor. Folosim
informaţiile introduse în acest mod pentru a accesa panoul (imaginea 10.11.).

Page 111 of 126


Imaginea 10.11. Prima pagină a panoului de administrare Django

Folosind butonul + lângă numele tabelelor, vom adăuga un student şi apoi o maşină care va lua obiectul
acelui student ca atribut Student, adică se va referi la el.

Imaginea 10.12. Adăugarea obiectelor în tabel, folosind panoul de administrare Django

Modificările din acest panou vor fi actualizate automat în clientul phpMyAdmin, aşa cum putem vedea
în imaginea 10.13.

Page 112 of 126


Imaginea 10.13. Prezentarea actualizării automate a datelor în clientul bazei de date phpMyAdmin

După cum putem vedea în imaginea 10.13., maşina are o referinţă la student, pe care o stochează în
coloana student_id. Cu alte cuvinte, această maşină este relaţionată exact de un student care, în cazul
nostru, este Sam Garcia, a cărui cheie primară sau id are valoarea 2. Cardinalitatea definită în tabelul
nostru este că, dacă un student nu există, nu poate exista nici maşina. Din acest motiv, nu este permisă
ştergerea studentului, atât timp cât există o maşină care are o referinţă la acesta. În continuare este
prezentată o eroare provocată de ştergerea studentului la care se referă maşina (imaginea 10.14.).

Imaginea 10.14. Eroarea provocată din încercarea ştergerii studentului la care maşina are referinţă

Page 113 of 126


3.3 Utilizarea cadrului SQLAlchemy

Rezumat
• SQLAlchemy este un instrument SQL popular, cunoscut datorită mapării obiectual-relaţionale
(ORM), cu care clasele pot fi mapate la o bază de date.
• SQLAlchemy foloseşte un sistem dialectal pentru a comunica cu diferite tipuri de implementări
DB-API şi baze de date relaţionale, cum ar fi MS SQL, MySQL, Oracle SQL, PostgreSQL, SQLite,
SyBase şi Firebird.
• Expression Language este un limbaj de expresie folosit ca sistem de reprezentare a structurilor
şi expresiilor bazelor de date relaţionale folosind construcţiile Python.
• ORM este un model abstract de utilizare a limbajului de expresie.
• Engine este o clasă pentru stabilirea surselor de conexiune şi a comportamentului bazei de date.
Conectează baza conexiunii şi dialectul. Se foloseşte la compilarea codului în limbajul bazei de
date SQL.
• Pentru conectarea la baza de date în SQLAlchemy, se utilizează metoda create_engine(), care,
ca parametru, primeşte adresa URL de conexiune în care dialectul este definit.
• MetaData este o clasă care asociază datele cu o schemă; scopul său este stocarea tabelelor.
• Table este o clasă utilizată pentru crearea tabelelor. Aceasta, ca parametri, ia numele tabelului,
metadatele şi obiectele coloană.
• Column este o clasă utilizată pentru crearea coloanelor. Ca parametri ia numele coloanei, tipul
de date şi constrângerile.
• Pentru crearea datelor şi stocarea lor în metadate, apelăm funcţia obligatorie create_all(engine).
• Pentru conectarea la baza de date, folosim funcţia connect(), pe care o plasăm în variabila conn.
• Pentru execuţia interogărilor în bazele de date, folosim funcţia execute(), care ca parametru
primeşte o interogare. Rezultatul acestei execuţii are aceeaşi funcţionalitate ca şi cursorul şi este
de dorit să-l salvăm într-o variabilă result.
• SQLAlchemy susţine patru operaţiuni CRUD de bază (insert, select, update şi delete).
• Apelarea operaţiunilor CRUD în modulul SQLAlchemy se face printr-un obiect de tabel. Sintaxa
utilizată pentru apelarea operaţiunilor CRUD este următoarea:

o inserare - insert().values();
o selectare - select().where();
o actualizare - update().where().values();
o ştergere - delete().where().

• Clasa de bază este clasa care stochează catalogul claselor şi tabelele mapate în sistemul de
declaraţii. Aceasta se creează apelând declarative_base() şi se transmite la clasele definite pe
care dorim să le mapăm.
• Crearea metadatelor în clasa de bază se face prin metadata.create_all(engine).
• Session este o clasă de sesiune care este definită utilizând o metodă de sesiune configurabilă
care este legată de un obiect engine creat anterior. Session = sessionmaker (bind = engine).
Sesiunea începe prin apelarea constructorului session = Session().
• Pentru a insera un obiect nou într-o sesiune, folosim metoda add(), căreia îi transmitem obiectul,
apoi folosim [Link]() pentru modificări permanente în baza de date. Funcţia add_all()
adaugă toate obiectele din listă.
• Pentru a aplica interogări pe baza de date, folosim Query(mappedClass, session) sau apelarea
echivalentă a metodei query(mapped class). Prin adăugarea funcţiei .all() la această instrucţiune,
se preiau toate rândurile unei clase date.

SQLAlchemy este un instrument SQL popular şi este ORM (object-relational mapper). Este un software
de tip open source şi multiplataform, scris în Python şi publicat sub licenţa MIT. SQLAlchemy este
cunoscut pentru maparea sa obiectual-relaţională (ORM), prin care clasele pot fi mapate într-o bază de

Page 114 of 126


date, permiţând astfel modelului obiect şi schemei bazei de date să evolueze într-un mod pur şi separat
de la început.

Pe măsură ce dimensiunea şi performanţa bazelor de date SQL devine din ce în ce mai importante, ele
se comportă mai puţin ca nişte colecţii de obiecte. Pe de altă parte, pe măsură ce abstractizarea în
colecţiile de obiecte devine validă, ele se comportă mai puţin ca tabele şi rânduri. Scopul SQLAlchemy
este adaptarea ambelor principii. Din acest motiv, a adoptat un model de mapare a datelor, mai degrabă
decât un model de înregistrare activ utilizat de un număr de alte ORM-uri.

Pentru a utiliza SQLAlchemy, este necesară instalarea sa, care se efectuează cu comanda pip install
sqlalchemy în linia de comandă. Pentru a verifica dacă instalarea a avut succes, vom importa acest
modul în mediul nostru de comandă Python şi vom apela comanda de interogare a versiunii (imaginea
11.1).

Imaginea 11.1. Verificarea instalării modulului SQLALchemy

SQLAlchemy foloseşte un sistem dialectal pentru a comunica cu diferite tipuri de implementări DB-API şi
cu bazele de date relaţionale, cum ar fi MS SQL, MySQL, Oracle SQL, PostgreSQL, SQLite, SyBase şi
Firebird. După cum am menţionat în lecţia Manipularea şi salvarea conexiunilor, specificaţia DB-API este
interfaţa standard pentru bazele de date Python. Toate dialectele necesită instalarea driverului DB-API
corespunzător.

SQLAlchemy-ul nativ acceptă programul de redare SQL (clasa Engine), integrarea interfeţei DB-API,
integrarea tranzacţiilor şi a schemelor de date. Acesta foloseşte un limbaj de expresie (expression
language) care se aplică schemei de date, în timp ce ORM-ul său este mai orientat pe domeniu. Cu alte
cuvinte, Expression Language este una dintre componentele de bază ale SQLAlchemy, care este
valabilă pentru sistemul de reprezentare a structurilor şi expresiilor bazelor de date relaţionale, care
utilizează construcţii Python, spre deosebire de ORM, care reprezintă un model de utilizare la nivel înalt
şi abstract de aplicare, care este şi singur o utilizare aplicată a limbajului de expresie. Comenzile
limbajului de expresie sunt compilate în interogările SQL corespunzătoare din programul de redare
SQLAlchemy.

Conexiunea

Clasa Engine, menţionată anterior în acest modul, se utilizează la conectarea modulului SQLAlchemy
la baza de date. Pentru a stabili sursa conexiunii şi comportamentul bazei de date, clasa Engine
conectează baza de date a conexiunii şi dialectul.

Notă

Un pool de conexiuni este un set de conexiuni stocate în cache, astfel încât conexiunile să poată fi reutilizate
atunci când baza de date primeşte cereri în viitor. După stabilirea conexiunii, aceasta este plasată în baza de
date şi utilizată din nou, astfel încât să nu fie necesară stabilirea unei conexiuni noi de fiecare dată. Acest
mecanism este utilizat pentru a îmbunătăţi execuţia comenzilor în baza de date.

Page 115 of 126


Un obiect al clasei Engine este instanţiat apelând metoda create_engine(). Această funcţie ia baza
de date pentru a fi creată ca argument. Forma standard de apelare a acestei funcţii necesită
redirecţionarea bazei de date într-un format URL care reprezintă date de tip string şi conţine informaţiile
necesare despre dialectul bazei de date şi detaliile conexiunii. Parametrul echo este o comandă rapidă
pentru configurarea autentificării, care se realizează prin modulul standard de înregistrare (logging)
Python. Când acest parametru este setat la Yes, înseamnă că rezultatele tuturor interogărilor executate
vor fi afişate pe ecran. Urmează exemple de conexiuni la baza de date SQLite şi MySQL în modulul
SQLAlchemy.

Exemplu de conexiune la baza de date SQLite folosind funcţia create_engine() a


modulului SQLAlchemy:

from sqlalchemy import create_engine


engine = create_engine('sqlite:///[Link]', echo = True)

Exemplu de conexiune la baza de date MySQL folosind funcţia create_engine() a


modulului SQLAlchemy:

from sqlalchemy import create_engine


engine = create_engine("mysql://user:pwd@localhost/example",echo = True)

Forma adresei URL transmisă sub forma unui string reprezintă interfaţa DB-API utilizată de baza de date.
Pentru a înţelege mai uşor modul în care sunt definiţi parametrii în acest link, mai jos vă prezentăm
structura generală:

dialect[+driver]://user:password@host/dbname

Crearea unei baze de date pentru toate DBMS-urile acceptate de SQLAlchemy se poate vedea la linkul
oficial al documentaţiei SQLAlchemy. Mai jos sunt câteva dintre cele mai importante funcţii ale clasei
Engine (tabelul 11.1).

Funcţiile clasei Engine în modulul SQLAlchemy


Funcţie Descriere
connect() returnează obiectul conexiunii
execute() execută comanda SQL

afişează managerul de context care oferă conexiunea la tranzacţia stabilită;


după o operaţiune reuşită, tranzacţia este executată, altfel este returnată
begin()
dispose() anulează baza de conexiune creată de Engine
driver() numele driverului de dialect utilizat de Engine
table_names() afişează o listă cu toate numele tabelelor disponibile în baza de date
transaction() execută funcţia atribuită în limitele tranzacţiei

Tabelul 11.1. Funcţiile clasei Engine în modulul SQLAlchemy

Crearea tabelelor

SQL Expression Language execută comenzile sale în conformitate cu coloanele din tabel.
Obiectul Column al acestui modul reprezintă o coloană din tabelul bazei de date, care la rândul său este
reprezentat de obiectul Table. Tabelele definite şi obiectele asociate precum indexuri, vizualizări,

Page 116 of 126


declanşatoare etc. reprezintă o colecţie de metadate. De aici şi obiectul clasei MetaData, al cărui rol
este să stocheze o colecţie de obiecte de tabel, precum şi conexiunea opţională la maşină (Engine) sau
conexiune (Connection).

Mai întâi, trebuie să instanţiem obiectul metadata. Apelarea constructorului MetaData() creează o
nouă instanţă a acestui obiect, al cărui scop este conectarea datelor la schemă. Dacă parametrii nu sunt
transmişi către constructor, valoarea lor va fi None în mod implicit. Vom salva apoi instanţa într-o
variabilă.

Următorul pas este crearea unui tabel, care primeşte metadate ca parametri, numele tabelului care se
va crea şi obiectele coloanelor. Obiectele coloanelor conţin numele coloanei, tipul de date şi
constrângerea asupra acestor date. O listă a tipurilor de date generice susţinute în SQLAlchemy poate
fi verificată aici. Pentru a utiliza aceste tipuri de date, trebuie să introduceţi clasele lor în program.
Tabelul creat trebuie salvat într-o variabilă.

Ultimul pas în procesul de creare a unui tabel este apelarea metodei createall() a obiectului
metadata, care ia ca parametru obiectul engine, al cărui rol este să creeze date şi să le stocheze în
metadate. Următorul exemplu ne ilustrează cum arată procesul de creare a unui tabel student într-o
bază de date SQLite. Deoarece am setat parametrul echo al funcţiei create la True, pe ecran se va afişa
interogarea executată în limbajul de interogare SQL.

Exemplu de creare a unui tabel în baza de date SQLite, folosind modulul SQLAlchemy:

1from sqlalchemy import create_engine, MetaData, Table, Column,


2 Integer, String
3
4 engine = create_engine('sqlite:///[Link]', echo = True)
5 meta = MetaData()
6
7 students = Table(
8 'students', meta,
9 Column('id', Integer, primary_key = True),
10 Column('name', String),
11 Column('address', String),
12 )
13
14 meta.create_all(engine)

Imaginea 11.2. Prezentarea rezultatelor de execuţie a comenzii SQLAlchemy în formatul SQL

Page 117 of 126


Execuţia comenzilor SQL

Comenzile SQL sunt executate folosind metode adecvate pentru crearea interogărilor, în raport cu
obiectul de tabelul ţintă. Aceste comenzi includ operaţiuni CRUD - inserare, selectare, actualizare şi
ştergere. Apelăm execuţia lor în modulul SQLAlchemy prin metodele cu acelaşi nume. Toate comenzile
din SQLAlchemy sunt apelate printr-un obiect de tabel. În exemplul nostru, apelarea comenzii de
inserare a datelor ar arăta astfel:

query = [Link]()

Această comandă înlocuieşte instrucţiunea SQL clasică de mai jos, care este, de fapt, echivalentul ei, care
poate fi confirmat de funcţia str(ins), care, ca parametru, primeşte interogarea noastră în baza de
date şi o afişează în formatul limbajului clasic de interogare SQL:

'INSERT INTO students (student_id, name, address)


VALUES (:student_id, :name, :address)'

Pentru inserarea valorilor specifice, apelăm metoda values() din obiectul pe care dorim să îl inserăm.

query = [Link]().values(name = 'Bob Johnson')

'INSERT INTO students (name) VALUES (:name)'

În acest caz, valoarea specifică a parametrului nu este afişată. În schimb, SQLAlchemy generează
parametri asociaţi care sunt vizibili în forma compilată a expresiei:

[Link]().params

{'name': 'Bob Johnson'}

Pentru ca interogările afişate să fie executate, trebuie să avem o sursă de conexiune DB-API verificată
activ, pe care o realizăm cu comanda [Link](). Folosind conexiunea stabilită, după
introducerea datelor în baza de date, vom apela metoda de execuţie - execute(), care va face
modificări permanente în baza de date:
1conn = [Link]()
2query = [Link]().values(
3name = 'Bob Johnson', address = 'Green Avenue 2331')
4result = [Link](ins)
5
6INSERT INTO students (name, lastname) VALUES (?, ?)
7('Bob Johnson', 'Green Avenue 2331')
8COMMIT

Urmează un exemplu al modului în care adăugăm un nou student în tabelul nostru de studenţi.

Exemplu de inserare a datelor în tabelul bazei de date SQLite, folosind modulul SQLAlchemy:

1query = [Link]()
2query = [Link]().values(name = 'Bob Johnson',
3 address = 'Green avenue 2331')
4conn = [Link]()
5result = [Link](query)

Page 118 of 126


Am văzut cum arată un exemplu de inserare a unui singur rând într-un tabel al bazei de date. Dacă am
dori să inserăm mai multe înregistrări simultan, am transmite datele ca o listă de dicţionare de date, care
ar arăta ca în exemplul următor.

Exemplu de inserare a mai multor rânduri de date într-un tabel de baze de date SQLite utilizând
modulul SQLAlchemy:

1 [Link]([Link](), [
2 {'name':'Jessica Sims', 'address' : '5th Avenue 27'},
3 {'name':'Adam Benson','address' : 'Wallstreet 147'},
4 {'name':'Melissa Jenkins','address' : 'Boulevard 654'},
5 {'name':'Kevin Robertson','address' : '6th Avenue 103'},
6])

Pentru afişarea datelor, folosim metoda select() pentru crearea interogărilor:

query = [Link]()
'SELECT [Link], [Link], [Link] FROM students'

Pentru execuţia permanentă a modificărilor asupra bazei de date, ca şi la metoda insert, folosim
funcţia execute().

result = [Link](query)
SELECT [Link], [Link], [Link]
FROM students

Dacă acordăm atenţie, vom observa că variabila result pe care am definit-o, acum a obţinut, de fapt,
rolul de cursor. Prin urmare, prin intermediul acestei variabile, putem să apelăm funcţia fetchone(),
care returnează rezultatul următorului rând de interogare, în timp ce vizualizarea tuturor rândurilor este
posibilă utilizând bucla for. În continuare, prezentăm aplicarea interogărilor pentru afişarea datelor
într-o bază de date SQLite.

Exemplu de afişare a datelor dintr-un tabel de baze de date SQLite utilizând modulul SQLAlchemy:

1query = [Link]()
2conn = [Link]()
3result = [Link](query)
4
5for row in result:
6 print (row)

Datele din tabel pot fi, de asemenea, afişate pe baza condiţilor de afişare date, care sunt definite de
funcţia where(), căreia îi sunt transmise aceste condiţii. Mai jos vom arăta cum am cere programului
să returneze doar studenţii al căror nume este Melissa Jenkins. Atributul c din
parametrul [Link] reprezintă aliasul coloanei.

Page 119 of 126


Exemplu de afişare a datelor + dintr-un tabel de baze de date SQLite cu o condiţie dată, utilizând
modulul SQLAlchemy:

1conn = [Link]()
2query = [Link]().where([Link]=='Melissa Jenkins')
3result = [Link](query)
4
5for row in result:
6 print (row)

Rezultat: (4, 'Melissa Jenkins', 'Boulevard 654')


Pentru actualizarea datelor din baza de date, utilizăm următoarea sintaxă:

[Link]().where(conditions).values(SET expressions)

Metoda de interogare update() este apelată din tabelul peste care dorim să realizăm actualizarea. În
cadrul metodei de actualizare, stabilim condiţia pentru schimbarea datelor sub forma parametrilor
funcţiei where() şi noile valori ale datelor sub forma parametrilor funcţiei values(). Aşa ar arăta un
exemplu de actualizare a adresei unui student.
Exemplu de actualizare a datelor dintr-un tabel al bazei de date SQLite, utilizând modulul
SQLAlchemy:

1conn = [Link]()
2
3query = [Link]().where(
[Link] == 'Wallstreet 147').values(address = 'Floorstreet 5')
5
[Link](query)

'UPDATE students SET address = :address


WHERE [Link] = :address_1'

O altă metodă de interogare susţinută de SQLAlchemy este delete(), care se execută în acelaşi mod
ca şi celelalte, apelând-o dintr-un obiect al tabelului. Aşa arată apelarea metodei de ştergere în
SQLAlchemy şi echivalentul acesteia în limbajul de interogare SQL:

stmt = [Link]()
'DELETE FROM students'

Această metodă de interogare poate accepta, de asemenea, condiţia în care va fi efectuată operaţiunea
de ştergere şi face acest lucru acceptând condiţia ca parametru al metodei where().

Exemplu de ştergere a datelor dintr-un tabel de baze de date SQLite utilizând modulul SQLAlchemy:
1conn = [Link]()
2
3query = [Link]().where([Link] == 'Jessica Sims')
4
[Link](query)

'DELETE FROM students WHERE [Link] = :name_1'

Page 120 of 126


Urmează o reprezentare sumară, un rezumat al tuturor comenzilor care au fost executate în baza noastră
de date. Pe baza rezultatului ataşat, concluzionăm că modificările de bază au fost aplicate cu succes.

Rezultat: [(1, 'Bob Johnson', 'Green avenue 2331'), (3, 'Adam Benson',
'Floorstreet 5'), (4, 'Melissa Jenkins', 'Boulevard 654'), (5, 'Kevin
Robertson', '6th Avenue 103')]

Reprezentare sumară a utilizării operaţiunilor CRUD asupra bazei de date SQLite în modulul
SQLAlchemy

1from sqlalchemy import create_engine, MetaData, Table, Column,


2Integer, String
3
4engine = create_engine('sqlite:///[Link]', echo = True)
5meta = MetaData()
6
7students = Table(
8 'students', meta,
9 Column('id', Integer, primary_key = True),
10 Column('name', String),
11 Column('address', String),
12)
13
14meta.create_all(engine)
15
16query1 = [Link]()
17query1 = [Link]().values(name = 'Bob Johnson',
18 address = 'Green avenue 2331')
19
20conn = [Link]()
21
[Link](query1)
23
[Link]([Link](), [
25 {'name':'Jessica Sims', 'address' : '5th Avenue 27'},
26 {'name':'Adam Benson','address' : 'Wallstreet 147'},
27 {'name':'Melissa Jenkins','address' : 'Boulevard 654'},
28 {'name':'Kevin Robertson','address' : '6th Avenue 103'},
29])
30
31query2 = [Link]().where(
[Link] == 'Wallstreet 147').values(address = 'Floorstreet 5')
33
[Link](query2)
35
36query3 = [Link]().where([Link] == 'Jessica Sims')
37
[Link](query3)
39
40s = [Link]()
41data = [Link](s).fetchall()
42print(data)

Page 121 of 126


SQLAlchemy ORM

Scopul principal al SQLAlchemy ORM este de a face mai uşoară conectarea claselor Python definite de
utilizator cu tabelele bazei de date şi a obiectelor acelor clase cu rândurile din tabelele corespunzătoare.
SQLAlchemy ORM este realizat deasupra limbajului de instrucţiuni SQL. Este, de fapt, un model de
utilizare înalt şi abstract, adică este aplicată utilizarea limbajului de expresie. Deşi o aplicaţie reuşită se
poate construi folosind exclusiv maparea obiectual-relaţională, uneori, o aplicaţie creată cu ORM poate
folosi direct limbajul de expresie în care sunt necesare anumite interacţiuni cu baza de date.
În ORM, procesul de configurare începe prin descrierea tabelelor bazei de date şi apoi prin definirea
claselor care vor fi mapate la acele tabele. În SQLAlchemy, aceste două sarcini sunt efectuate simultan.
Acest lucru se realizează prin utilizarea unui sistem de declaraţii, în care clasele create includ directive
pentru descrierea tabelului real al bazei de date în care sunt mapate. Clasa care stochează catalogul
claselor şi tabelele mapate în sistemul de declaraţie, se numeşte clasă declarativă sau de bază.
Funcţia declarative_base() este utilizată pentru a o crea. Această funcţie este definită în
modulul [Link]. Odată ce clasa de bază este declarată, orice număr de
clase mapate poate fi definit din aceasta.
Ca şi până acum, pentru a începe să lucrăm cu baza de date, avem nevoie de o conexiune la aceasta. Pe
lângă modulul declarativ, vom introduce şi module de tip date în programul nostru, precum şi modulul
engine. Mai departe în program, vom defini clasa Students, care conţine un tabel în care sunt mapate
numele şi tipurile de date ale coloanelor din acesta. Apoi, folosind funcţia create_all() din
metadatele clasei de bază, vom ordona crearea unui tabel definit.

Exemplu de creare a unei clase de bază şi maparea acesteia la o bază de date utilizând SQLAlchemy
ORM:

1from sqlalchemy import Column, Integer, String


2from sqlalchemy import create_engine
3from [Link] import declarative_base
4
5engine = create_engine('sqlite:///example_orl.db', echo = True)
6Base = declarative_base()
7
8class Students(Base):
9 __tablename__ = 'students'
10
11 id = Column(Integer, primary_key = True)
12 name = Column(String)
13 address = Column(String)
14
[Link].create_all(engine)
Pentru a putea să comunicăm cu baza de date, trebuie să obţinem managerul acesteia. Această
funcţionalitate este furnizată de obiectul sesiunii. Clasa Session se defineşte utilizând metoda sesiunii
configurabile, numită sessionmaker(), care este asociată cu un obiect engine creat anterior.
Sesiunea creată în acest mod foloseşte apoi un constructor gol.

Exemplu de creare a unei sesiuni utilizând SQLAlchemy ORM:

1from [Link] import sessionmaker


2
3Session = sessionmaker(bind = engine)
4session = Session()
Urmează o prezentare generală a unora dintre cel mai frecvent utilizate funcţii de sesiune:

Page 122 of 126


Funcţiile clasei Session în SQLAlchemy ORM
Funcţie Descriere
begin() începe tranzacţia în această sesiune
add() setează obiectul în sesiune
add_all() adaugă o colecţie de obiecte la sesiune
commit() execută toate tranzacţiile, inclusiv cele în curs de desfăşurare
delete() şterge tranzacţia
execute() execută o instrucţiune SQL
expire() marchează atributul de instanţă ca nevalid, expirat
flush() aplică toate modificările la baza de date
invalidate() închide conexiunea
rollback() întrerupe procesul de tranzacţie şi readuce baza de date la starea iniţială
close() închide sesiunea curentă şi toate procesele în curs de desfăşurare

Tabelul 11.2. Funcţiile clasei Session în modulul SQLAlchemy

Deoarece am creat o sesiune şi am declarat clasa de studenţi care este mapată în tabel, următorul pas
este să adăugăm obiecte din această clasă, folosind metoda add() a obiectului de sesiune. În cadrul
acestei metode, trebuie să se atribuie valori coloanelor tabelului sub formă de parametri şi apoi să se
plaseze obiectul definit în acest fel într-o variabilă. Vom transmite apoi variabila obiect ca parametru al
funcţiei commit(), care va scrie permanent acest obiect în baza noastră de date.

Exemplu de adăugare a unui rând într-un tabel folosind SQLAlchemy ORM:

1s1 = Students(name = 'Bob Johnson', address = 'Green Avenue 2331')


[Link](s1)
[Link]()

Modulul SQLAlchemy vă permite să adăugaţi mai multe rânduri într-un tabel în acelaşi timp, utilizând
funcţia add_all(), a cărei aplicare se poate vedea în exemplul următor.

Exemplu de adăugare a mai multor rânduri într-un tabel, utilizând SQLAlchemy ORM:

1session.add_all([
2 Students(name='Jessica Sims', address='5th Avenue 27'),
3 Students(name='Adam Benson',address='Wallstreet 147'),
4 Students(name='Melissa Jenkins',address='Boulevard 654'),
5 Students(name='Kevin Robertson',address='6th Avenue 103')]
6)
7
[Link]()

Toate interogările SELECT generate de SQLAlchemy ORL construiesc un obiect de interogare, Query.
Fiecare apelare a interogării generează un obiect nou care reprezintă o copie a obiectului anterior,
modificat pe baza criteriilor de interogare date.

Obiectele de interogare sunt generate iniţial în metoda query(), care poate fi apelată dintr-un obiect
de sesiune. O prezentare generală a utilizării obiectului de interogare ar fi:

Page 123 of 126


q = [Link](mapped class)

iar expresia sa echivalentă este:

q = Query(mappedClass, session)

Pentru a returna lista obiectelor din tabel, utilizăm funcţia all(). Mai jos vom arăta cum folosim această
funcţie pentru listarea listei noastre de studenţi.

Exemplu de afişare a rândurilor din tabel, folosind SQLAlchemy ORM:

1from [Link] import sessionmaker


2Session = sessionmaker(bind = engine)
3session = Session()
4result = [Link](Students).all()
5
6for row in result:
7 print ("Name: ",[Link], "Address:",[Link])

Imaginea 11.3. Rezultatele afişării rândurilor din tabel în cadrul sesiunii în modulul SQLAlchemy

Funcţiile clasei Query în SQLAlchemy ORM


Funcţie Descriere
add_columns() adaugă una sau mai multe coloane la lista de rezultate care trebuie returnate
add_entity() adaugă entitatea mapată la lista de rezultate care trebuie returnate
count() returnează numărul de rânduri de interogare
delete() şterge rândurile obţinute prin interogare
distinct() returnează numai valori diferite din interogare
filter() aplică criteriul de filtrare dat asupra copiei interogării

first() returnează primul rând de rezultate ale interogării; dacă interogarea nu


returnează nicio valoare, se va lista Null

get() returnează o instanţă bazată pe un identificator de cheie primară, oferind acces


direct la harta de identitate a sesiunii pe care o deţine
group_by() aplică criterii de grupare a datelor şi returnează date noi
join() joncţionează tabelele pe baza unei interogări date
one() returnează exact un rezultat sau raportează o eroare
order_by() aplică criteriul de sortare
update() actualizează interogările care corespund criteriilor specificate

Tabelul 11.3. Funcţiile clasei Query în modulul SQLAlchemy

Page 124 of 126


Toate funcţionalităţile pe care le-am aplicat în legătură cu SQLAlchemy ORL vor fi rezumate în exemplul
de mai jos, conform ordinii de execuţie a acestora.

Reprezentare sumară a utilizării funcţionalităţilor de bază SQLAlchemy ORL:

1from sqlalchemy import Column, Integer, String


2from sqlalchemy import create_engine
3from [Link] import declarative_base
4
5engine = create_engine('sqlite:///example_orl.db', echo = True)
6Base = declarative_base()
7
8class Students(Base):
9 __tablename__ = 'students'
10
11 id = Column(Integer, primary_key = True)
12 name = Column(String)
13 address = Column(String)
14
[Link].create_all(engine)
16
17from [Link] import sessionmaker
18
19Session = sessionmaker(bind = engine)
20session = Session()
21
22s1 = Students(name = 'Bob Johnson', address = 'Green Avenue 2331')
[Link](s1)
24
25
26session.add_all([
27 Students(name='Jessica Sims', address='5th Avenue 27'),
28 Students(name='Adam Benson',address='Wallstreet 147'),
29 Students(name='Melissa Jenkins',address='Boulevard 654'),
30 Students(name='Kevin Robertson',address='6th Avenue 103')]
31)
32
[Link]()
34
35result = [Link](Students).all()
36
37for row in result:
38 print ("Name: ",[Link], "Address:",[Link])

Exerciţiu

Folosind cadrul SQLAlchemy şi baza de date SQLite, creaţi un tabel Playlist care va conţine următoarele
coloane:

• id - cheie primară;
• author - text;
• song - text.

Page 125 of 126


În tabelul creat, trebuie să asiguraţi introducerea informaţiilor în coloanele existente din linia de
comandă, utilizând funcţia input().

După introducerea cântecului, trebuie să se afişeze toate rezultatele.

Soluţie:

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String


1
2engine = create_engine('sqlite:///[Link]', echo = False)
3meta = MetaData()
4
5playlist = Table(
6 'playlist', meta,
7 Column('id', Integer, primary_key = True),
8 Column('author', String),
9 Column('song', String),
10)
11
12meta.create_all(engine)
13
14song_id = input("Enter song id: ")
15song_author = input("Enter author: ")
16song_name = input("Enter song: ")
17
18query = [Link]().values(id = song_id, author= song_author, song =
19song_name)
20
21query2 = [Link]()
22
23conn = [Link]()
24r1 = [Link](query)
25result = [Link](query2)
26
27for row in result:
28 print("Author: {} | Song: {}".format(row[1], row[2]))

Explicaţie:

Primul pas presupune includerea modulelor SQLAlchemy necesare. După aceea, trebuie să creăm un
tabel folosind constructorul Table şi metoda create_all a obiectului MetaData. Apoi, folosind
funcţia input(), putem cere utilizatorului să introducă date şi să le salveze. Când toate datele sunt
completate, folosind metoda insert a variabilei playlist, care conţine tabelul nou creat, creăm o
interogare insert şi salvăm datele cântecului introdus. Pe lângă metoda insert, folosim şi
metoda select pentru crearea interogărilor pentru a afişa toate datele. La sfârşit, trebuie să executăm
interogările menţionate, folosind metoda execute a obiectului de conexiune, iar apoi, prin buclă să
trecem prin lista de rezultate ale celei de-a doua interogare şi după aceea afişăm toate cântecele.

Page 126 of 126

S-ar putea să vă placă și