1. Introducere în Limbajul SQL și Baza de Date Model (EMP & DEPT)

🔍 De la Algoritmi Imperativi (C++) la Interogări Declarative (SQL)

SQL (Structured Query Language) este limbajul standard utilizat pentru a comunica cu un Sistem de Gestiune a Bazelor de Date Relaționale (precum MySQL, Oracle, PostgreSQL sau MS SQL Server).

Spre deosebire de C++, care este un limbaj imperativ (unde trebuie să scrii pas cu pas cum se parcurge un vector cu o buclă for), SQL este un limbaj declarativ: tu doar specifici ce coloane și ce condiții trebuie să îndeplinească datele, iar SGBD-ul alege singur cel mai rapid algoritm de căutare.

💻 În C++ (Abordare Imperativă)

// Parcurgem manual tot vectorul de angajați for(int i = 0; i < n; i++) { if(emp[i].deptno == 30 && emp[i].sal > 1500) { cout << emp[i].ename << " " << emp[i].sal; } }

🗄️ În SQL (Abordare Declarativă)

-- Cerem direct rezultatul printr-o singură frază SELECT ENAME, SAL FROM EMP WHERE DEPTNO = 30 AND SAL > 1500;

Comenzile SQL se împart în 4 sub-limbaje principale, dintre care instrucțiunea SELECT este cea mai importantă și cea mai utilizată (atât în practică, cât și la examenul de Bacalaureat):
• DQL (Data Query Language): Interogarea și extragerea datelor — instrucțiunea SELECT.
• DDL (Data Definition Language): Definirea structurii tabelelor — CREATE TABLE, ALTER TABLE, DROP TABLE.
• DML (Data Manipulation Language): Modificarea datelor din tabele — INSERT, UPDATE, DELETE.
• DCL (Data Control Language): Controlul permisiunilor — GRANT, REVOKE.

Baza de Date de Exersare: Tabelele DEPT și EMP

Toate exemplele și exercițiile din acest material folosesc baza de date clasică a unei companii, formată din două tabele aflate în relație 1:M (Unu-la-Mulți):
1. DEPT (Departamente — Tabelul Părinte, partea 1): reține codul departamentului (DEPTNO — Cheie Primară), numele departamentului (DNAME) și orașul (LOC).
2. EMP (Angajați / Employees — Tabelul Copil, partea M): reține marca angajatului (EMPNO — Cheie Primară), numele (ENAME), funcția (JOB), codul șefului direct (MGR), data angajării (HIREDATE), salariul (SAL), comisionul (COMM — doar pentru agenții de vânzări) și codul departamentului în care lucrează (DEPTNO — Cheie Externă).

Tabelul 1: DEPT (4 înregistrări)

DEPTNO (PK) DNAME LOC
10ACCOUNTINGNEW YORK
20RESEARCHDALLAS
30SALESCHICAGO
40OPERATIONSBOSTON

💡 Observație: Departamentul 40 (OPERATIONS) există în tabelul DEPT, dar nu are încă niciun angajat alocat în tabelul EMP.

Tabelul 2: EMP (9 înregistrări)

EMPNO (PK) ENAME JOB MGR HIREDATE SAL COMM DEPTNO (FK)
7839KINGPRESIDENTNULL1981-11-175000NULL10
7566JONESMANAGER78391981-04-022975NULL20
7698BLAKEMANAGER78391981-05-012850NULL30
7782CLARKMANAGER78391981-06-092450NULL10
7788SCOTTANALYST75661987-04-193000NULL20
7499ALLENSALESMAN76981981-02-20160030030
7521WARDSALESMAN76981981-02-22125050030
7654MARTINSALESMAN76981981-09-281250140030
7369SMITHCLERK75661980-12-17800NULL20

2. Anatomia Instrucțiunii SELECT (Ordinea Scrierii vs. Ordinea Execuției)

O instrucțiune SELECT completă este alcătuită din până la 6 clauze principale. Primele două (SELECT și FROM) sunt obligatorii, iar următoarele patru sunt opționale, dar când sunt folosite, trebuie scrise întotdeauna în această ordine strictă.

⚠️ Secretul pe care trebuie să îl știi la Mate-Info: Deși scriem comanda începând cu SELECT, motorul bazei de date o execută intern în altă ordine logică:
1. FROM (ia tabelul) → 2. WHERE (filtrează rândurile brute) → 3. GROUP BY (formează grupurile) → 4. HAVING (filtrează grupurile) → 5. SELECT (extrage coloanele/calculele) → 6. ORDER BY (sortează tabelul final).

1. SELECT (Proiecția)

