Baze de date
Introducerea conceptelor fundamentale privind organizarea, gestionarea și interogarea bazelor de date relaționale.
Bacalaureat
Modelul relațional — tabele, chei, integritate
O bază de date relațională organizează datele în tabele (relații). Fiecare tabel are coloane (câmpuri, atribute) — cu nume și tip de date fixe — și linii (înregistrări, tupluri) — câte una pentru fiecare entitate memorată. Software-ul care creează și administrează baza este SGBD-ul (sistem de gestiune a bazelor de date): MySQL, PostgreSQL, Oracle, SQLite. Distincția SGBD vs baza de date propriu-zisă este întrebare de teorie: baza este colecția de date, SGBD-ul este programul care o gestionează.
Cheia primară (primary key) este câmpul sau grupul de câmpuri care identifică unic fiecare înregistrare. Regulile ei: valorile sunt unice și nu pot fi NULL. Un tabel poate avea mai multe câmpuri candidate la acest rol (chei candidate), dar o singură cheie primară.
Cheia externă (foreign key) este un câmp dintr-un tabel care referă cheia primară a altui tabel, materializând legătura dintre ele. Ea impune integritatea referențială: nu poți insera o valoare care nu există în tabelul referit și nu poți șterge o înregistrare părinte cât timp există înregistrări copil care o referă (fără reguli speciale de tip cascadă).
Tipurile de relații între tabele:
- 1:1 — unei înregistrări îi corespunde cel mult una în celălalt tabel;
- 1:n — o înregistrare părinte are oricâte înregistrări copil (un client — mai multe comenzi); se implementează prin cheie externă în tabelul „n”;
- m:n — se implementează printr-un tabel asociativ (de legătură) care conține două chei externe; încercarea de a lega direct două tabele m:n este greșeala clasică de proiectare.
Definirea structurii: SQL DDL
SQL (Structured Query Language) are două familii de comenzi care se cer deosebite la examen: DDL (Data Definition Language) — definește structura — și DML (Data Manipulation Language) — lucrează cu datele.
Comenzile DDL esențiale:
- CREATE TABLE — creează un tabel, precizând pentru fiecare coloană numele, tipul de date (INT, DECIMAL, VARCHAR(n), DATE, CHAR(n)) și eventualele constrângeri: PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT, FOREIGN KEY ... REFERENCES;
- ALTER TABLE — modifică structura unui tabel existent: adaugă coloană (ADD), șterge coloană (DROP COLUMN), modifică tipul unei coloane;
- DROP TABLE — elimină complet tabelul, cu structură cu tot.
Perechea de comenzi confundată cel mai des la grile: DROP vs DELETE vs TRUNCATE. DELETE (DML) șterge înregistrări, păstrând tabelul; TRUNCATE golește rapid toate înregistrările, păstrând structura; DROP șterge tabelul însuși. Cine răspunde că DELETE fără WHERE șterge tabelul pierde punctul — tabelul rămâne, doar gol.
Exemplu de structură pentru o școală:
- tabelul elevi: id (INT, PRIMARY KEY), nume (VARCHAR(50), NOT NULL), clasa (VARCHAR(5));
- tabelul note: id (INT, PRIMARY KEY), id_elev (INT, FOREIGN KEY către elevi.id), materie (VARCHAR(30)), nota (INT), data (DATE).
Alegerea tipului de date se punctează: numerele pe care se fac calcule se țin ca tipuri numerice, nu ca text; datele calendaristice ca DATE, nu VARCHAR — altfel compararea și sortarea cronologică nu mai funcționează corect. VARCHAR(n) reține șiruri de lungime variabilă cu maximum n caractere, spre deosebire de CHAR(n), care ocupă mereu exact n.
Interogarea datelor: SELECT, WHERE, ORDER BY
Interogarea fundamentală are forma: SELECT coloane FROM tabel WHERE condiție ORDER BY criteriu. SELECT alege coloanele (proiecție), WHERE filtrează liniile (selecție) — inversarea celor două roluri este o greșeală de teorie frecventă.
- SELECT * întoarce toate coloanele; SELECT nume, clasa doar pe cele enumerate;
- SELECT DISTINCT elimină duplicatele din rezultat;
- WHERE acceptă comparații (=, <>, <, >, <=, >=), operatori logici AND, OR, NOT, intervalul BETWEEN a AND b (inclusiv capetele!), apartenența IN (listă) și potrivirea de șabloane LIKE, unde % înlocuiește oricâte caractere (inclusiv niciunul), iar _ exact un caracter: LIKE „M%” — începe cu M; LIKE „_a%” — a doua literă este a;
- valorile NULL se testează exclusiv cu IS NULL / IS NOT NULL; condiția camp = NULL nu este niciodată adevărată — capcană sigură de punctaj, pentru că NULL înseamnă „valoare necunoscută”, iar orice comparație cu necunoscutul rămâne necunoscută;
- ORDER BY sortează rezultatul: implicit ASC (crescător), explicit DESC pentru descrescător; se poate sorta după mai multe criterii, în ordinea enumerării;
- LIMIT n (în MySQL) păstrează doar primele n linii — combinat cu ORDER BY dă interogările de tip „primii 3”.
Ordinea sintactică a clauzelor este fixă: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY. Scrierea lui WHERE după ORDER BY este eroare de sintaxă.
Exemplu integrat: elevii din clasa a XII-a A cu media peste 9, în ordine alfabetică — SELECT nume FROM elevi WHERE clasa = „12A” AND media > 9 ORDER BY nume;
Funcții agregate, GROUP BY și JOIN
Funcțiile agregate calculează o valoare unică dintr-o mulțime de linii: COUNT (numărare), SUM (sumă), AVG (medie), MIN, MAX. Nuanță de punctaj: COUNT(*) numără toate liniile, în timp ce COUNT(coloana) numără doar liniile în care coloana nu este NULL.
GROUP BY împarte liniile în grupuri după valorile unei coloane, iar funcțiile agregate se calculează pe fiecare grup: SELECT clasa, AVG(media) FROM elevi GROUP BY clasa — media fiecărei clase. Regula strictă: în SELECT pot apărea doar coloanele de grupare și funcții agregate; a cere o coloană negrupată și neagregată este eroare (ce nume de elev ai afișa pentru un grup întreg?).
Distincția WHERE vs HAVING este întrebarea preferată a subiectelor: WHERE filtrează liniile înainte de grupare și nu poate conține funcții agregate; HAVING filtrează grupurile după calcularea agregatelor. „Clasele cu media peste 8”: GROUP BY clasa HAVING AVG(media) > 8 — cu WHERE nu se poate exprima.
JOIN combină linii din două tabele pe baza unei condiții de legătură, tipic cheie externă = cheie primară:
- INNER JOIN păstrează doar perechile care au corespondent în ambele tabele;
- LEFT JOIN păstrează toate liniile din tabelul stâng; unde nu există corespondent în dreapta, coloanele acestuia se completează cu NULL — varianta necesară pentru interogări de tip „inclusiv elevii fără nicio notă”.
Exemplu: SELECT e.nume, n.nota FROM elevi e INNER JOIN note n ON e.id = n.id_elev; — fiecare notă alături de numele elevului. Fără condiția ON se obține produsul cartezian: fiecare linie cu fiecare linie, rezultat umflat și fără sens — bug clasic la subiectele practice.
Actualizarea datelor și normalizarea
Comenzile DML de modificare:
- INSERT INTO tabel (coloane) VALUES (valori) — adaugă înregistrări noi; coloanele omise primesc valoarea implicită sau NULL, iar încălcarea unei constrângeri (cheie primară duplicată, NOT NULL nerespectat, cheie externă inexistentă) respinge inserarea;
- UPDATE tabel SET camp = valoare WHERE condiție — modifică înregistrările care satisfac condiția;
- DELETE FROM tabel WHERE condiție — șterge înregistrările care satisfac condiția.
Avertismentul care se cere explicit: UPDATE sau DELETE fără clauza WHERE se aplică TUTUROR înregistrărilor din tabel. Un UPDATE elevi SET media = 10 fără WHERE rescrie media tuturor — greșeala este ireversibilă fără copie de siguranță.
Normalizarea este procesul de organizare a tabelelor astfel încât să se elimine redundanța (aceeași informație memorată de mai multe ori) și anomaliile de actualizare, inserare și ștergere. Formele normale, la nivelul cerut:
- FN1: toate valorile sunt atomice — o celulă nu conține liste („mate, info, fizică” într-un singur câmp încalcă FN1);
- FN2: FN1 plus fiecare câmp nechie depinde de întreaga cheie primară, nu doar de o parte a ei (relevant la chei compuse);
- FN3: FN2 plus fără dependențe tranzitive — un câmp nechie nu depinde de alt câmp nechie; dacă în tabelul elevi ținem și dirigintele, care depinde de clasă, nu de elev, numele dirigintelui se repetă pentru fiecare elev al clasei, iar schimbarea lui cere modificări în zeci de linii — soluția este un tabel separat clase(clasa, diriginte).
Intuiția de reținut: normalizarea sparge tabelele mari în tabele mai mici legate prin chei externe, astfel încât fiecare fapt să fie memorat o singură dată.
De reținut
- bază de date relațională
- colecție de date organizate în tabele formate din coloane (atribute cu tip fixat) și linii (înregistrări), legate între ele prin chei
- SGBD
- sistem de gestiune a bazelor de date — programul care creează, administrează și interoghează bazele de date (exemple: MySQL, PostgreSQL, Oracle)
- cheie primară
- câmp sau grup de câmpuri care identifică unic fiecare înregistrare a unui tabel; valorile sale sunt unice și nu pot fi NULL
- cheie externă
- câmp al unui tabel care referă cheia primară a altui tabel, realizând legătura dintre tabele și asigurând integritatea referențială
- SELECT
- comanda SQL de interogare, cu forma generală SELECT coloane FROM tabel WHERE condiție ORDER BY criteriu; SELECT alege coloanele, WHERE filtrează liniile
- funcții agregate
- funcții SQL care calculează o valoare unică dintr-o mulțime de linii: COUNT, SUM, AVG, MIN, MAX; COUNT(*) numără toate liniile, COUNT(coloana) doar pe cele cu valoare nenulă
- GROUP BY / HAVING
- GROUP BY grupează liniile după valorile unei coloane pentru calcule agregate pe grup; HAVING filtrează grupurile după valorile agregatelor, spre deosebire de WHERE, care filtrează liniile înainte de grupare
- INNER JOIN / LEFT JOIN
- INNER JOIN combină liniile din două tabele păstrând doar perechile cu corespondent în ambele; LEFT JOIN păstrează toate liniile tabelului stâng, completând cu NULL acolo unde lipsește corespondentul
- normalizare
- procesul de reorganizare a tabelelor pentru eliminarea redundanței și a anomaliilor de actualizare, prin descompunerea în tabele legate prin chei externe (formele normale FN1, FN2, FN3)
- NULL
- marcaj pentru valoare necunoscută sau absentă într-un câmp; se testează exclusiv cu IS NULL sau IS NOT NULL, orice comparație directă cu NULL nefiind adevărată
Greșeli frecvente
Greșit: Testarea valorilor lipsă cu condiția camp = NULL
Corect: NULL nu este egal cu nimic, nici cu el însuși; valorile lipsă se testează cu camp IS NULL, respectiv camp IS NOT NULL
Greșit: Folosirea clauzei WHERE pentru condiții pe funcții agregate, de tip WHERE AVG(nota) > 8
Corect: WHERE filtrează liniile înainte de grupare și nu poate conține agregate; condițiile pe rezultatele agregatelor se pun în HAVING, după GROUP BY
Greșit: Rularea comenzilor UPDATE sau DELETE fără clauza WHERE
Corect: Fără WHERE, comanda se aplică tuturor înregistrărilor din tabel — toate liniile sunt modificate sau șterse; condiția WHERE delimitează exact înregistrările vizate
Greșit: Confundarea comenzii DELETE cu DROP TABLE
Corect: DELETE șterge înregistrări și păstrează tabelul (chiar dacă rămâne gol); DROP TABLE elimină tabelul cu totul, inclusiv structura lui
Greșit: Implementarea unei relații m:n printr-o cheie externă directă între cele două tabele
Corect: Relația m:n cere un tabel asociativ intermediar, cu două chei externe către cele două tabele; o singură cheie externă poate exprima doar relații 1:n
Test — 6 întrebări ca la examen
1. Care afirmație despre cheia primară a unui tabel este adevărată?
- poate conține valori duplicate dacă sunt pe linii diferite
- identifică unic fiecare înregistrare și nu poate fi NULL
- este obligatoriu de tip numeric
- un tabel poate avea oricâte chei primare
Vezi răspunsul
identifică unic fiecare înregistrare și nu poate fi NULL. Cheia primară garantează identificarea unică a fiecărei linii, deci exclude duplicatele și valorile NULL. Poate fi și de tip text (de exemplu CNP memorat ca șir), iar un tabel are o singură cheie primară — celelalte câmpuri unice rămân chei candidate.
2. Interogarea SELECT nume FROM elevi WHERE nume LIKE „_a%” returnează elevii al căror nume:
- începe cu litera a
- conține litera a pe orice poziție
- are litera a pe a doua poziție
- se termină cu litera a
Vezi răspunsul
are litera a pe a doua poziție. Liniuța de subliniere ține locul exact unui caracter, deci prima literă e oarecare, a doua trebuie să fie a, iar procentul acoperă restul. Varianta „începe cu a” corespunde șablonului „a%” — confuzia dintre cele două metacaractere este exact ce testează întrebarea.
3. Pentru a afișa clasele în care media notelor depășește 8, interogarea corectă este:
- SELECT clasa FROM elevi WHERE AVG(media) > 8 GROUP BY clasa
- SELECT clasa FROM elevi GROUP BY clasa HAVING AVG(media) > 8
- SELECT clasa FROM elevi HAVING AVG(media) > 8
- SELECT clasa, AVG(media) FROM elevi WHERE media > 8
Vezi răspunsul
SELECT clasa FROM elevi GROUP BY clasa HAVING AVG(media) > 8. Condiția poartă asupra unei valori agregate pe grup, deci cere GROUP BY urmat de HAVING. Prima variantă — distractorul principal — pune agregatul în WHERE, ceea ce este interzis, WHERE evaluându-se pe linii individuale, înainte de formarea grupurilor; ultima filtrează elevii cu media personală peste 8, altă întrebare decât cea pusă.
4. Tabelele elevi și note sunt legate prin note.id_elev = elevi.id. Pentru a afișa TOȚI elevii, inclusiv pe cei fără nicio notă, se folosește:
- INNER JOIN între elevi și note
- LEFT JOIN cu tabelul elevi în stânga
- două interogări SELECT concatenate
- produsul cartezian al celor două tabele
Vezi răspunsul
LEFT JOIN cu tabelul elevi în stânga. LEFT JOIN păstrează toate liniile tabelului din stânga (elevi), completând cu NULL coloanele notelor pentru elevii fără corespondent. INNER JOIN — alegerea reflexă a multora — elimină exact elevii fără note, adică pe cei ceruți explicit de enunț.
5. Ce efect are comanda DELETE FROM elevi; executată fără clauza WHERE?
- șterge tabelul elevi împreună cu structura lui
- șterge toate înregistrările din tabel, dar tabelul rămâne
- produce eroare de sintaxă, WHERE fiind obligatoriu
- șterge doar prima înregistrare din tabel
Vezi răspunsul
șterge toate înregistrările din tabel, dar tabelul rămâne. DELETE fără WHERE se aplică tuturor liniilor, dar structura tabelului rămâne intactă — tabelul devine gol, nu inexistent. Prima variantă descrie DROP TABLE, confuzia clasică între ștergerea datelor și ștergerea obiectului.
6. Într-un tabel cu elevii unei școli se memorează pe fiecare linie și numele dirigintelui, care depinde doar de clasa elevului. Această structură încalcă:
- forma normală 1, pentru că valorile nu sunt atomice
- forma normală 3, existând o dependență tranzitivă prin câmpul clasa
- integritatea referențială
- unicitatea cheii primare
Vezi răspunsul
forma normală 3, existând o dependență tranzitivă prin câmpul clasa. Dirigintele depinde de clasă, iar clasa de elev — o dependență tranzitivă între câmpuri nechie, exact ce interzice FN3; consecința practică este redundanța: numele dirigintelui se repetă la fiecare elev al clasei. FN1 nu e încălcată, valorile fiind atomice — distractorul mizează pe faptul că FN1 e singura formă normală pe care mulți o rețin.
Deschide varianta interactivă — cu AI care îți explică