1. Schema bazei de date — doua tabele relationale
- studenti — id, nume, clasa (date despre persoana)
- note — id, student_id, materie, nota (date despre evaluare)
note.student_idreferastudenti.id— evita redundanta numelui
Foaia Studenti are un numar unic per rand (ID). Foaia Note contine acel numar in loc sa repete numele. Daca studentul isi schimba numele, il schimbi o singura data in foaia Studenti — notele raman corecte automat. Aceasta este esenta normalizarii.
import sqlite3 conn = sqlite3.connect('catalog.db') cur = conn.cursor() cur.execute('''CREATE TABLE IF NOT EXISTS studenti ( id INTEGER PRIMARY KEY AUTOINCREMENT, nume TEXT NOT NULL, clasa TEXT NOT NULL )''') cur.execute('''CREATE TABLE IF NOT EXISTS note ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, materie TEXT NOT NULL, nota REAL NOT NULL, FOREIGN KEY(student_id) REFERENCES studenti(id) )''') conn.commit()
2. INSERT si SELECT — adaugare si interogare
?) ca sa prevenim SQL injection.# INSERT cu executemany — eficient pentru date in lot studenti = [ ('Ana Ionescu', 'XII-A'), ('Mihai Pop', 'XII-A'), ('Elena Radu', 'XII-B'), ('Bogdan Stan', 'XII-B'), ] cur.executemany( 'INSERT INTO studenti (nume, clasa) VALUES (?, ?)', studenti ) conn.commit() # SELECT cu WHERE si ORDER BY print('--- Studenti din XII-A ---') for row in cur.execute( 'SELECT id, nume FROM studenti WHERE clasa = ? ORDER BY nume', ('XII-A',)): print(row)
--- Studenti din XII-A --- (1, 'Ana Ionescu') (2, 'Mihai Pop')
"WHERE nume = '" + x + "'"). Foloseste intotdeauna parametri ? — SQLite le escapeaza automat.3. UPDATE si DELETE — modificare si stergere selectiva
# INSERT note pentru studenti note = [ (1, 'Informatica', 9.5), (2, 'Informatica', 4.8), (3, 'Informatica', 7.2), (4, 'Informatica', 8.0), ] cur.executemany( 'INSERT INTO note (student_id, materie, nota) VALUES (?, ?, ?)', note ) conn.commit() # UPDATE — corecteaza nota unui student cur.execute( 'UPDATE note SET nota = ? WHERE student_id = ? AND materie = ?', (5.5, 2, 'Informatica') ) conn.commit() row = cur.execute( 'SELECT nota FROM note WHERE student_id = 2' ).fetchone() print(f'Dupa UPDATE: nota Mihai Pop = {row[0]}') # DELETE — sterge notele sub 5 cur.execute('DELETE FROM note WHERE nota < 5') conn.commit() cnt = cur.execute('SELECT COUNT(*) FROM note').fetchone()[0] print(f'Note ramase dupa DELETE(nota<5): {cnt}')
Dupa UPDATE: nota Mihai Pop = 5.5 Note ramase dupa DELETE(nota<5): 3
4. JOIN — interogarea datelor din doua tabele
# INNER JOIN — studenti cu notele lor la Informatica print('--- Raport note Informatica ---') for row in cur.execute(''' SELECT s.nume, s.clasa, n.nota FROM studenti s JOIN note n ON s.id = n.student_id WHERE n.materie = 'Informatica' ORDER BY n.nota DESC '''): print(row) # Functii agregate: AVG si COUNT avg, cnt = cur.execute(''' SELECT AVG(n.nota), COUNT(*) FROM studenti s JOIN note n ON s.id = n.student_id WHERE n.materie = 'Informatica' ''').fetchone() print(f'Media Informatica: {avg:.2f} (din {cnt} note)')
--- Raport note Informatica ---
('Ana Ionescu', 'XII-A', 9.5)
('Elena Radu', 'XII-B', 7.2)
('Mihai Pop', 'XII-A', 5.5)
Media Informatica: 7.40 (din 3 note)s, n) — prescurtari pentru nume de tabele in interogari lungi. AVG() si COUNT() sunt functii agregate: calculeaza o singura valoare din mai multe randuri.5. Aplicatia completa — clasa CatalogDB
import sqlite3, os class CatalogDB: def __init__(self, db_path): self.conn = sqlite3.connect(db_path) self.conn.row_factory = sqlite3.Row # acces prin nume coloana self._creare_tabele() def _creare_tabele(self): self.conn.executescript(''' CREATE TABLE IF NOT EXISTS studenti ( id INTEGER PRIMARY KEY AUTOINCREMENT, nume TEXT NOT NULL, clasa TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS note ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, materie TEXT NOT NULL, nota REAL NOT NULL, FOREIGN KEY(student_id) REFERENCES studenti(id) ); ''') def adauga_student(self, nume, clasa): cur = self.conn.execute( 'INSERT INTO studenti (nume, clasa) VALUES (?, ?)', (nume, clasa)) self.conn.commit() return cur.lastrowid # ID-ul generat def adauga_nota(self, student_id, materie, nota): self.conn.execute( 'INSERT INTO note (student_id, materie, nota) VALUES (?, ?, ?)', (student_id, materie, nota)) self.conn.commit() def raport(self, materie): return self.conn.execute(''' SELECT s.nume, s.clasa, n.nota FROM studenti s JOIN note n ON s.id = n.student_id WHERE n.materie = ? ORDER BY n.nota DESC ''', (materie,)).fetchall() def media(self, materie): row = self.conn.execute( 'SELECT AVG(nota), COUNT(*) FROM note WHERE materie = ?', (materie,)).fetchone() return row[0], row[1] def inchide(self): self.conn.close() # === MAIN === DB = 'catalog.db' cat = CatalogDB(DB) id_ana = cat.adauga_student('Ana Ionescu', 'XII-A') id_mihai = cat.adauga_student('Mihai Pop', 'XII-A') id_elena = cat.adauga_student('Elena Radu', 'XII-B') id_bogdan = cat.adauga_student('Bogdan Stan', 'XII-B') for sid, nota in [(id_ana, 9.5), (id_mihai, 5.5), (id_elena, 7.2), (id_bogdan, 8.0)]: cat.adauga_nota(sid, 'Informatica', nota) print('=== RAPORT INFORMATICA ===') for r in cat.raport('Informatica'): status = 'PROMOVAT' if r[2] >= 5 else 'RESPINS' print(f' {r[0]:<20} {r[1]:<6} nota={r[2]:.1f} [{status}]') avg, cnt = cat.media('Informatica') print(f'Media: {avg:.2f} (din {cnt} note)') cat.inchide() os.remove(DB)
=== RAPORT INFORMATICA === Ana Ionescu XII-A nota=9.5 [PROMOVAT] Bogdan Stan XII-B nota=8.0 [PROMOVAT] Elena Radu XII-B nota=7.2 [PROMOVAT] Mihai Pop XII-A nota=5.5 [PROMOVAT] Media: 7.55 (din 4 note)
6. Normalizare si principii de proiectare EXCLUSIV INTENSIV
Normalizarea reduce redundanta si anomaliile de actualizare. Primele doua forme normale (1NF, 2NF) sunt suficiente pentru proiecte scolare.
| Anti-pattern (un singur tabel) | Normalizat (doua tabele) |
|---|---|
| Ana Ionescu, XII-A, Informatica, 9.5 | studenti(1, Ana Ionescu, XII-A) |
| Ana Ionescu, XII-A, Matematica, 8.0 | note(1, 1, Informatica, 9.5) |
| Ana Ionescu, XII-A, Romana, 7.5 | note(2, 1, Matematica, 8.0) |
| Numele si clasa repetate de 3 ori! | note(3, 1, Romana, 7.5) |
- Anomalie de actualizare: in anti-pattern, schimbarea clasei lui Ana necesita UPDATE pe toate randurile ei
- Anomalie de stergere: stergand toate notele unui student, pierzi si datele despre student
- 1NF: fiecare coloana contine o singura valoare atomica (nu liste)
- 2NF: fiecare coloana non-cheie depinde de INTREAGA cheie primara (nu partial)
- Separarea datelor: entitati distincte = tabele distincte
- Integritate referentiala: FOREIGN KEY garanteaza ca
student_idexista intotdeauna in studenti - Parametri legati (
?): previn SQL injection, intotdeauna - Incapsulare OOP: clasa
CatalogDBascunde SQL-ul — codul client nu stie cum sunt stocate datele