Specifică ce coloane sau calcule (expresii, funcții agregat) vrem să apară în tabelul rezultat.

2. FROM (Sursa)

Specifică tabelul (sau tabelele) din care se citesc datele (ex: FROM EMP). Se execută prima!

3. WHERE (Selecția)

Filtrează rândurile individuale înainte de grupare. Rândurile care nu respectă condiția sunt eliminate.

4. GROUP BY (Gruparea)

Împarte rândurile rămase în submulțimi (grupuri) care au aceeași valoare pe coloana specificată (ex: pe departamente).

5. HAVING (Filtru Grup)

Filtrează grupurile formate de GROUP BY pe baza rezultatelor unor funcții agregat (COUNT, AVG, SUM).

6. ORDER BY (Sortarea)

Ordonează rândurile din rezultatul final crescător (ASC, implicit) sau descrescător (DESC). Se execută ultima!

SELECT DEPTNO, COUNT(*) AS Nr_Angajati, AVG(SAL) AS Salariu_Mediu
FROM EMP
WHERE JOB <> 'CLERK'
GROUP BY DEPTNO
HAVING COUNT(*) >= 2
ORDER BY Salariu_Mediu DESC;

👆 Apasă pe butoanele de mai sus pentru a vedea rolul fiecărei clauze în interogarea demonstrativă pe tabelul EMP.

3. Pas cu Pas: Proiecție, Filtrare (WHERE) și Sortare (ORDER BY)

Urmărește cum se construiesc interogările SQL de la cele mai simple la cele complexe, verificând de fiecare dată rezultatul pe tabelul EMP din Secțiunea 1.

Pasul 1 Alegerea Coloanelor (Proiecția), DISTINCT și Coloane Calculate

Putem extrage toate coloanele folosind simbolul *, doar anumite coloane separate prin virgulă, valori unice fără duplicate (DISTINCT) sau chiar expresii matematice redenumite cu AS (alias).

Exemplul 1.1: Extragerea funcțiilor unice din companie (DISTINCT)

Dacă am scrie SELECT JOB FROM EMP;, am primi 9 rânduri (funcția MANAGER și SALESMAN s-ar repeta). Cuvântul cheie DISTINCT elimină duplicatele din afișare:

SELECT DISTINCT JOB FROM EMP;
JOB
PRESIDENT
MANAGER
ANALYST
SALESMAN
CLERK

Exemplul 1.2: Coloane calculate și redenumirea lor cu AS (Alias)

Afișăm numele, salariul lunar și salariul anual (SAL * 12) redenumit ca Venit_Anual pentru angajații din departamentul 10:

SELECT ENAME AS Nume_Angajat, SAL, SAL * 12 AS Venit_Anual FROM EMP WHERE DEPTNO = 10;
Nume_AngajatSALVenit_Anual
KING500060000
CLARK245029400

Pasul 2 Filtrarea Rândurilor cu WHERE și Operatori Logici

Clauza WHERE testează fiecare rând din tabel. În SQL, egalitatea se scrie cu un singur egal (=, nu == ca în C++), iar diferit se scrie <> (sau !=). Șirurile de caractere și datele calendaristice se pun între apostrofuri simple ('MANAGER').

Exemplul 2.1: Condiții compuse cu AND și OR

Să se afișeze numele, funcția, salariul și departamentul pentru angajații din departamentul 30 care au salariul mai mare sau egal cu 1500:

SELECT ENAME, JOB, SAL, DEPTNO FROM EMP WHERE DEPTNO = 30 AND SAL >= 1500;
ENAMEJOBSALDEPTNO
BLAKEMANAGER285030
ALLENSALESMAN160030

Pasul 3 Operatori Speciali în SQL: BETWEEN, IN, LIKE și IS NULL

SQL oferă patru operatori extrem de expresivi care simplifică scrierea condițiilor și apar frecvent în subiectele de examen:

• BETWEEN a AND b: verifică apartenența la intervalul închis [a, b] (echivalent cu val >= a AND val <= b).
• IN (v1, v2, ...): verifică dacă valoarea se regăsește într-o mulțime de valori (echivalent cu val = v1 OR val = v2 ...).
• LIKE 'șablon': caută șiruri de caractere folosind două metacaractere (wildcards):
    — % înlocuiește oricâte caractere (0, 1 sau mai multe);
    — _ (underscore) înlocuiește exact un singur caracter.
• IS NULL / IS NOT NULL: testează dacă un câmp este necompletat. Atenție: Nu se scrie niciodată COMM = NULL!

Exemplul 3.1: Operatorii BETWEEN și IN

Afișăm angajații cu salariul cuprins între 2000 și 3000 (inclusiv capetele) care lucrează în departamentul 10 sau 20:

SELECT ENAME, JOB, SAL, DEPTNO FROM EMP WHERE SAL BETWEEN 2000 AND 3000 AND DEPTNO IN (10, 20);
ENAMEJOBSALDEPTNO
JONESMANAGER297520
CLARKMANAGER245010
SCOTTANALYST300020

Exemplul 3.2: Căutarea după șablon cu LIKE

Afișăm angajații al căror nume începe cu litera 'S' (LIKE 'S%') sau are litera 'L' pe a doua poziție (LIKE '_L%'):

SELECT EMPNO, ENAME, JOB FROM EMP WHERE ENAME LIKE 'S%' OR ENAME LIKE '_L%';
EMPNOENAMEJOB
7698BLAKEMANAGER
7782CLARKMANAGER
7788SCOTTANALYST
7499ALLENSALESMAN
7369SMITHCLERK

Exemplul 3.3: Testarea valorilor NULL (IS NOT NULL)

Afișăm doar angajații care primesc comision (adică au o valoare completată pe coloana COMM):

SELECT ENAME, JOB, SAL, COMM FROM EMP WHERE COMM IS NOT NULL;
ENAMEJOBSALCOMM
ALLENSALESMAN1600300
WARDSALESMAN1250500
MARTINSALESMAN12501400

Pasul 4 Sortarea Rezultatelor cu ORDER BY

Clauza ORDER BY se scrie întotdeauna la finalul instrucțiunii SELECT. Implicit, ordonarea este crescătoare (ASC). Pentru ordonare descrescătoare folosim DESC. Putem sorta și după mai multe criterii: dacă doi angajați au același salariu, îi departajăm alfabetic după nume.

SELECT ENAME, JOB, SAL FROM EMP WHERE DEPTNO = 30 ORDER BY SAL DESC, ENAME ASC;
ENAMEJOBSAL
BLAKEMANAGER2850
ALLENSALESMAN1600
MARTINSALESMAN1250
WARDSALESMAN1250

Observați cum MARTIN și WARD au același salariu (1250), dar MARTIN apare primul datorită celui de-al doilea criteriu: ENAME ASC.

4. Funcții Agregat, Grupare (GROUP BY / HAVING) și Joncțiuni (JOIN)

Pasul 5 Funcțiile de Agregare (Statistici pe Tabel)

Funcțiile agregat prelucrează o mulțime de rânduri și returnează o singură valoare rezumat:
• COUNT(*) — numără toate rândurile;
• COUNT(coloana) — numără doar rândurile unde acea coloană nu este NULL;
• SUM(coloana) — calculează suma valorilor numerice;
• AVG(coloana) — calculează media aritmetică;
• MIN(coloana) și MAX(coloana) — determină valoarea minimă, respectiv maximă.

Exemplul 5.1: Diferența dintre COUNT(*) și COUNT(COMM) pe tabelul EMP

SELECT COUNT(*) AS Total_Angajati, COUNT(COMM) AS Cu_Comision, MIN(SAL) AS Sal_Min, MAX(SAL) AS Sal_Max FROM EMP;
Total_AngajatiCu_ComisionSal_MinSal_Max
938005000

Pasul 6 Gruparea Datelor cu GROUP BY și Filtrarea cu HAVING

Ce facem dacă nu vrem salariul maxim pe toată firma, ci vrem numărul de angajați și salariul maxim pentru fiecare departament în parte? Folosim GROUP BY DEPTNO.

Regula de aur pentru WHERE vs. HAVING:
• În WHERE nu ai voie să pui funcții agregat (precum COUNT, AVG, SUM), deoarece WHERE filtrează rândurile individuale înainte de formarea grupurilor.
• În HAVING pui condiții pe funcțiile agregat (ex: HAVING COUNT(*) >= 3), deoarece filtrează grupurile după ce au fost calculate.

Exemplul 6.1: Statistici pe fiecare departament cu cel puțin 3 angajați

SELECT DEPTNO, COUNT(*) AS Nr_Ang, SUM(SAL) AS Fond_Salarii FROM EMP GROUP BY DEPTNO HAVING COUNT(*) >= 3;
DEPTNONr_AngFond_Salarii
2036775
3046950

Observați că departamentul 10 (care are doar 2 angajați: KING și CLARK) a fost eliminat de clauza HAVING COUNT(*) >= 3.

Pasul 7 Interogarea pe Două Tabele: Joncțiunea (INNER JOIN)

În tabelul EMP avem doar codul departamentului (DEPTNO), nu și numele departamentului (DNAME) sau orașul (LOC), care se află în tabelul DEPT. Pentru a afișa date din ambele tabele simultan, le unim prin condiția de egalitate dintre Cheia Externă (EMP.DEPTNO) și Cheia Primară (DEPT.DEPTNO).

Exemplul 7.1: Afișarea numelui angajatului, a funcției, a denumirii departamentului și a orașului

În SQL există două moduri echivalente de a scrie această joncțiune (ambele sunt acceptate la Bacalaureat):

Varianta 1 (Standard SQL cu INNER JOIN ... ON):

SELECT E.ENAME, E.JOB, D.DNAME, D.LOC FROM EMP E INNER JOIN DEPT D ON E.DEPTNO = D.DEPTNO WHERE D.LOC = 'DALLAS';

Varianta 2 (Sintaxa clasică în WHERE):

SELECT E.ENAME, E.JOB, D.DNAME, D.LOC FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO AND D.LOC = 'DALLAS';

Tabelul rezultat (pentru oricare dintre cele două variante de mai sus):

ENAMEJOBDNAMELOC
JONESMANAGERRESEARCHDALLAS
SCOTTANALYSTRESEARCHDALLAS
SMITHCLERKRESEARCHDALLAS

5. Laborator de Exersare și Quiz SQL (pe baza EMP & DEPT)

Testează-ți cunoștințele! Consultă tabelele EMP și DEPT din Secțiunea 1 ori de câte ori vrei să verifici rezultatul unei interogări.

Partea I: Alege Clauza sau Condiția SQL Corectă

Pentru fiecare cerință de mai jos, apasă pe varianta de cod SQL care rezolvă corect problema:

1. Cum filtrăm în WHERE angajații care NU primesc comision (au NULL pe coloana COMM)?
2. Ce șablon LIKE selectează toți angajații al căror nume (ENAME) se termină cu litera 'N' (ex: ALLEN, MARTIN)?
3. Vrem să afișăm doar funcțiile (JOB) în care salariul mediu depășește 2500. Ce clauză folosim după GROUP BY JOB?
4. Care expresie este complet echivalentă cu WHERE SAL >= 1250 AND SAL <= 2850?
5. Cum ordonăm angajații crescător după departament (DEPTNO), iar în cadrul aceluiași departament, descrescător după salariu (SAL)?

Partea a II-a: Test Grilă de Evaluare SQL (pe datele din EMP & DEPT)

Calculează rezultatul interogărilor de mai jos pe baza celor 9 angajați din tabelul EMP și a celor 4 departamente din DEPT:

1. Ce valoare va afișa interogarea: SELECT COUNT(MGR) FROM EMP WHERE DEPTNO = 10; ? (Verifică angajații din departamentul 10 în tabelul EMP!)

A. 2
B. 1
C. 0

2. Care este primul nume afișat de interogarea: SELECT ENAME FROM EMP WHERE JOB = 'SALESMAN' ORDER BY COMM DESC; ?

A. ALLEN
B. MARTIN
C. KING

3. Care este ordinea CORECTĂ în care se scriu clauzele unei instrucțiuni SELECT în limbajul SQL?

A. SELECT → FROM → GROUP BY → WHERE → ORDER BY → HAVING
B. SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
C. FROM → WHERE → SELECT → GROUP BY → HAVING → ORDER BY

4. Câte rânduri returnează interogarea: SELECT ENAME FROM EMP WHERE DEPTNO IN (10, 20) AND SAL > 2500; ?

A. 4 rânduri (KING, JONES, BLAKE, SCOTT)
B. 3 rânduri (KING, JONES, SCOTT)
C. 2 rânduri (KING, SCOTT)

5. Executăm o joncțiune internă între cele două tabele: SELECT E.ENAME, D.DNAME FROM EMP E INNER JOIN DEPT D ON E.DEPTNO = D.DEPTNO;. De ce nu apare departamentul OPERATIONS (cod 40) pe niciun rând din rezultat?

A. Deoarece INNER JOIN afișează doar departamentele care au cel puțin un angajat asociat în tabelul EMP.
B. Deoarece SQL poate afișa maximum 3 departamente într-un JOIN.
C. Deoarece departamentul OPERATIONS are cheia primară NULL.

6. Ce valoare returnează interogarea: SELECT SUM(COMM) FROM EMP WHERE DEPTNO = 30; ?

A. NULL, deoarece angajatul BLAKE din departamentul 30 are COMM = NULL.
B. 2200
C. 6950
Scor curent: 0 / 11 puncte
Rezolvă cele 5 exerciții rapide (Partea I) și cele 6 întrebări grilă (Partea a II-a)!