====== Laborator 00 (partea a II-a) ====== ====== Sisteme de Gestiune pentru Baze de Date. MySQL ====== ===== Obiective ==== * definirea unui sistem de baze de date / sistem pentru de gestiune pentru baze de date; cunoașterea funcționalității puse la dispoziție de acestea * proiectarea unei baze de date astfel încât structura acesteia să respecte diferite forme normale; identificarea formei normale optime pentru satisfacerea cerințelor unei aplicații de timp real * familiarizarea cu principalele instrucțiuni de definire și manipulare a datelor din cadrul sistemului de gestiune pentru baze de date MySQL pentru implementarea unor aplicații care realizează nivelul de persistență prin astfel de metode * folosirea obiectelor din baza de date (rutine stocate, triggere, evenimente, vizualizări) ca instrumente ce furnizează eficient informații, preluând o parte din sarcinile nivelurilor superioare ale aplicațiilor * utilizarea tranzacțiilor cu nivele de izolare adecvate pentru asigurarea coerenței datelor stocate în cadrul bazei de date ===== Cuvinte Cheie ==== Database Systems, Database Management Systems, Data Definition Language, Data Manipulation Language, View Definition Language, normal forms, 1-NF, 2-NF, 3-NF, BC-NF, stored routine, procedure, function, trigger, event, view, cursor, transactions, read uncommitted, read committed, serializable, repeatable read ===== Materiale Ajutătoare ====== {{ :laboratoare:laborator00b:aipi2015-lab00b-slides.pdf }} ===== Sisteme de Gestiune pentru Baze de Date ===== Un **SGBD = sistem de gestiune pentru baze de date** (//eng.// DBMS = Database Management Systems) este o colecţie de programe care are drept scop indicarea structurii bazei de date, popularea și exploatarea sa, asigurând totodată și protecţia datelor. Aceasta reprezintă o aplicaţie informatică complexă ce însoţeşte baza de date, având ca funcţii definirea (specificarea structurii şi a tipurilor de date stocate), construcţia (popularea) şi manipularea ei (diferite operaţii de la implementarea interogărilor pentru regăsirea informaţiilor până la generarea de rapoarte). Rolul său este de a facilita accesul la date, fapt ce este realizat prin asigurarea transparenţei reprezentării acestora faţă de utilizatori în cadrul procesului de interacţiune cu ele. Din acest punct de vedere, sistemul de gestiune pentru baze de date este interfaţa între nivelul fizic, cel mai scăzut, şi programele prin care sunt transmise aplicaţiei interogările de regăsire a datelor. Împreună cu datele propriu-zise (prin date înțelegându-se nu doar la colecţiile de informaţii stocate de către utilizator ci şi dicţionarul de date care conţine meta-date: structura bazei de date, definiţiile tipurilor de date / restricţiile de integritate, viziunile, indecşii precum şi fişierele anexe care conţin un context curent de lucru (parametrii de configurare), acesta formează **SBD = sistemul de baze de date** (//eng.// DBS = Database System). **Arhitectura** asociată unei baze de date este reprezentată pe trei niveluri, având ca scop separarea aplicaţiilor utilizatorului de informaţiile stocate: * nivelul (schema) intern(ă) specifică structura de stocare fizică a datelor, folosind un model al datelor fizice; acesta descrie atât modul de stocare cât și modul de acces; * nivelul (schema) conceptual(ă) descrie structura bazei de date prin entităţi şi tipul de date asociat precum şi relaţiile dintre acestea, precizând şi restricţiile de integritate; * nivelul (schema) extern(ă) prezintă moduri de vizualizare ale bazei de date din perspectiva diferiţilor utilizatori care o accesează. **Obiectivele** unui sistem de gestiune pentru baze de date sunt: * asigurarea independenţei datelor, încât modificarea structurii de memorare a datelor sau schimbarea modului de acces la ele (independenţă fizică) precum şi a schemei conceptuale (independenţă logică) să nu presupună rescrierea programelor de aplicaţie; * asigurarea unei redundanţe minime şi controlate a datelor; * asigurarea unor facilităţi de utilizare a datelor (concurenţa utilizatorilor, uşurinţa regăsirii datelor prin limbaje apropiate de cel natural, optimizarea timpilor de acces); * oferirea unui nivel de securitate ridicat împotriva accesului neautorizat; * asigurarea integrităţii datelor prin proceduri de validare şi printr-un protocol de refacere a bazei de date. Toate sistemele de gestiune a bazelor de date oferă **un set de funcţii**: * funcţia de definire a datelor, implementată prin intermediul unui limbaj de definire a datelor (DDL – Data Definition Language) ce permite crearea bazelor de date, specificarea structurii tabelelor unei baze de date prin precizarea atributelor, a tipurilor de date, a legăturilor dintre ele, a restricţiilor sau modalităţile de acces; schema bazei de date descrisă la acest nivel va fi memorată în dicţionarul de date; * funcţia de manipulare a datelor, realizată printr-un limbaj de manipulare a datelor (DML – Data Manipulation Language) care permite adăugarea, modificarea sau ştergerea unor înregistrări, regăsirea de informaţii potrivit unor criterii şi ordonarea lor; * funcţia de utilizare ce oferă interfeţe de regăsire a informaţiilor, printr-un limbaj de definire a vizualizărilor (VDL – View Definition Language); există mai multe tipuri de utilizatori: * beneficiari (utilizatori conversaţionali) care vizualizează informaţiile fără a folosi vreun limbaj de interogare a bazei de date; * programatori care utilizează limbajele de manipulare, exploatând baza de date prin mecanisme complexe; * administratorul bazei de date care ia decizii în privinţa funţionării corespunzătoare a întregului sistem. * funcţia de administrare a bazei de date care permite configurarea diferiţilor parametri (performanţă, securitate) în funcţie de scopul în care se doreşte să se utilizeze baza de date. **Clasificarea** sistemelor de gestiune a bazelor de date se poate face după mai multe criterii: * în funcţie de modelul de date utilizat: relaţional, reţea, ierarhizat, obiectual; * numărul de utilizatori ce pot accesa baza de date simultan: monouser, multiuser; * modul de stocare al datelor: centralizat, distribuit; * costurile pentru licenţiere: produse gratuite şi cu cost de licenţiere scăzut / ridicat; * limbajul pentru scrierea de aplicaţii: medii cu limbaj nativ şi cu limbaj gazdă. De asemenea, pe lângă sistemele de bază de date convenţionale, au fost dezvoltate sisteme de baze de date evoluate cum ar fi cele paralele, mobile, spaţiale, multimedia, distribuite, dezvoltate pentru aplicaţii specializate, caracterizate prin funcţionalităţi sau tipuri de date care dau specificul sistemului de baze de date respectiv. ===== Proiectarea bazelor de date ===== În proiectarea bazelor de date trebuie să se țină cont de următoarele principii: * structura bazei de date trebuie să fie **normalizată** (să respecte o formă normală), astfel încât să se evite anomaliile la operaţiile de actualizare în baza de date; * pentru fiecare tabelă trebuie să se definească o **cheie primară** (formată din unul sau mai multe atribute) care să identifice în mod unic o înregistrare; * **tipul de date** trebuie ales astfel încât să corespundă necesităţilor, evitând irosirea de memorie pe discul fizic, dar evitând situaţia în care anumite informaţii nu pot fi reţinute datorită unei alocări insuficiente; * toate tabelele unei baze de date trebuie să fie legate prin legături de tip **cheie străină**: o tabelă care nu are legătură cu celelalte tabele nu face parte din baza de date respectivă. ==== Normalizarea ==== Normalizarea este procesul de proiectare a structurii unei tabele pentru a se minimiza redundanţa datelor, urmărindu-se evitarea anomaliilor în cazul operaţiilor de actualizare. Astfel, schemele de relaţie nesatisfăcătoare sunt descompuse obţinându-se mai multe scheme de relaţie mai mici care să respecte proprietăţile dorite. Normalizarea nu asigură însă în mod necesar buna construcţie a bazei de date. Din punct de vedere structural, formele normale respectă relaţia FN1 < FN2 < FN3 < FNBC, astfel că o formă normală de ordin superior este mai bună decât o formă normală de ordin inferior, cel puțin din punctul de vedere al anomaliilor ce pot apărea în cadrul procesului de actualizare. Au fost definite şi forme normale superioare, precum forma normală 4 (FN4) şi forma normală 5 (FN5) sau forma normală domeniu-cheie (DKNF - eng. domanin-key normal form), însă acestea nu sunt întâlnite în mediul de afaceri, având o imporanţă strict teoretică. În stabilirea formei normale pe care o va respecta schema de relaţie trebuie să se aibă în vedere şi principiul vitezei de răspuns a interogărilor, care depinde de numărul de joncţiuni realizat între tabele, acesta fiind invers proporţional cu forma normală. De cele mai multe ori, forma normală 3 este suficientă spre a satisface cerinţele organizaţiilor, realizând cel mai bun compromis între evitarea anomaliilor în operaţiile de manipulare a datelor şi asigurarea unei viteze de răspuns corespunzătoare. Procesul de normalizare asigură conformitatea unei tabele fată de conceptul de **relaţie bine formată**, caracterizată prin: - fiecare tabelă corespunde unei singure entităţi, conţinând exclusiv atributele specifice acesteia; - principiul redundanţei minime şi controlate - nici o informaţie nu va fi reţinută în mai mult de o tabelă în cazul în care nu este necesar; - atributele non-prime sunt dependente doar de cheia primară astfel că aceasta le identifică în mod unic; - principiul integrităţii şi consistenţei datelor: nici o tabelă nu conţine anomalii la adăugare, modificare sau ştergere. O **supercheie** ''S'' într-o relaţie ''R = (a1, a2 ... , an)'' este un set de atribute din ''R'' având proprietatea că nu există două n-tupluri ''t1'' şi ''t2'' în orice instanţă ''r'' a lui ''R'' astfel ca ''t1(S)=t2(S)''. Diferenţa între o cheie şi o supercheie constă în faptul că întotdeauna cheia conţine un număr minim de atribute. Un atribut al relaţiei ''R'' se numeşte prim dacă este membru al unei superchei din ''R'' şi este nonprim dacă nu este un atribut prim. Prin urmare, o cheie primară este o supercheie minimală (ireductibilă). O **dependenţă funcţională** este o relaţie între două atribute ''X'' şi ''Y'' ale unei relaţii ''R'', notată ''X → Y'', cu proprietatea că fiecare valoare a lui ''X'' determină o singură valoare a lui ''Y''. Cu alte cuvinte, oricare ar fi două tupluri ''t1'' şi ''t2'' din ''R'', astfel încât ''t1(X)=t2(X)'', atunci ''t1(Y)=t2(Y)''. O dependenţă funcţională ''X → Y'' (cu ''X'' atribut compus) este **completă** dacă prin eliminarea oricărui atribut ''Z ∈ X'' dependenţa funcţională este distrusă. O dependenţă funcţională ''X → Y'' (cu ''X'' atribut compus) este **parţială** dacă există un atribut (sau set de atribute) ''Z ∈ X'' astfel încât ''X \ {Z} → Y''. Definiţia poate fi extinsă în cazul în care ambele atribute ale dependenţei funcţionale sunt compuse: o dependenţă funcţională ''X → Y'' (cu ''X'', ''Y'' atribute compuse) este parţială dacă există atribute (sau seturi de atribute) ''Z1 ∈ X'', ''Z2 ∈ Y'' astfel încât ''X \ {Z1} → Y \ {Z2}''. O dependenţă funcţională ''X → Y'' (cu ''X'' cheie primară) este **tranzitivă** dacă există un atribut (sau set de atribute) ''Z'' care nu fac parte din cheia primară astfel încât ''X → Z'' şi ''Z → Y''. Dependenţele funcţionale tranzitive pot fi identificate cu uşurinţă în schemele de relaţie unde există dependenţe funcţionale între atribute non-prime. Prin urmare, modificările în structurile tabelelor caracterizate prin această problemă vor porni de la dependenţa funcţională dintre atributele non-prime. === Exemplu === Se consideră o schemă de relaţie pentru gestiunea proiectelor şi a resurselor umane din cadrul unei organizaţii, reţinând câte ore a lucrat fiecare angajat dintr-un departament la proiectul la care a fost asociat precum şi salariul său tarifar: ''gestiune_organizatie = { id_proiect, nume_proiect, id_angajat, nume_angajat, pozitie_angajat, salariu_tarifar_angajat, ore_lucrate_angajat }'' {{ :laboratoare:laborator00b:01no_normal_form.png?nolink&600 }} Această schemă de relaţie este caracterizată prin **inconsistenţa datelor** (acelaşi departament poate fi exprimat prin valori diferite) cât şi prin **redundanţă** (pentru fiecare angajat se reţin de mai multe ori numele, departamentul şi salariul tarifar - dacă a lucrat la mai multe proiecte), ceea ce determină anomalii la principalele operaţii de manipulare a datelor: * adăugare (pentru proiect/angajat se introduc valori null) * modificare (datele referitoare la proiect/angajat trebuie actualizate în toate tuplurile care le referă) * ștergere (atunci când un proiect/angajat este eliminat, se pierd şi informaţiile aferente acestora). Pentru eliminarea acestor probleme, au fost definite forme normale: === Forma Normală 1 (FN1) === Forma normală primară (FN1) nu permite ca pentru un atribut de tip cheie să existe mai multe valori identice sau ca schema de relaţie să conţină atribute compuse. În acest sens, se impune **stabilirea unei chei primare** care să identifice în mod unic tuplurile din schema de relaţie, **punându-se în evidenţă totodată şi dependenţele funcţionale**. În cazul exemplului, se observă cheia primară ''(id_proiect, id_angajat)'', evidenţiindu-se totodată şi următoarele dependenţe funcţionale: * dependenţe funcţionale parţiale\\ ''id_proiect → nume_proiect''\\ ''id_angajat → nume_angajat, pozitie_angajat, salariu_tarifar_angajat'' * dependenţe funcţionale tranzitive\\ ''pozitie_angajat → salariu_tarifar_angajat'' Astfel, schema de relaţie în care s-a identificat cheia primară (compusă) şi dependenţele funcţionale, respectă forma normală 1, reprezentată prin următoarea diagramă funcţională: {{ :laboratoare:laborator00b:02normal_form_1.png?nolink&600 }} === Forma Normală 2 (FN2) === Forma normală secundară (FN2) este satisfăcută de schemele de relaţie care îndeplinesc condiţiile formei normale primare şi pentru care orice atribut nonprim este complet dependent funcţional de cheia primară. În acest sens, se impune **eliminarea dependenţelor funcţionale parţiale** care implică redundanţa datelor şi anomalii la operaţiile de manipulare a acestora. În cazul în care cerinţele legate de performanţă (viteză de răspuns) implică păstrarea dependenţelor de tip parţial, este recomandată trecerea la implementarea unui depozit de date în care redundanţa reprezintă un principiu de proiectare. **Observaţie**. Orice schemă de relaţie ce respectă FN1 şi are cheia primară formată dintr-un singur atribut respectă în mod automat şi FN2. Se vor creea noi scheme de relaţie având drept chei primare atributele care determină dependenţele funcţionale parţiale, împreună cu atributele non-prime care se află în relaţie cu ele. Ele vor rămâne şi în schema de relaţie iniţială cu atributele non-prime faţă de care există dependenţe funcţionale complete. Menținerea în schema de relaţie iniţială a tuturor atributelor care alcătuiesc cheia primară este determinată de faptul că acestea vor reprezenta referinţe pentru legăturile ce se vor stabili cu noile scheme de relaţie constituite. Pentru exemplul de faţă se vor constitui tabelele: * ''proiecte'' (cu cheia primară ''id_proiect'') din care face parte atributul non-prim determinat ''nume_proiect'' * ''angajati'' (cu cheia primara ''id_angajat'') din care fac parte atributele non-prime determinate ''nume_angajat'', ''pozitie_angajat'', ''salariu_tarifar_angajat''. În schema de relaţie iniţială (având cheia primară compusă ''(id_proiect, id_angajat)'') rămâne atributul non-prim ''ore_lucrate_angajat'', complet dependent funcţional de cheia primară. ① ''proiecte = {__id_proiect__, nume_proiect}''\\ ''id_proiect → nume_proiect''\\ ② ''angajati = {__id_angajat__, nume_angajat, pozitie_angajat, salariu_tarifar_angajat}''\\ ''id_angajat → nume_angajat, pozitie_angajat, salariu_tarifar_angajat''\\ ''pozitie_angajat → salariu_tarifar_angajat''\\ ⓪ ''asocieri = {__id_proiect__, __id_angajat__, ore_lucrate_angajat}''\\ ''(id_proiect, id_angajat) → ore_lucrate_angajat'' {{ :laboratoare:laborator00b:03normal_form_2.png?nolink&600 }} === Forma Normală 3 (FN3) === Forma normală terţiară (FN3) este îndeplinită de schemele de relaţie care satisfac condiţiile formei normale secundare şi în care nu există dependenţe tranzitive. În acest sens, se impune **eliminarea dependenţelor funcţionale tranzitive** care implică redundanţa datelor şi anomalii la operaţiile de manipulare a acestora. Se vor creea noi scheme de relaţie având drept chei primare atributele care determină dependenţele funcţionale tranzitive, împreună cu atributele ce se află în relaţie cu ele. Ca şi în situaţia descompunerii din cazul formei normale 2, ele vor rămâne şi în schema de relaţie iniţială. Pentru exemplul de faţă se va constitui tabela ''salarii_pozitii'', având cheia primară ''pozitie_angajat'' și atributul ''salariu_tarifar_angajat'' ce va fi eliminat din schema de relatie ''angajaţi''. ① ''proiecte = {__id_proiect__, nume_proiect}''\\ ''id_proiect → nume_proiect''\\ ② ''angajati = {__id_angajat__, nume_angajat, pozitie_angajat}''\\ ''id_angajat → nume_angajat, pozitie_angajat''\\ ③ ''salarii_pozitii = {__pozitie_angajat__, salariu_tarifar_angajat}''\\ ''pozitie_angajat → salariu_tarifar_angajat''\\ ⓪ ''asocieri = {__id_proiect__, __id_angajat__, ore_lucrate_angajat}''\\ ''(id_proiect, id_angajat) → ore_lucrate_angajat'' {{ :laboratoare:laborator00b:04normal_form_3.png?nolink&600 }} ==== ==== În procesul de normalizare a unei scheme de relaţie trebuie asigurate iniţial formele normale inferioare trecându-se apoi la formele normale superioare. În cazul FN2 se elimină dependenţele funcţionale parţiale iar în cazul FN3 dependenţele funcţionale tranzitive, mecanismul fiind acelaşi: se creează noi scheme de relaţie conţinând atributele implicate în dependenţa funcţională respectivă, păstrând atributele determinante în schema de relaţie iniţială (pentru a servi drept referinţe) şi scoţând atributele determinate. === Forma Normală Boyce-Codd === Forma normală Boyce-Codd respectă forma normală 3; aşadar orice schemă de relaţie care este în FNBC este şi în FN3, fără ca reciproca să fie valabilă în mod necesar. Condiţia pe care trebuie să o îndeplinească tabela este că pentru orice dependenţă funcţională ''X → Y'', ''X'' este o supercheie, diferenţa faţă de FN3 fiind că aceasta permite ca ''Y'' să fie non-prim dacă ''X'' nu este supercheie. **Exemplu**. Se consideră o schemă de relaţie pentru determinarea celui mai apropiat magazin de un anume tip faţă de o locaţie dată printr-o denumire. ''magazin_apropiat_tip = { denumire_locatie, tip_magazin, denumire_magazin_apropiat }'' {{ :laboratoare:laborator00b:05normal_form_boyce_codd_a.png?nolink&300 }} Candidate pentru chei sunt ''(denumire_locatie, tip_magazin)'' sau ''(denumire_locatie, denumire_magazin_apropiat)''. Toate cele trei atribute sunt prime, deci schema face parte din FN3. Totuşi, ''tip_magazin'' depinde de ''denumire_magazin''_apropiat care nu este supercheie, deci tabela nu respectă FNBC, ceea ce înseamnă că este susceptibilă de producerea anomaliilor la actualizare. Vom descompune această schemă de relaţie în altele două: ① ''magazin_apropiat = {__denumire_locatie__, __denumire_magazin_apropiat__}''\\ ② ''specializare = {__denumire_magazin_apropiat__, __tip_magazin__}'' {{ :laboratoare:laborator00b:06normal_form_boyce_codd_b.png?nolink&300 }} O problemă o reprezintă totuşi faptul că schema de relaţie permite ca pentru o ''denumire_locatie'' să se specifice mai multe ''denumire_magazin_apropiat'' de acelaşi tip. ==== ==== De aceea, se preferă proiectarea unor baze de date conform formei normale 3 (FN3) şi nu conform formei normale Boyce-Codd. Deşi asigură scăderea anomaliilor în cazul operaţiilor de manipulare a datelor, aceasta creşte foarte mult timpul în care se execută interogările întrucât va fi necesar accesul la mai multe tabele simultan împreună cu asocierea informaţiilor din acestea, implicând operaţii de căutare mai complexe. ==== Alegerea cheii primare ==== De regulă, în sistemele de gestiune pentru baze de date, cheile primare sunt implementate sub forma unor arbori B+, astfel încât atunci când se alege un atribut care să se supună acestei constrângeri e integritate, trebuie să se țină cont de tipurile de date asociate, astfel încât acestea să asigure o regăsire mai rapidă a informaţiilor (astfel, se preferă datele numerice faţă de datele de tip şir de caractere avându-se în vedere şi dimensiunea lor). === === Arborii B+ sunt structuri de date folosite cu precădere la regăsirea informaţiilor (mai ales în sisteme de fişiere). În cadrul acestora, fiecare valoare posibilă a câmpului de căutare apare împreună cu un pointer către date în cadrul nodurilor frunză. Astfel de valori pot fi repetate în nodurile interne pentru a ghida căutarea. Pentru un arbore B+ de ordin p, sunt îndeplinite următoarele caracteristici: - nodurile interne au structura ''
1, K1, P2, K2, ..., Pq-1, Kq-1, Pq>'', ''q ≤ p'', unde ''Pi'' reprezintă un pointer către un subarbore, iar ''Ki'' reprezintă valori ale cheilor de căutare ordonate crescător: ''K1 < K2 < ... < Kq-1''. Dacă se caută o valoare ''X'' în subardorele referit de ''Pi'' atunci ''Ki-1 < X ≤ Ki'', cu ''i ≠ 1'' şi ''i ≠ q'' (dacă ''i=1'', ''X ≤ K1'', dacă ''i=q'', ''X > Kq-1''). Orice nod intern are cel mult ''p'' si cel puţin ''[ (p/2) ]'' pointeri către subarbori (nodul rădăcină are cel puţin 2 pointeri, dacă este nod intern). Un nod intern cu ''q'' pointeri are ''q-1'' valori ale câmpului de căutare.\\ {{ :laboratoare:laborator00b:07Bplus_tree_internal_node.png?nolink&500 }}
- nodurile frunză au structura ''<
CAST(value AS type)
spre exemplu:
CAST('123.45' AS DECIMAL(5,2)) = 123.45
CAST('2013-10-14' AS DATETIME) = '2013-10-14 00:00:00'
CAST(123 AS CHAR) = ’123’
În operaţii în care intervin valori având tipuri de date diferite, în măsura în care este posibil, conversia este realizată în mod implicit de sistemul de gestiune pentru baze de date.
===== Instrucțiuni pentru gestiunea datelor =====
==== Operații de definire a datelor ====
În MySQL, informațiile sunt grupate sub formă de baze de date (= scheme), fiecare dintre acestea putând conține mai multe tabele.
=== CREATE DATABASE ===
Crearea unei baze de date se face prin instrucțiunea:
CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] database_name
[create_specification] ...
create_specification:
[DEFAULT] CHARACTER SET [=] charset_name
| [DEFAULT] COLLATE [=] collation_name
Operația este realizată cu succes dacă utilizatorul deține dreptul ''CREATE'' asupra bazei de date și dacă baza de date nu există anterior (altfel, trebuie specificată clauza ''IF NOT EXISTS''). Inițial, baza de date este vidă.
O bază de date este stocată sub forma unui director care conține câte un fișier pentru fiecare tabelă și un fișier ''db.opt'' pentru a se reține atribute precum setul de caractere și modul de asamblare (ce pot fi specificate la crearea bazei de date).
=== ALTER DATABASE ===
Prin intermediul comenzii ''ALTER DATABASE'' pot fi modificate atributele bazei de date, cum ar fi setul de caractere și modul de asamblare.
ALTER {DATABASE | SCHEMA} [database_name]
alter_specification ...
alter_specification:
[DEFAULT] CHARACTER SET [=] charset_name
| [DEFAULT] COLLATE [=] collation_name
Operația este realizată cu succes dacă utilizatorul deține dreptul ''ALTER'' asupra bazei de date.
Dacă nu se specifică nici un identificator pentru baza de date, va fi utilizată baza de date implicită (selectată în mod curent).
În cazul în care baza de date conține rutine stocate care utilizează setul de caractere sau modul de asamblare implicite, acestea trebuie redefinite pentru a folosi aceste proprietăți.
=== DROP DATABASE ===
Ștergerea unei baze de date (împreună cu toate tabelele pe care le conține, cu excepția celor marcate ca fiind temporare - care sunt eliminate odată cu sesiunea curentă) se face prin instrucțiunea:
DROP {DATABASE | SCHEMA} [IF EXISTS] database_name;
Operația este realizată cu succes dacă utilizatorul deține dreptul ''DROP'' asupra bazei de date și dacă baza de date există în prealabil (altfel, trebuie specificată clauza ''IF EXISTS'').
Dacă există o bază de date implicită stabilită anterior, în urma execuției acestei instrucțiuni nu va mai exista o bază de date implicită.
=== SHOW DATABASES ===
Vizualizarea listei ce conține bazele de date existente poate fi realizată prin intermediul comenzii:
SHOW {DATABASES | SCHEMAS}
[LIKE 'pattern' | WHERE expr]
Vor fi incluse doar acele baze de date pentru care utilizatorul curent are anumite privilegii (cu excepția cazului în care deține dreptul de acces ''SHOW DATABASES'').
USE database_name;
GRANT | REVOKE
priv_type [(column_list)]
[, priv_type [(column_list)]] ...
ON [object_type] priv_level
TO | FROM user_specification [, user_specification] ...
object_type:
TABLE
| FUNCTION
| PROCEDURE
priv_level:
*
| *.*
| db_name.*
| db_name.tbl_name
| tbl_name
| db_name.routine_name
user_specification:
user
[
| IDENTIFIED WITH auth_plugin [AS 'auth_string']
IDENTIFIED BY [PASSWORD] 'password'
]
Un utilizator nu poate acorda / revoca drepturi de acces decât dacă deține privilegiul ''GRANT OPTION'' / ''REVOKE OPTION'' precum și drepturile de acces pe care le acordă / revocă.
Privilegiile suportate sunt ''ALL [PRIVILEGES]'', ''ALTER'', ''ALTER ROUTINE'', ''CREATE'', ''CREATE ROUTINE'', ''CREATE TABLESPACE'', ''CREATE TEMPORARY TABLES'', ''CREATE USER'', ''CREATE VIEW'', ''DELETE'', ''DROP'', ''EVENT'', ''EXECUTE'', ''FILE'', ''GRANT OPTION'', ''INDEX'', ''INSERT'', ''LOCK TABLES'', ''PROCESS'', ''PROXY'', ''REFERENCES'', ''RELOAD'', ''REPLICATION CLIENT'', ''REPLICATION SLAVE'', ''SELECT'', ''SHOW DATABASES'', ''SHOW VIEW'', ''SHUTDOWN'', ''SUPER'', ''TRIGGER'', ''UPDATE'' și ''USAGE''.
Privilegiile globale sunt specificate folosind sintaxa ''ON *.*'' și sunt stocate în tabela ''mysql.user''.
Privilegiile la nivel de bază de date sunt specificate folosind sintaxa ''ON database_name.*'' și sunt stocate în tabela ''mysql.db''. Dacă se folosește sintaxa ''ON *'' și există o bază de date implicită, privilegiile specificate se vor referi la aceasta.
Privilegiile la nivel de tabel sunt specificate folosind sintaxa ''ON database_name.table_name'' și sunt stocate în tabela ''mysql.tables_priv''. Există posibilitatea de a nu prefixa denumirea tabelei prin numele bazei de date, dacă există o bază de date implicită și tabela specificată există în contextul acesteia.
Privilegiile la nivel de coloană se fac prin specificarea atributelor respective, între paranteze, după indicarea drepturilor de acces respective. Acestea sunt stocate în tabela ''mysql.columns_priv''.
Privilegiul ''ALL'' se referă la toate drepturile de acces disponibile la nivelul la care se acordă (global, bază de date, tabel, coloană).
Pot fi specificate drepturi de acces pentru obiecte care nu există încă, privilegiile fiind aplicate din momentul în care acestea există în baza de date.
=== CREATE TABLE ===
O tabelă din cadrul bazei de date este creată prin intermediul instrucţiunii ''CREATE TABLE'', având forma:
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] table_name
(create_definition,...)
[table_options]
[partition_options]
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] table_name
[(create_definition,...)]
[table_options]
[partition_options]
select_statement
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] table_name
{ LIKE old_table_name | (LIKE old_table_name) }
create_definition:
column_name column_definition
| [CONSTRAINT [symbol]] PRIMARY KEY [index_type] (index_column_name,...)
[index_option] ...
| {INDEX|KEY} [index_name] [index_type] (index_column_name,...)
[index_option] ...
| [CONSTRAINT [symbol]] UNIQUE [INDEX|KEY]
[index_name] [index_type] (index_column_name,...)
[index_option] ...
| {FULLTEXT} [INDEX|KEY] [index_name] (index_column_name,...)
[index_option] ...
| [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (index_column_name,...) reference_definition
| CHECK (expression)
column_definition:
data_type [NOT NULL | NULL] [DEFAULT default_value]
[AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY]
[COMMENT 'message']
[COLUMN_FORMAT {FIXED|DYNAMIC|DEFAULT}]
[STORAGE {DISK|MEMORY|DEFAULT}]
[reference_definition]
index_column_name:
column_name [(length)] [ASC | DESC]
index_type:
USING {BTREE | HASH}
reference_definition:
REFERENCES table_name (index_column_name,...)
[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE]
[ON DELETE reference_option]
[ON UPDATE reference_option]
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION
O tabelă este reprezentată sub forma unui fișier ''.frm'' în directorul corespunzător bazei de date din care face parte. De asemenea, în funcție de motorul de stocare, pot fi create și alte fișiere pentru fiecare tabelă în parte.
Clauza ''TEMPORARY'' specifică o tabelă care va exista doar pe perioada sesiunii curente, urmând a fi ştearsă în momentul în care aceasta este închisă.
Clauza ''IF NOT EXISTS'' previne producerea unor erori în situaţia în care există deja o tabelă cu acelaşi nume în baza de date implicită. Totuși, nu se realizează nici o verificare cu privire la identitatea dintre structura precizată în instrucțiune și structura tabelei existente.
Precizarea unui atribut implică specificarea mai multor proprietăți:
* tipul de date, specificat de ''data_type'' sau ''spatial_type'' poate lua orice valoare din cele disponibile (numerice, șiruri de caractere, date calendaristice, extensii pentru date spațiale);
* clauzele ''NULL'' (implicit) / ''NOT NULL'' controlează permisiunea ca atributul în cauză să accepte și valori nule;
* clauza ''DEFAULT'' specifică valoarea implicită pe care o va avea câmpul respectiv în cazul care nu se indică altfel, aceasta trebuind să fie o constantă (nu o expresie) sau ''CURRENT_TIMESTAMP'' (pentru o coloană având tipul ''DATETIME''; pentru câmpurile de tip ''BLOB'' sau ''TEXT'' nu se poate asocia o valoare implicită;
* clauza ''AUTO_INCREMENT'' implică completarea automată cu următoarea secvență din atributul respectiv (de regulă, incrementare cu 1) dacă se introduce o valoare ''NULL'' sau valoarea 0; se aplică doar pentru tipurile de date întregi sau reale;
* clauza ''COMMENT'' permite documentarea unui atribut printr-un text care nu poate depăşi 1024 de caractere;
* clauza ''COLUMN_FORMAT'' precizează spaţiul de memorie ce se va aloca, acesta putând fi fix (''FIXED''), dinamic (''DYNAMIC'') sau stabilit în funcţie de tipul de dată specificat (''DEFAULT''); implementarea curentă ignoră această clauză;
* clauza ''STORAGE'' specifică locaţia unde va fi stocată informaţia reţinută de câmpul respectiv în cazul mediilor distribuite: pe disc - ''DISK'' sau în memorie - ''MEMORY'' (implicit).
* clauzele ''KEY'' și ''INDEX'' sunt sinonime și denotă un atribut de tip index (un index poate fi creat şi prin comanda ''CREATE INDEX'' şi şters prin comanda ''DROP INDEX'')
* un index cu clauza ''UNIQUE'' impune ca toate valorile câmpului respectiv să fie distincte între ele; sunt permise însă mai multe valori de tip ''NULL'' într-un astfel de atribut, dacă nu se specifică explicit altfel;
* pentru tipurile de date ''CHAR'', ''VARCHAR'', ''BINARY'', ''VARBINARY'' pot fi specificaţi indecşi doar pentru prefixurile valorilor memorate, a căror dimensiune este specificată după denumirea propriu-zisă a atributului ''column_name(length)'', unde ''length'' este dată ca număr de caractere pentru tipurile de date non-binare și ca număr de octeți pentru tipurile de date binare;
* clauzele ''ASC'' / ''DESC'', referitoare la indecși, sunt parsate dar ignoratăemomentan (ordonarea indecşilor se face crescător indiferent de valoarea precizată);
* clauza ''FULLTEXT'' face ca indexarea să se realizeze după întreaga valoare a atributului, opţiunea fiind folosită pentru căutări în text, fiind însă disponibilă numai pentru atribute de tipul ''CHAR'', ''VARCHAR'' și ''TEXT'';
* unele motoare de căutare permit specificarea tipului de index, putând fi folosite arbori B+ (''BTREE'') sau o funcție hash (''HASH'').
* constrângerile asupra câmpurilor ce ar putea fi specificate prin clauza ''CHECK'' nu sunt încă implementate în versiunea MySQL curentă, astfel încât impunerea unor astfel de condiţii trebuie realizată folosind alte mecanisme pe care le pune la dispoziţie sistemul de gestiune pentru baze de date şi anume trigger-ele.
ALTER [IGNORE] TABLE tbl_name
[alter_specification [, alter_specification] ...]
alter_specification:
| ADD [COLUMN] column_name column_definition [FIRST | AFTER column_name ]
| ADD [COLUMN] (column_name column_definition,...)
| ADD {INDEX|KEY} [index_name]
[index_type] (index_column_name,...) [index_option] ...
| ADD [CONSTRAINT [symbol]] PRIMARY KEY
[index_type] (index_column_name,...) [index_option] ...
| ADD [CONSTRAINT [symbol]] UNIQUE [INDEX|KEY] [index_name]
[index_type] (index_column_name,...) [index_option] ...
| ADD FULLTEXT [INDEX|KEY] [index_name] (index_column_name,...) [index_option] ...
| ADD [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (index_column_name,...)
reference_definition
| ALTER [COLUMN] column_name {SET DEFAULT literal | DROP DEFAULT}
| CHANGE [COLUMN] old_column_name new_column_name column_definition
[FIRST|AFTER column_name]
| MODIFY [COLUMN] column_name column_definition [FIRST | AFTER column_name]
| DROP [COLUMN] column_name
| DROP PRIMARY KEY
| DROP {INDEX|KEY} index_name
| DROP FOREIGN KEY fk_symbol
| DISABLE KEYS
| ENABLE KEYS
| RENAME [TO|AS] new_table_name
| ORDER BY column_name [, column_name] ...
| CONVERT TO CHARACTER SET charset_name [COLLATE collation_name]
| [DEFAULT] CHARACTER SET [=] charset_name [COLLATE [=] collation_name]
Prin intermediul instrucţiunii ''ALTER TABLE'' se pot adăuga sau şterge coloane, se poate schimba tipul de date sau dimensiunea unei coloane, se pot adăuga sau şterge constrângeri de tip cheie primară sau străină respectiv index, se poate redenumi o coloană sau chiar tabela însăşi.
De regulă, instrucțiunea ''ALTER TABLE'' crează o copie temporară a tabelei, operează modificările asupra acesteia și atunci când acestea sunt pregătite, șterge tabela originală și redenumește tabela în care au fost realizate actualizările.\\ Operațiile de citire realizate din alte sesiuni sunt disponibile pe tabela originală până la momentul în care se realizează transferul modificărilor.\\ Operațiile de scriere din alte sesiuni sunt amânate până ce se realizează actualizările specificate.
=== RENAME TABLE ===
Redenumirea uneia sau mai multor tabele se poate face şi prin intermediul comenzii ''RENAME'', executată atomic, astfel încât nici o altă sesiune nu pot accesa tabelele implicate în timpul execuției acesteia. Comanda poate fi folosită pentru interschimbarea denumirilor a două tabele sau pentru mutarea unei baze de date în alta (cu condiția ca acestea să fie stocate folosind același sistem de fișiere).
RENAME TABLE table_name TO new_table_name
[, table_name2 TO new_table_name2] ...
Operația este realizată cu succes cu condiția să nu există triggere definite în cadrul tabelei care este redenumită, să nu existe tabele blocate sau tranzacții în desfășurare și să nu fie implicate tabele temporare.
Vizualizările sunt migrate odată cu procesul de redenumire. În schimb, drepturile de acces (privilegiile) existente nu vor fi păstrate.
În situația în care sunt redenumite mai multe tabele simultan, acestea sunt procesate de la stânga la dreapta. Dacă una dintre operațiile de redenumire eșuează, se restaurează starea anterioară, de dinaintea execuției comenzii.
=== DROP TABLE ===
Ştergerea unei tabele (atât a structurii cât și a conținutului) se face prin comanda ''DROP TABLE'':
DROP [TEMPORARY] TABLE [IF EXISTS]
tbl_name [, tbl_name] ...
[RESTRICT | CASCADE]
Clauza ''TEMPORARY'' șterge tabelele temporare, fără a avea nici un impact asupra tranzacțiilor în desfășurare la momentul respectiv și fără a mai verifica drepturile de acces.
Clauzele ''RESTRICT'' și ''CASCADE'' nu au nici un efect, având rolul de a asigura compatibilitatea cu standardul SQL.
=== SHOW TABLES ===
Vizualizarea listei ce conține tabelele (non-temporare) precum şi vizualizările existente în baza de date implicită poate fi realizată prin intermediul comenzii:
SHOW [FULL] TABLES [{FROM | IN} database_name]
[LIKE 'pattern' | WHERE expression]
Clauza ''FULL'' determină afișarea tipului de obiect (''BASE TABLE'' pentru tabelă și ''VIEW'' pentru vizualizare).
Clauza ''LIKE'' permite filtrarea rezultatelor, luând în considerare denumirile care respectă un anumit șablon (expresie regulată).
Clauza ''WHERE'' permite specificarea de condiții folosind atributele incluse în rezultat (în cazul de față, ''Tables_in_database_name'').
=== DESCRIBE ===
Instrucțiunea ''DESCRIBE'' are un efect similar cu ''SHOW COLUMNS'' și oferă informații cu privire la structura tabelei specificate.
DESCRIBE table_name;
Informațiile conținute în rezultatul instrucțiunii ''DESCRIBE'' sunt denumirea atributului, tipul de date asociat, permisiunea de a lua valori de tip ''NULL'', proprietatea de index (''PRI'' - cheie primară, ''UNI'' - index unic, ''MUL'' - index nonunic ce permite mai multe ocurențe ale unei aceleiași valori), valoarea implicită precum și alte proprietăți (''auto_increment'', ''on update CURRENT_TIMESTAMP'').
=== Exemplu ===
Se doreşte crearea unei baze de date ce conţine informaţii despre animalele de companie tratate în cadrul unei clinici veterinare.
Pentru fiecare animal se vor reține informații cu privire la nume, rasă, gen, data naşterii şi eventual data morţii, aparținător și opțional despre părinți (dacă se află și aceștia în evidența aceleiași unități).
De asemenea, pentru fiecare animal se va stoca fișa medicală conținând fiecare consultație, unde se va preciza data la care a fost efectuată, medicul care a realizat examinarea, diagnosticul pus și tratamentul aplicat.
Se proiectează schema conceptuală a bazei de date în care se evidențiază structura fiecărei tabele, cu cheile primare și legăturile de tip cheie străină:
{{ :laboratoare:laborator00b:09_database_conceptual_scheme.png?nolink&600 }}
Pe baza acesteia se emit instrucțiunile pentru crearea bazei de date și a tabelelor componente, alegându-se tipuri de date corespunzătoare pentru atribute și impunându-se constrângerile de integritate necesare:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] table_name
[(column_name,...)]
{VALUES | VALUE} ({expression | DEFAULT},...),(...),...
[ ON DUPLICATE KEY UPDATE
column_name=expression
[, column_name=expression] ... ]
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] table_name
SET column_name={expression | DEFAULT}, ...
[ ON DUPLICATE KEY UPDATE
column_name=expression
[, column_name=expression] ... ]
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] table_name
[(column_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE
column_name=expression
[, column_name=expression] ... ]
Trebuie să existe o corespondență 1-la-1 între denumirile coloanelor și valorile specificate. În cazul în care nu sunt indicate explicit denumirile coloanelor pentru care sunt introduse datele, clauza ''VALUES'' / ''VALUE'' trebuie să specifice valori pentru toate atributele tabelei (în ordinea specificată la definirea tabelei).
Clauza ''SET'' specifică valori pentru fiecare câmp al tabelei care se doreşte completat.
Prin cuvântul-cheie ''DEFAULT'' se precizează faptul că valoarea coloanei este cea implicită. Un comportament similar se obţine atunci când instrucţiunea se apelează fără parametri atât în privinţa numelor de coloane cât şi a valorilor.
USE veterinary_clinic;
INSERT LOW_PRIORITY INTO breed (name, description) VALUES
('dog', 'The domestic dog (Canis lupus familiaris, or Canis familiaris) is a member of the Canidae family of the mammalian order Carnivora. The term \"domestic dog\" is generally used for both domesticated and feral varieties. The dog was the first domesticated animal and has been the most widely kept working, hunting, and pet animal in human history. The word \"dog\" can also refer to the male of a canine species, as opposed to the word \"bitch\" which refers to the female of the species.'),
('cat','The domestic cat (Felis catus or Felis silvestris catus) is a small, usually furry, domesticated, and carnivorous mammal. It is often called a housecat when kept as an indoor pet, or simply a cat when there is no need to distinguish it from other felids and felines. Cats are often valued by humans for companionship, and their ability to hunt vermin and household pests.'),
('guinea pig', NULL),
('horse', 'The horse (Equus ferus caballus) is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature into the large, single-toed animal of today. Humans began to domesticate horses around 4000 BC, and their domestication is believed to have been widespread by 3000 BC. Horses in the subspecies caballus are domesticated, although some domesticated populations live in the wild as feral horses. These feral populations are not true wild horses, as this term is used to describe horses that have never been domesticated, such as the endangered Przewalski\'s horse, a separate subspecies, and the only remaining true wild horse. There is an extensive, specialized vocabulary used to describe equine-related concepts, covering everything from anatomy to life stages, size, colors, markings, breeds, locomotion, and behavior.'),
('gold fish', NULL);
INSERT IGNORE INTO owner
SET first_name='Sarah', last_name='LEE', email='sarah.lee@lavabit.com', phone_number='123456789', bank_account='US01AABB0000000001';
INSERT IGNORE INTO owner
SET first_name='William', last_name='LOPEZ', email='william.lopez@aim.com', phone_number='456789123', bank_account='UK02CCDD0000000002';
INSERT IGNORE INTO owner
SET first_name='Samuel', last_name='JONES', email='samuel.jones@fastmail.com', phone_number='789123456', bank_account='DE03EEFF0000000003';
INSERT IGNORE INTO owner
SET first_name='William', last_name='THOMPSON', email='william.thompson@lmyway.com', phone_number='123789456', bank_account='FR04GGHH0000000004';
INSERT IGNORE INTO owner
SET first_name='Richard', last_name='MARTIN', email='richard.martin@myspace.com', phone_number='789456123', bank_account='IT05IIJJ0000000005';
INSERT HIGH_PRIORITY INTO doctor(first_name, last_name, title, speciality, code, email, phone_number)
SELECT d.first_name, d.last_name, t.name, s.name, d.code, d.email, d.phone_number
FROM physician_catalog.doctor d, physician_catalog.title t, physician_catalog.speciality s WHERE t.id = d.title_id AND s.id = d.speciality_id;
INSERT INTO diagnosis VALUES
(NULL, 'giardiasis', 'Giardiasis is a protozoal, parasitic, gastrointestinal zoonotic disease in humans and domestic and wild animals.', 'medium', 'nitroimidazole derivatives, benzimidazole compounds or acridine dyes'),
(NULL, 'rabies', 'Rabies is a severely fatal, viral, neurological disease of mammals.', 'high', 'There is no treatment once the clinical signs appear.'),
(NULL, 'dermatophytosis', 'Dermatophytosis is a fungal skin disease that commonly affects humans as well as wild and domestic animals', 'low', 'Dermatophyte infections are treated with a variety of topical and oral antifungal drugs.'),
(NULL, 'mycobacteriosis', 'Mycobacteriosis is a bacterial, systemic, granulomatous skin disease that occurs in aquarium and culture food fish and can affect humans.', 'low', ' Antibiotic therapy may be warranted to prevent progression to deep infection.'),
(NULL, 'malignant catarrhal fever', 'Malignant catarrhal fever (MCF) is a serious, often fatal, viral disease affecting cattle, bison, deer, moose, exotic ruminants, and pigs.', 'severe', 'There is no cure discovered so far');
=== LOAD DATA INFILE ===
Adăugarea de informaţii într-o tabelă se poate face și prin instrucţiunea ''LOAD DATA INFILE'', caz în care sunt introduse date direct din fişier, la o viteză foarte mare, specificându-se totodată şi formatul datelor din fişier (pentru specificarea valorilor de tip NULL din cadrul unui fişier, se va completa valoarea '\N'.):
LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name'
[REPLACE | IGNORE]
INTO TABLE table_name
[CHARACTER SET charset_name]
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']]
[IGNORE number {LINES | ROWS}]
[(column_name_or_user_var,...)]
[SET column_name = expression,...]
Instrucţiunea presupune că toate datele din fişier respectă acelaşi set de caractere indiferent de tipul de date pe care îl au câmpurile din tabela unde se doresc introduse, astfel încât trebuie verificat faptul că acestea folosesc un set de caractere suportat (nu pot fi încărcate fişiere care folosesc seturile de caractere ''ucs2'', ''utf16'', ''utf16le'' sau ''utf32'').
Comportamentul în cazul folosirii clauzei ''LOW_PRIORITY'' este acelaşi cu al instrucţiunii ''INSERT'', iar în cazul ''CONCURRENT'' este permis accesul altor fire de execuţie asupra tabelei.
Dacă este folosit cuvântul-cheie ''LOCAL'', atunci fişierul este citit de client de pe maşina locală (putând fi indicată atât calea absolută în care se găseşte fişierul cât şi o cale relativă la locaţia de unde a fost lansat în execuţie clientul) şi transmis către server care realizează o copie a acestuia în directorul temporar al sistemului de operare. În cazul în care nu se specifică această clauză, fişierul trebuie să existe pe server la locaţia absolută / relativă indicată. Încărcarea unui fişier de la client este mai lentă întrucât conţinutul său trebuie transmis prin intermediul reţelei de calculatoare, însă execuţia nu este oprită în cazul producerii de erori (violări ale constrângerilor de integritate), acestea fiind semnalate ca avertismente după terminarea operaţiei.
Clauzele ''REPLACE'' şi ''IGNORE'' reglează comportamentul în cazul nerespectării constrângerilor de integritate de tip ''PRIMARY KEY'' şi ''UNIQUE''. În cazul ''REPLACE'', înregistrările ce au aceleaşi valori ale câmpurilor specificate drept cheie primară sau index unic sunt înlocuite iar în cazul ''IGNORE'' acestea sunt trecute cu vederea, fără a se genera o eroare. În cazul în care nu se precizează nici unul dintre aceste cuvinte-cheie, comportamentul va fi dat de clauza ''LOCAL'' (dacă nu este specificată se va genera eroare pentru valorile duplicate, dacă există valorile vor fi ignorate, continându-se execuţia).
Dacă se folosesc atributele ''FIELDS'' | ''COLUMNS'' sau ''LINES'', ele trebuie însoţite de cel puţin unul dintre câmpurile ''TERMINATED BY'', ''ENCLOSED BY'', ''ESCAPED BY'', ''STARTING BY''. Dacă aceste clauze nu sunt specificate, comportamentul implicit este:
FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\'
LINES TERMINATED BY '\n' STARTING BY ''
De asemenea, clauza ''IGNORE'' permite omiterea unui număr de rânduri / linii de la începutul fişierului.
În cazul în care nu sunt specificate coloanele tabelului, datele din fişier trebuie să corespundă structurii acestora. Dacă ordinea din fişier este diferită sau acesta conţine numai anumite valori, coloanele trebuie indicate explicit pentru ca asocierea să se facă corect. De asemenea, în loc de nume de coloane pot fi specificate nume de variabile (precedate de caracterul '@') ce se pot folosi ulterior pentru stabilirea valorii unui atribut prin intermediul unei expresii, utilizând clauza ''SET''. Restricţiile cu privire la utilizarea clauzei ''SET'' se referă la faptul că atribuirile nu pot referi decât coloane ale tabelei, valorile asociate putând fi constante sau valori scalare obţinute în urma unor (sub)interogări care nu pot viza însă tabela în care sunt încărcate informaţiile.
Dacă la încărcare sunt identificate prea puţine câmpuri faţă de numărul de atribute al tabelei, coloanele rămase vor fi completate cu valorile implicite. În cazul în care nu au fost specificate valori implicite pentru câmpul respectiv, vor fi completate valorile implicite corespunzătoare tipului de date asociate atributului: 0 pentru date numerice şi date de tip calendaristic respectiv şirul vid (%%''%%) pentru tipul de date şir de caractere. Dacă există prea multe câmpuri în fişier faţă de cele aşteptate, după completarea tuturor atributelor din coloană, datele suplimentare existente pe linia respectivă vor fi ignorate.
Toate valorile sunt considerate şiruri de caractere astfel că nu se vor putea încărca valori de tip numeric pentru câmpuri care au tipul de date ''ENUM'' sau ''SET''. Așadar, este necesar ca acestea să fie definite ca şiruri de caractere pentru a putea fi folosite împreună cu instrucţiunea ''LOAD DATA INFILE''.
**Exemple.** Exemple de populare a tabelelor definite anterior folosind instrucțiuni de tip ''LOAD DATA INFILE'' ar putea fi:
USE veterinary_clinic;
LOAD DATA LOCAL INFILE 'C:\\Users\\Aipi2014\\animals.txt'
INTO TABLE animal
FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\r\n' STARTING BY ''
(name, breed_id, gender, birth_date, owner_id, father_id, mother_id);
LOAD DATA LOCAL INFILE '/home/aipi2014/medical_records.txt'
INTO TABLE medical_record
FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\r\n' STARTING BY ''
(animal_id, doctor_id, diagnosis_id)
SET date=CURRENT_DATE, treatment='-';
=== UPDATE ===
Modificarea înregistrărilor din tabelă se face prin instrucţiunea ''UPDATE'', care poate fi folosită pentru actualizarea informaţiilor din mai multe tabele simultan (situație în care clauzele ''ORDER BY'' şi ''LIMIT'' nu vor mai putea fi folosite; între tabelele în cauză trebuie să existe o relaţie de tip cheie străină, ea fiind condiţia de identificare a înregistrărilor din clauza ''WHERE''):
UPDATE [LOW_PRIORITY] [IGNORE] table_reference
SET column_name1={expression1|DEFAULT} [, column_name2={expression2|DEFAULT}] ...
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]
UPDATE [LOW_PRIORITY] [IGNORE] table_references
SET column_name1={expression1|DEFAULT} [, column_name2={expression2|DEFAULT}] ...
[WHERE where_condition]
Sunt actualizate datele aferente coloanelor din clauza ''SET'' dintr-una sau mai multe tabele, care îndeplinesc condiţia specificată în clauza ''WHERE''. În cazul în care clauza ''WHERE'' nu este precizată, vor fi actualizate toate înregistrările tabelei (exceptând cazul în care numărul înregistrărilor ce vor fi modificate este condiţionat prin clauza ''LIMIT'').
Rezultatul pe care îl întoarce instrucțiunea este numărul de înregistrări care au fost actualizate.
Modificatorii ''LOW_PRIORITY'' şi ''IGNORE'' au aceeaşi semnificaţie ca în cazul celorlalte instrucţiuni, amânând operaţia până la momentul în care nu mai există alte cereri de acces asupra tabelei respectiv continuând execuţia comenzii chiar dacă se produc unele erori.
Expresia ''SET'' precizează coloanele care trebuie modificate și valorile care le vor fi atribuite (fie o expresie, fie cuvântul cheie ''DEFAULT'' prin care li se va asocia valoarea implicită a atributului / tipului de dată respectiv).
Clauza ''WHERE'' poate folosi:
* operatori aritmetici (''+'', ''-'', ''*'', ''/'', ''%'');
* operatori pentru comparaţie (''='', ''<>'' sau ''!='', ''<'', ''<='', ''>'', ''>='', ''BETWEEN'', ''IN'', ''IS NULL'', ''IS NOT NULL'', ''LIKE'', ''REGEXP'', ''SOUNDS LIKE'');
* operatori logici (''NOT'' sau ''!'', ''AND'' sau ''&&'', ''OR'' sau ''||'', ''XOR'');
* operatori pe biţi (''&'', ''|'', ''%%<<%%'', ''%%>>%%'', ''^'');
Clauza ''ORDER BY'' are drept efect ordonarea procesului de actualizare putând primi ca parametri atributele ''ASC'' sau ''DESC''. O astfel de opţiune poate fi utilă în cazul în care procesarea înregistrărilor în altă ordine ar putea genera încălcări ale constrângerilor de integritate.
Clauza ''LIMIT'' stabileşte numărul de înregistrări care satisfac clauza ''WHERE'' după care se încheie procesul de actualizare, indiferent dacă modificarea acestora a avut sau nu a avut loc.
UPDATE LOW_PRIORITY animal SET birth_date=CURRENT_DATE
WHERE YEAR(CURRENT_TIMESTAMP) - YEAR(birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(birth_date))) < 0
ORDER BY birth_date DESC;
UPDATE medical_record mr, diagnosis d
SET mr.treatment = d.cure
WHERE mr.diagnosis_id = d.id;
=== DELETE ===
Ştergerea unor înregistrări dintr-una sau mai multe tabele se face apelând instrucțiunea DELETE:
DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM table_name
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
table_name[.*] [, table_name[.*]] ...
FROM table_references
[WHERE where_condition]
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
FROM table_name[.*] [, table_name[.*]] ...
USING table_references
[WHERE where_condition]
Instrucţiunea ''DELETE'' şterge un număr de înregistrări dintr-o tabelă care îndeplinesc condiţia specificată în clauza ''WHERE''. Dacă se dorește ștergerea tuturor înregistrărilor dintr-o tabelă, este mai eficient să se utilizeze instrucțiunea ''TRUNCATE TABLE'' decât ''DELETE'' fără parametrul ''WHERE''.
Comanda întoarce numărul de înregistrări care sunt eliminate din tabelă.
Pentru a adresa diferite probleme de performanţă pe care le poate implica această comandă, se poate specifica constrângerea ''LIMIT'' ce permite ca doar un număr de înregistrări să fie şterse. Totodată, poate fi controlată ordinea în care este executată operaţia prin clauza ''ORDER BY''.
Clauzele ''LOW_PRIORITY'' şi ''IGNORE'' au aceeaşi semnificaţie ca şi în cazul celorlalte instrucţiuni de manipulare a datelor, în timp ce atributul ''QUICK'' poate avea un impact pentru viteza cu care este executată operaţia de ştergere prin faptul că se evită compactarea nodurilor de tip frunză pentru arborii B+ asociaţi unor indecşi. În situaţia în care se şterg numeroase înregistrări din tabelă, este recomandat să se folosească clauza ''QUICK'', urmată de instrucţiunea ''OPTIMIZE_TABLE'' care reconstruieşte indecşii.
Pentru câmpurile care au fost definite folosind atributul ''AUTO_INCREMENT'' valorile care au fost şterse nu vor fi alocate în cazul când se adaugă noi valori. Pentru unele motoare ale sistemului de baze de date MySQL, se pot reseta aceste valori în situaţia în care se şterge întregul conţinut al tabelei respective.
Instrucţiunea ''DELETE'' poate fi folosită inclusiv pentru ştergerea de date din mai multe tabele în funcţie de condiţia din clauza ''WHERE''. Există 2 sintaxe pentru această operaţie, una eliminând rândurile din tabelele de dinaintea clauzei ''FROM'', cealaltă eliminând înregistrările din tabelele precizate în clauza ''FROM''. În cadrul acestei operaţii căutările pot implica şi alte tabele.
DELETE QUICK breed, animal
FROM breed, animal
WHERE animal.breed_id = breed.id AND breed.description IS NULL;
DELETE LOW_PRIORITY IGNORE FROM animal, owner
USING animal INNER JOIN owner ON animal.owner_id=owner.id
WHERE animal.death_date IS NOT NULL;
=== SELECT ===
Interogarea uneia sau mai multe tabele dintr-o bază de date se realizează prin instrucțiunea ''SELECT'':
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expression [, select_expression ...]
[FROM table_references
[WHERE where_condition]
[GROUP BY {column_name | expression | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {column_name | expression | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
export_options
| INTO DUMPFILE 'file_name'
| INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]
O instrucţiune de tip ''SELECT'' trebuie să includă cel puţin o expresie, aceasta nefiind legată în mod necesar de atributele unei tabele. Ea poate include coloanele uneia sau mai multor tabele (eventual prefixate de numele tabelelor din care fac parte, în caz de ambiguitate). O expresie poate avea asociat un alias (introdus prin cuvântul-cheie ''AS'' (opţional) şi o denumire), acesta putând fi folosit ulterior pentru expresiile ''GROUP BY'', ''HAVING'' sau ''ORDER BY'', dar nu şi pentru ''WHERE'' (pentru că este posibil să nu fi fost evaluat la momentul respectiv). Dacă se dorește vizualizarea de informații cu privire la toate câmpurile unei / unor tabele, se va folosi masca ''*''.
SELECT a.name AS name,
b.name AS breed,
a.gender AS gender,
YEAR(CURRENT_TIMESTAMP) - YEAR(a.birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(a.birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(a.birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(a.birth_date))) AS age,
CONCAT(o.first_name, ' ' , o.last_name) AS owner,
COALESCE((SELECT name FROM animal where id=COALESCE(a.father_id,'0')),'-') AS father,
COALESCE((SELECT name FROM animal where id=COALESCE(a.mother_id,'0')),'-') AS mother,
(SELECT GROUP_CONCAT(DISTINCT d.name) FROM diagnosis d, medical_record mr WHERE d.id = mr.diagnosis_id AND mr.animal_id=a.id) AS diseases,
(SELECT GROUP_CONCAT(DISTINCT CONCAT(d.first_name,' ', d.last_name)) FROM doctor d, medical_record mr WHERE d.id = mr.doctor_id AND mr.animal_id=a.id) AS physicians
FROM animal a, breed b, owner o
WHERE b.id = a.breed_id AND o.id = a.owner_id
HAVING age > 0;
+-------+-------+--------+------+------------------+--------+--------+---------------------------+----------------------------------------------+
| name | breed | gender | age | owner | father | mother | diseases | physicians |
+-------+-------+--------+------+------------------+--------+--------+---------------------------+----------------------------------------------+
| Rocky | dog | M | 13 | William THOMPSON | - | - | malignant catarrhal fever | Harry LEWIS,Thomas CARTER |
| Koby | dog | M | 9 | William THOMPSON | Rocky | - | NULL | NULL |
| Hutch | horse | M | 4 | Richard MARTIN | - | - | rabies | Richard ALLEN,Rachel RODRIGUEZ,Thomas CARTER |
+-------+-------+--------+------+------------------+--------+--------+---------------------------+----------------------------------------------+
3 rows in set (0.00 sec)
== Joncțiuni între tabele ==
O interogare poate fi făcută pe mai multe tabele din baza de date între care există o relaţie, o astfel de operație purtând numele de joncțiune (//eng.// join).
În MySQL, sintaxa pentru realizarea unor astfel de asocieri este:
table_reference:
table_factor
| join_table
table_factor:
table_name [PARTITION (partition_names)]
[[AS] alias] [index_hint_list]
| table_subquery [AS] alias
| ( table_references )
join_table:
table_reference [INNER | CROSS] JOIN table_factor [join_condition]
| table_reference STRAIGHT_JOIN table_factor
| table_reference STRAIGHT_JOIN table_factor ON conditional_expr
| table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition
| table_reference NATURAL [{LEFT|RIGHT} [OUTER]] JOIN table_factor
join_condition:
ON conditional_expr
| USING (column_list)
Există mai multe tipuri de joncțiuni ce se pot stabili între tabele:
* **cross-join**, rezultatul fiind produsul cartezian al tuplurilor din cadrul tabelelor implicate în operaţie; fie ''t1'' şi ''t2'' tabelele pentru care se realizeaza cross-join, având ''m'' şi respectiv ''n'' înregistrări; rezultatul interogării conţine ''m x n'' înregistrări;
SELECT * FROM animal CROSS JOIN owner;
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
| id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id | id | first_name | last_name | email | phone_number | bank_account |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
35 rows in set (0.00 sec)
* **inner-join**, ce include înregistrările din tabele pentru care există corespondențe în privința atributului pe care se face asocierea;
SELECT * FROM animal INNER JOIN owner ON animal.owner_id = owner.id
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+--------------------+
| id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id | id | first_name | last_name | email | phone_number | bank_account |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+--------------------+
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+--------------------+
6 rows in set (0.00 sec)
Tot tipuri de inner-join sunt și:
* **equi-join**, în care condiția dintre atributele prin intermediul cărora se face asocierea dintre tabele trebuie să fie reprezentată de o operație de egalitate;
* **natural-join**, care încearcă să realizeze asocierea dintre tabele pe baza unui atribut având aceeași denumire; în situația în care acesta nu este identificat, rezultatul este produsul cartezian, motiv pentru care folosirea acestui tip de joncțiune nu este foarte răspândită.
SELECT * FROM animal LEFT OUTER JOIN owner ON animal.owner_id = owner.id;
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+------+------------+-----------+-----------------------------+--------------+--------------------+
| id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id | id | first_name | last_name | email | phone_number | bank_account |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+------+------------+-----------+-----------------------------+--------------+--------------------+
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 4 | Hutch | 4 | M | 2010-03-20 11:00:00 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL |
+----+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+------+------------+-----------+-----------------------------+--------------+--------------------+
7 rows in set (0.00 sec)
\\ Sunt incluse toate înregistrările din tabela ''animal'', pentru cele care nu au corespondent în tabela ''owner'' completându-se atributele respective cu valori ''NULL''.
* right
SELECT * FROM animal RIGHT OUTER JOIN owner ON animal.owner_id = owner.id;
+------+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
| id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id | id | first_name | last_name | email | phone_number | bank_account |
+------+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
| 5 | Logan | 3 | M | 2020-09-20 13:00:00 | NULL | 1 | NULL | NULL | 1 | Sarah | LEE | sarah.lee@lavabit.com | 123456789 | US01AABB0000000001 |
| 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 2 | William | LOPEZ | william.lopez@aim.com | 456789123 | UK02CCDD0000000002 |
| 2 | Cleopatra | 5 | F | 2031-12-20 14:00:00 | NULL | 3 | NULL | NULL | 3 | Samuel | JONES | samuel.jones@fastmail.com | 789123456 | DE03EEFF0000000003 |
| 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 4 | William | THOMPSON | william.thompson@lmyway.com | 123789456 | FR04GGHH0000000004 |
| NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 6 | Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | IT05IIIJJJ00000005 |
+------+-----------+----------+--------+---------------------+------------+----------+-----------+-----------+----+------------+-----------+-----------------------------+--------------+----------------------+
7 rows in set (0.00 sec)
\\ Sunt incluse toate înregistrările din tabela ''owner'', pentru cele care nu au corespondent în tabela ''animal'' completându-se atributele respective cu valori ''NULL''.
În MySQL ''NATURAL JOIN'' este echivalent cu ''INNER JOIN'' sau ''LEFT JOIN'' folosit împreună cu clauza ''USING'' în care sunt incluse toate coloanele care există în ambele tabele.
De asemenea, ''STRAIGHT_JOIN'' este echivalent cu ''JOIN'' cu precizarea că tabela din stânga este citită întotdeauna înaintea tabelei din dreapta, această funcționalitate putând fi utilizată în cazul în care modulul de optimizare foloseşte o ordine greşită.
Clauza ''ON'' specifică câmpurile în funcţie de care se face asocierea dintre tabele, clauza ''USING'' putând fi folosită în situaţia în care denumirea atributelor este aceeaşi în toate tabelele care sunt incluse în această asociere.
Tipuri particulare de asocieri sunt:
* **self-join**, care implică duplicarea tabelei prin utilizarea de alias-uri;
SELECT * FROM animal a1 JOIN animal a2 ON a1.father_id = a2.id OR a1.mother_id = a2.id;
+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+
| id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id | id | name | breed_id | gender | birth_date | death_date | owner_id | father_id | mother_id |
+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+
| 6 | Koby | 1 | M | 2005-06-20 12:00:00 | NULL | 4 | 1 | NULL | 1 | Rocky | 1 | M | 2001-01-20 10:00:00 | NULL | 4 | NULL | NULL |
| 7 | Ivory | 2 | F | 2025-06-20 14:00:00 | NULL | 2 | NULL | 3 | 3 | Lola | 2 | F | 2015-06-20 12:00:00 | NULL | 2 | NULL | NULL |
+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+----+-------+----------+--------+---------------------+------------+----------+-----------+-----------+
* **unions** care presupune adăugarea tuturor înregistrărilor din tabele pentru a determina suma compozită a acestora (numărul de atribute întoarse și denumirea acestora trebuie să fie aceeași pentru a se permite realizarea acestei operații)
SELECT first_name, last_name, email, phone_number, 'Pet Owner' AS role FROM owner
UNION
SELECT first_name, last_name, email, phone_number, 'Physician' AS role FROM doctor;
+------------+-----------+------------------------------+--------------+-----------+
| first_name | last_name | email | phone_number | role |
+------------+-----------+------------------------------+--------------+-----------+
| Sarah | LEE | sarah.lee@lavabit.com | 123456789 | Pet Owner |
| William | LOPEZ | william.lopez@aim.com | 456789123 | Pet Owner |
| Samuel | JONES | samuel.jones@fastmail.com | 789123456 | Pet Owner |
| William | THOMPSON | william.thompson@lmyway.com | 123789456 | Pet Owner |
| Joseph | WILLSON | joseph.willson@fanbox.com | 789456123 | Pet Owner |
| Thomas | CARTER | thomas.carter@google.com | 112233 | Physician |
| Jessica | WHITE | jessica.white@aim.com | 445566 | Physician |
| Rachel | RODRIGUEZ | rachel.rodrigues@hotmail.com | 778899 | Physician |
| Harry | LEWIS | harry.lewis@live.com | 113355 | Physician |
| Richard | ALLEN | richard.allen@space.com | 557799 | Physician |
+------------+-----------+------------------------------+--------------+-----------+
10 rows in set (0.00 sec)
== Interogări imbricate ==
Alternativ la operaţiile de asociere complexe între tabele pot fi folosite interogări imbricate, acestea având avantajul că pot fi înţelese mai uşor având totodată o structură mai flexibilă.
**a.** O **subinterogare** poate întoarce o valoare (un scalar), un rând, o coloană sau un tabel. Ca subinterogări pot fi folosite operaţiile ''INSERT'', ''UPDATE'', ''DELETE'' sau ''SELECT'' cu cele mai multe dintre clauzele lor. O restricţie ce se aplică în acest caz este reprezentată de faptul că într-o interogare internă nu se pot realiza operaţii asupra unei tabele care este folosită şi în interogarea externă.
Cel mai frecvent, subinterogările sunt folosite pentru a realiza comparaţii, având forma:
non_subquery_operand comparison_operator (subquery)
în cazul în care subinterogarea întoarce o singură valoare.
De regulă, operatorii de comparaţie folosiți sunt %%''=''%%, %%''<''%%, %%''>''%%, %%''<=''%%, %%''>=''%%, %%''<>''%%, %%''!=''%%.
**Exemplu**. Determinarea animalului de companie cu vârsta cea mai mare aflat în evidența clinicii veterinare poate fi realizată prin instrucțiunea:
SELECT a.name AS name,
YEAR(CURRENT_TIMESTAMP) - YEAR(a.birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(a.birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(a.birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(a.birth_date))) AS age
FROM animal a
WHERE birth_date = (SELECT MIN(birth_date) FROM animal);
Când subinterogarea întoarce mai multe valori, pot fi folosite cuvintele-cheie ''ANY'', ''SOME'', ''IN'' respectiv ''ALL'':
operand comparison_operator ANY (subquery)
operand IN (subquery)
operand comparison_operator SOME (subquery)
operand comparison_operator ALL (subquery)
''SOME'' este un alias pentru ''ANY''. Comparaţiile incluzând aceste cuvinte-cheie sunt îndeplinite dacă condiţiile sunt satisfăcute pentru unele dintre valorile incluse în mulţimea respectivă. În cazul lui ''ALL'', condiţiile trebuie satisfăcute pentru toate valorile din mulţime. Din acest punct de vedere ''IN'' este un alias pentru ''= ANY'' sau ''= SOME'', în timp ce ''NOT IN'' este un alias pentru ''!= ALL''.
**Exemplu**. Determinarea animalului de companie cu vârsta cea mai mare aflat în evidența clinicii veterinare poate fi realizată si prin instrucțiunea:
SELECT a.name AS name,
YEAR(CURRENT_TIMESTAMP) - YEAR(a.birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(a.birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(a.birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(a.birth_date))) AS age
FROM animal a
WHERE birth_date <= ALL(SELECT birth_date FROM animal);
Pentru subinterogările care pot întoarce unul sau mai multe rânduri pot fi folosite clauzele ''EXISTS'', respectiv ''NOT EXISTS'';
SELECT ... FROM table_name WHERE EXISTS (SELECT ... FROM table_name);
Astfel de subinterogări pot fi folosite spre a afla categoriile de înregistrări care îndeplinesc o anumită condiţie.
**Exemplu**. Determinarea animalului de companie pentru care au existat consultații în cadrul clinicii veterinare poate fi realizată si prin instrucțiunea:
SELECT a.name AS name,
b.name AS breed
FROM animal a INNER JOIN breed b ON a.breed_id=b.id
WHERE EXISTS (SELECT * FROM medical_record mr WHERE mr.animal_id = a.id);
Pentru situaţia în care interogarea internă (subinterogarea) foloseşte informaţii din interogarea externă, se spune că **interogările sunt corelate**.
**b.** **Subinterogările pot fi apelate şi în clauza ''FROM''** în situaţia în care selecţia se face dintr-un subset de date al unei tabele sau dintr-un set de date obţinut prin intermediul unor prelucrări:
SELECT ... FROM (subquery) [AS] name ...
Rezultatul interogării imbricate trebuie să aibă un nume asociat întrucât orice tabelă trebuie să poată fi identificată. De asemenea, orice coloană dintr-o subinterogare trebuie să aibă asociat un nume unic. În această situaţie nu pot fi folosite interogări corelate cu excepţia cazului în care sunt utilizate cu clauza ''ON'' a unei operaţii de tip ''JOIN''.
**Exemplu**. Determinarea speciei cu cea mai mare vârstă medie dintre cele aflate în evidența clinicii veterinare poate fi realizată prin instrucțiunea:
SELECT b.name,
MAX(average_age)
FROM (SELECT a.breed_id,
AVG(YEAR(CURRENT_TIMESTAMP) - YEAR(a.birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(a.birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(a.birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(a.birth_date)))) AS average_age
FROM animal a
GROUP BY a.breed_id) statistics, breed b
WHERE b.id = statistics.breed_id;
Există mai multe optimizări care se pot realiza cu ajutorul subinterogărilor. Ca o regulă generală, este util ca operaţiile de tip join să fie înlocuite prin interogări imbricate. Există şi tipuri de ''LEFT [OUTER] JOIN'' care sunt mai rapide decât subinterogările deoarece sunt optimizate intern. De asemenea, trebuie să se aibă în vedere ca rezultatele întoarse de subinterogări să fie cât mai mic cu putinţă.
== Funcții de grup ==
În MySQL există şi funcţii de grup (funcţii agregate) care pot fi folosite pentru determinarea unor statistici aferente unor seturi de date din cadrul tabelelor:
|< 100% >|
| ''AVG()'' | valoarea medie | ''STD()'' | deviaţia standard a populaţiei |
| ''BIT_AND()'' | AND pe biţi | ''STDDEV_POP()'' | deviaţia standard a populaţiei |
| ''BIT_OR()'' | OR pe biţi | ''STDDEV_SAMP()'' | deviaţia standard a eşantionului |
| ''BIT_XOR()'' | XOR pe biţi | ''STDDEV()'' | deviaţia standard a populaţiei |
| ''COUNT(DISTINCT)'' | numărul valorilor distincte | ''SUM()'' | suma |
| ''COUNT()'' | numărul de rânduri | ''VAR_POP()'' | varianţa standard a populaţiei |
| ''GROUP_CONCAT()'' | şir de caractere concatenat | ''VAR_SAMP()'' | varianţa eşantionului |
| ''MAX()'' | valoarea maximă | ''VARIANCE()'' | varianţa standard a populaţiei |
| ''MIN()'' | valoarea minimă |
**Exemplu**. Determinarea numărului de consultații de care a beneficiat fiecare animal de companie în parte în cadrul clinicii veterinare poate fi realizat prin instrucțiunea:
SELECT a.name AS name,
(SELECT COUNT(*) FROM medical_record mr WHERE mr.animal_id = a.id) AS number_of_examinations
FROM animal a;
===== Obiecte ale bazei de date =====
==== Programe stocate ====
MySQL permite definirea de **programe stocate** ce pot fi clasificate astfel:
* **rutine stocate** (proceduri sau funcţii) invocate prin instrucţiunea ''CALL'';
* **triggere**, obiecte asociate unei tabele care sunt executate atunci când se produc anumite evenimente;
* **evenimente**, sarcini pe care serverul le execută conform unei planificări.
Pentru fiecare dintre aceste tipuri de obiecte există instrucţiuni de tip ''CREATE'', ''ALTER'' şi ''DROP'' asociate care controlează modul în care acestea există şi funcţionează în contextul bazei de date.
=== Rutine stocate ===
O rutină stocată (fie funcţie, fie procedură) are trei componente:
* parametrii de intrare (argumentele);
* parametrii de ieşire (valorile returnate);
* corpul rutinei care conţine instrucţiunile ce trebuie executate, acestea fiind încadrate de cuvintele cheie ''BEGIN'' şi ''END'' şi separate prin caracterul ';'. Întrucât pentru clientul MySQL caracterul ';' reprezintă un delimitator care marchează încheierea unei instrucţiuni, acest tip de delimitator trebuie să fie redefinit astfel încât întreaga definiţie a rutinei stocate să fie transmisă serverului. Acest lucru se face prin intermediul cuvântului-cheie ''DELIMITER''. Un delimitator poate consta dintr-unul sau mai multe caractere.
Pentru a putea crea o rutină, un utilizator trebuie să aibă privilegiul ''CREATE ROUTINE'', iar pentru a o executa, privilegiul ''EXECUTE''.
Diferenţa dintre o procedură şi o funcţie constă în modul prin care rezultatul este întors. O procedură poate realiza modificări asupra tabelelor fără să producă o valoare în mod necesar. În cazul când aceasta trebuie să fie vizibilă către programul care a apelat-o, transferul său se poate realiza prin intermediul parametrilor. O funcţie întoarce rezultat în mod necesar.
Sintaxa pentru creearea acestora este:
CREATE
[DEFINER = { user | CURRENT_USER }]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
CREATE
[DEFINER = { user | CURRENT_USER }]
FUNCTION sp_name ([func_parameter[,...]])
RETURNS type
[characteristic ...] routine_body
proc_parameter:
[ IN | OUT | INOUT ] param_name type
func_parameter:
param_name type
type:
Any valid MySQL data type
characteristic:
COMMENT 'string'
| LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
Clauza ''DEFINER'' asociază rutinei stocate un utilizator ale cărui privilegii vor fi verificate la momentul execuţiei sale.
Dacă valoarea ''SQL SECURITY'' e ''DEFINER'' se verifică privilegiile utilizatorului care a creat procedura stocată, iar dacă este ''INVOKER'' se verifică privilegiile utilizatorului care apelează procedura stocată.
Numele rutinei stocate trebuie să fie diferit de cel al rutinelor SQL standard, în caz contrar producându-se o eroare.
Parametrii unei proceduri stocate pot avea tipurile ''IN'' (implicit), ''OUT'' respectiv ''INOUT'' după cum pot avea rolul de parametrii de intrare, ieşire, respectiv de intrare-ieşire. Aceştia trebuie plasaţi între paranteze.
Toţi parametrii unei funcţii stocate au doar rolul de a transmite date şi tipul lor nu mai trebuie specificat. Lista parametrilor unei funcţii trebuie urmată de cuvântul-cheie ''RETURNS'' care specifică tipul de date al rezultatului pe care aceasta îl întoarce. Dacă rezultatul transmis prin clauza ''RETURN'' are alt tip decât cel specificat în antet, conversia se va realiza în mod automat.
O rutină stocată care nu foloseşte nici un fel de parametri va marca acest fapt prin ''()'' după numele său.
Corpul unei rutine poate conţine orice fel de instrucţiuni DDL sau DML, inclusiv instrucţiuni de control şi bucle.
Prin cuvântul-cheie ''COMMENT'' se introduce o descriere a rutinei, iar clauza ''LANGUAGE'' conţine limbajul în care este scrisă. Această clauză este momentan ignorată, fiind permise numai instrucţiuni SQL.
Clauzele ''DETERMINISTIC'' respectiv ''NON-DETERMINISTIC'' (implicit) descriu comportamentul rutinei stocate. O rutină este deterministică dacă produce întotdeauna aceleaşi rezultate pentru aceiaşi parametri de intrare. Totodată, rutina este nedeterministică dacă la execuţii diferite va rula imprevizibil. Spre exemplu, apelul unor funcţii precum ''NOW()'' sau ''RAND()'' poate determina generarea unui comportament nedeterministic. MySQL nu verifică corectitudinea comportamentului declarat, însă declararea sa greşită poate determina modulul de optimizare să realizeze planuri de execuţie neperformante.
Pentru o rutină pot fi precizate nişte clauze care indică modul în care sunt utilizate datele în cadrul rutinei stocate, fără a limita operaţiile permise:
* ''CONTAINS SQL'' – specifică faptul că rutina stocată nu conţine instrucţiuni care citeşte sau scrie date;
* ''NO SQL'' – arată că rutina stocată nu conţine comenzi SQL;
* ''READS SQL DATA'' – indică faptul că rutina stocată conţine instrucţiuni care citesc date (cum ar fi ''SELECT''), dar nu şi instrucţiuni care le scriu;
* ''MODIFIES SQL DATA'' – precizează faptul că rutina stocată poate conţine instrucţiuni care scriu date (precum ''INSERT'', ''UPDATE'' sau ''DELETE'').
Rutinele stocate trebuie să întoarcă un scalar. De asemenea, în timp ce procedurile pot apela instrucţiuni ce întorc mai multe valori, comportamentul nu este permis în cadrul funcţiilor.
**Exemple**. Determinarea numărului de boli de care a suferit, respectiv a numărului de consultații de care a beneficiat fiecare animal de companie în parte în cadrul clinicii veterinare poate fi realizat prin intermediul unei proceduri, respectiv a unei funcții stocate:
DELIMITER //
CREATE PROCEDURE get_number_of_diseases(
IN animal_id INT,
OUT number_of_diseases INT(2)
)
BEGIN
SELECT COUNT(DISTINCT mr.diagnosis_id) INTO number_of_diseases
FROM medical_record mr INNER JOIN animal a ON mr.animal_id = a.id
WHERE a.id = animal_id;
END; //
CALL get_number_of_diseases(2, @result);
SELECT @result;
DELIMITER //
CREATE FUNCTION get_number_of_examinations(animal_id INT)
RETURNS INT(2)
BEGIN
DECLARE number_of_examinations INT(2);
SELECT COUNT(mr.id) INTO number_of_examinations
FROM medical_record mr INNER JOIN animal a ON mr.animal_id = a.id
WHERE a.id = animal_id;
RETURN number_of_examinations;
END; //
SELECT get_number_of_examinations(2);
=== Triggere ===
Un trigger este definit să se activeze atunci când se produce o operaţie de introducere, modificare sau ştergere a datelor pentru a verifica respectarea unor constrângeri sau pentru a realiza anumite calcule asupra unor valori.
Sintaxa pentru creearea unui astfel de obiect este:
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON table_name FOR EACH ROW
trigger_body
trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }
Pentru fiecare trigger trebuie să se definească tabelul la care se referă precum şi operaţia care va determina lansarea lui în execuţie şi momentul la care se întâmplă aceasta. În MySQL nu pot exista mai multe triggere pentru un tabel având acelaşi tip de eveniment şi acelaşi moment de declanşare.
În corpul unui trigger pot fi folosite alias-urile ''OLD'' pentru a defini valorile de dinaintea unei operaţii de modificare sau ştergere, respectiv ''NEW'', cu referire la valorile de după o operaţie de adăugare sau modificare.
În cazul în care un trigger eşuează, operaţia care îi este asociată nu va fi executată, indiferent de momentul la care acesta este invocat.
Un trigger nu poate folosi apeluri de rutine stocate care întorc valori utilizatorului (pot fi folosite doar proceduri ale căror rezultate pot fi transmise prin parametri de tip ''OUT'' sau ''INOUT'') sau care folosesc SQL dinamic. Totodată nu pot fi folosite operaţii care încep sau termină o tranzacţie (''COMMIT'', ''ROLLBACK'').
**Exemple**. Prin intermediul unor triggere, se poate impune respectarea unor constrângeri de integritate sau se pot corecta anumite anomalii la actualizare:
DELIMITER //
CREATE TRIGGER owner_insert_check
BEFORE INSERT ON owner
FOR EACH ROW
BEGIN
DECLARE message VARCHAR(255);
IF NEW.email IS NOT NULL AND NEW.email NOT LIKE '%@%.%' THEN
SET message = concat('The format of an email should be username@domain.country ', cast(NEW.email AS CHAR));
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = message;
END IF;
END; //
DELIMITER //
CREATE TRIGGER animal_update_check
AFTER UPDATE ON animal
FOR EACH ROW
BEGIN
IF NEW.birth_date > CURRENT_DATE THEN
SET NEW.birth_date = CURRENT_DATE;
ELSEIF YEAR(NEW.birth_date) - YEAR(CURRENT_DATE) > 20 THEN
SET NEW.birth_date = OLD.birth_date;
END IF;
END; //
=== Evenimente ===
Evenimentele sunt sarcini planificate ce sunt executate la un moment dat sau periodic în cazul în care planificatorul de execuţii este pornit. Planificatorul execuţiilor poate avea starea: pornit, oprit sau dezactivat.
De regulă, acestea sunt folosite pentru realizarea unor operaţii de întreţinere fie asupra anumitor tabele din baza de date, fie asupra sistemului de baze de date însuşi.
==== Vizualizări ====
Vizualizările (//eng.// views) sunt interogări stocate care produc rezultate atunci când sunt invocate. De multe ori, ele sunt referite ca tabele virtuale.
Sintaxa pentru creearea unei vizualizări este:
CREATE
[OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
[DEFINER = { user | CURRENT_USER }]
[SQL SECURITY { DEFINER | INVOKER }]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
Operaţia ''SELECT'' din cadrul vizualizării poate include tabele de bază sau alte vizualizări. Orice modificări ar fi realizate asupra tabelelor implicate după momentul definirii vizualizării nu sunt luate în considerare.
Clauzele ''DEFINER'' şi ''SQL SECURITY'' specifică contextul de securitate care va fi utilizat când se verifică privilegiile de acces ale vizualizării atunci când aceasta va fi invocată.
Întrucât tabelele de bază şi vizualizările partajează acelaşi spaţiu de nume denumirile acestora trebuie să fie diferite.
O vizualizare trebuie să definească nume unice pentru coloanele sale, putând fi specificate şi alias-uri.
În definirea unei vizualizări trebuie să se ţină seama de unele constrângeri:
* interogările de tip ''SELECT'' nu pot conţine interogări imbricate în cadrul clauzei ''FROM'';
* interogările de tip ''SELECT'' nu pot conţine variabile de sistem sau definite de utilizator;
* în cadrul unui program stocat definiţia nu poate referi parametrii săi sau variabilele locale;
* orice tabelă de bază sau vizualizare referită trebuie să existe; în situaţia când acestea sunt şterse, utilizarea vizualizării care le foloseşte generează o excepţie;
* nu pot fi referite tabele cu atributul ''TEMPORARY'' şi nici nu pot fi create vizualizări temporare;
* unei vizualizări nu i se poate asocia un trigger;
* alias-urile pentru numele coloanelor din instrucţiunile ''SELECT'' nu trebuie să depăşească 64 de caractere;
* clauza ''ORDER BY'' este permisă în definirea unei vizualizări însă în cazul când aceasta există într-o vizualizare referită, aceasta va avea prioritate.
Atributul ''ALGORITHM'' indică modul în care vizualizarea este procesată. Când este precizată valoarea ''MERGE'', porţiuni din definiţia vizualizării sunt copiate în instrucţiunile care o apelează. Pentru ''TEMPTABLE'', se creează o tabelă temporară în care este transferat conţinutul vizualizării, aceasta fiind folosită atunci când este vizualizarea este invocată în diferite instrucţiuni. În cazul când are valoarea ''UNDEFINED'', sistemul de gestiune pentru baze de date decide ce algoritm va fi utilizat. De regulă, se preferă algoritmul ''MERGE'' în detrimentul algoritmului ''TEMPTABLE'' întrucât este mai eficient şi pentru că în cazul folosirii tabelelor temporare vizualizarea nu poate fi actulizată. Utilizarea algoritmului ''TEMPTABLE'' este justificată doar în situaţia în care se doreşte eliberarea resurselor asociate tabelelor referite în cazul vizualizării.
Unele vizualizări pot fi actualizate în sensul că pot fi folosite în operaţii de tip ''INSERT'', ''UPDATE'', ''DELETE'' pentru a opera modificări în tabelele referite. Pentru ca o vizualizare să poată fi actualizată trebuie să existe o relaţie 1:1 între înregistrările sale şi înregistrările tabelei referite. Există numeroase impedimente care determină ca o vizualizare să nu poată fi actualizată: folosirea funcţiilor agregate (''SUM'', ''MIN'', ''MAX'', ''COUNT'', ''AVG''), utilizarea clauzelor ''DISTINCT'', ''GROUP BY'', ''HAVING'', ''UNION'' / ''UNION ALL'', folosirea interogărilor imbricate în ''SELECT'', anumite operatii de tip jonctiune, invocarea unor vizualizări ce nu pot fi actualizate în clauza ''FROM'', folosirea de interogări imbricate în clauza ''WHERE'' care referă aceeaşi tabelă din clauza ''FROM'', referinţe exclusiv la constante, utilizarea algoritmului ''TEMPTABLE'', referinţe multiple la aceeaşi coloană dintr-o tabelă de bază.
Clauza ''WITH CHECK OPTION'' face ca pentru vizualizările ce pot fi actualizate, operaţiile ''INSERT'' sau ''UPDATE'' să nu poată fi realizate decât dacă este îndeplinită condiţia specificată prin clauza ''WHERE''.
Când vizualizarea referă alte vizualizări, cuvintele-cheie ''LOCAL'' şi ''CASCADED'' (implicit) indică nivelul până la care sunt realizate verificările: doar la nivelul vizualizării definite în mod curent sau şi pentru vizualizările imbricate.
CREATE VIEW pets_per_owner AS
SELECT CONCAT(o.first_name,' ', o.last_name) AS name,
(SELECT GROUP_CONCAT(a.name) FROM animal a WHERE a.owner_id=o.id) AS pets
FROM owner o;
SELECT * FROM pets_per_owner;
===== Instrucțiuni pentru controlul fluxului =====
==== Variabile ====
Pot fi utilizate variabile definite de utilizator, a căror durată de viaţă nu depăşeşte însă sesiunea curentă:
SET @variable_name = expression [, @variable_name = expression] ...
Numele de variabile folosesc caractere alfa-numerice precum şi caracterele ''.'', ''_'' şi ''$'', acesta fiind case in-sensitive.
Atribuirea se poate face fie folosind ''='', fie folosind '':=''.
Variabilele pot lua doar valori de tip numeric sau şir de caractere (binar sau non-binar), însă în cazul numerelor reale poate avea loc o pierdere a preciziei. Atribuirile care folosesc tipuri nepermise vor fi convertite în mod automat către un tip permis. De asemenea, o variabilă poate avea valoarea ''NULL''.
O variabilă ce nu a fost iniţializată are valoarea ''NULL'' iar tipul de date asociat este şir de caractere.
==== IF ====
Sintaxa instrucţiunii ''IF'' este:
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
Dacă condiţia este verificată se execută instrucţiunea de pe ramura ''THEN'', altfel se trece la una din ramurile ''ELSEIF'' sau ''ELSE''.
Are şi variantele ''IFNULL'' şi ''NULLIF'' care primesc ca parametri două expresii:
* ''IFNULL'' întoarce valoarea primei expresii dacă aceasta nu este ''NULL'', altfel returnează valoarea celei de-a doua expresii;
* ''NULLIF'' este evaluată ca ''NULL'' dacă expresiile sunt egale, altfel întoarce valoarea primei expresii.
==== CASE ====
Instrucţiunea ''CASE'' are două variante:
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
în care o valoare este comparată cu mai multe variante, respectiv sunt evaluate mai multe condiţii, urmând ca una dintre aceste ramificații să fie executate.
Dacă nici una dintre ramuri nu este îndeplinită, se rulează comanda asociată ramurii ''ELSE''.
==== LOOP ====
O structură iterativă clasică este ''LOOP'', care are următoarea sintaxă:
[begin_label:] LOOP
statement_list
END LOOP [end_label]
Trecerea la un nou pas al iteraţiei se face prin instrucţiunea ''ITERATE'' care are asociată eticheta ce identifică acest ciclu, ieşirea din iteraţie (dacă este îndeplinită o condiţie) făcându-se prin comanda ''LEAVE'' sau, în cazul în care ciclul este definit în cadrul unei rutine stocate care întoarce valori prin instrucţiunea ''RETURN''.
==== REPEAT ====
Instrucţiunea ''REPEAT'' este tipul de iteraţie care se execută cel puţin o dată, până la îndeplinirea unei condiţii:
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
Întrucât există o condiţie naturală pentru părăsirea ciclului etichetarea acestui tip de instrucţiune nu este absolut necesar.
==== WHILE ====
Comanda ''WHILE'' implementează ciclul cu verficarea iniţială a condiţiei, astfel încât există posibilitatea de a nu se executa niciodată.
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
==== Cursoare ====
Un alt mod de a itera asupra unui set de date obţinut printr-o operaţie de tip ''SELECT'' în cadrul unor rutine stocate sunt **cursoarele**.
Operaţiile curente asociate cursoarelor sunt (în ordinea în care acestea trebuie apelate) sunt:
* declararea;
* deschiderea;
* parcurgerea;
* închiderea.
Caracteristicile unui cursor sunt:
- asenzitivitatea: serverul poate realiza sau nu copii ale tabelului care conţine rezultatele care sunt parcurse;
- proprietatea de a nu putea fi suprascrise;
- proprietatea de a putea fi parcurse într-o singură direcţie şi în ordine.
**1.** **Declararea unui cursor** se realizează după cea a variabilelor şi condiţiilor însă înainte de cea a handle-urilor şi are următoarea sintaxă:
DECLARE cursor_name CURSOR FOR select_statement
Operaţia de tip ''SELECT'' care este asociată cursorului nu poate avea clauza ''INTO''. O rutină stocată poate defini mai multe cursoare însă fiecare trebuie identificat printr-o denumire unică.
**2.** **Deschiderea cursorului** (necesară pentru ca acesta să fie folosit) se face folosind comanda ''OPEN'':
OPEN cursor_name
**3.** **Obţinerea valorilor** reţinute în cadrul unui cursor se face prin instrucţiunea ''FETCH'', având sintaxa:
FETCH [[NEXT] FROM] cursor_name INTO variable_name [, variable_name] ...
În momentul în care este apelată, se trece la următoarea înregistrare (dacă aceasta există), iar valorile sunt transferate în cele ale variabilelor definite, numărul lor trebuind să corespundă cu al celor din instrucţiunea ''SELECT'' asociată declarării cursorului.
**4**. **Închiderea cursorului** se face prin instrucţiunea ''CLOSE'':
CLOSE cursor_name
În situaţia în care cursorul nu este dechis, operația va genera o eroare. Dacă nu este închis explicit, acest lucru se va realiza automat la sfârşitul blocului ''BEGIN ... END'' în care a fost definit.
**Exemplu**. Un cursor poate fi folosit pentru a calcula vârsta medie a animalelor de companie diferențiată pe sexe, aplicând corecții asupra datei de naștere acolo unde se detectează valori incorecte:
DELIMITER //
CREATE PROCEDURE average_age_per_gender()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE _gender CHAR(1);
DECLARE _birth_date DATETIME;
DECLARE current_age, male_total_age, male_number, female_total_age, female_number INT;
DECLARE animal_cursor CURSOR FOR SELECT gender, birth_date FROM animal;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET male_total_age := 0;
SET male_number := 0;
SET female_total_age := 0;
SET female_number := 0;
OPEN animal_cursor;
this_loop: LOOP
FETCH animal_cursor INTO _gender, _birth_date;
IF done THEN
LEAVE this_loop;
END IF;
SET current_age := YEAR(CURRENT_TIMESTAMP) - YEAR(_birth_date) - (MONTH(CURRENT_TIMESTAMP) < MONTH(_birth_date) OR (MONTH(CURRENT_TIMESTAMP) = MONTH(_birth_date) AND DAY(CURRENT_TIMESTAMP) < DAY(_birth_date)));
IF current_age < 0 THEN
SET current_age := 0;
END IF;
CASE _gender
WHEN 'M' THEN
SET male_total_age := male_total_age + current_age;
SET male_number := male_number + 1;
WHEN 'F' THEN
SET female_total_age := female_total_age + current_age;
SET female_number := female_number + 1;
END CASE;
END LOOP;
SELECT CONCAT('Varsta medie a animalelor de sex masculin este ', male_total_age / male_number,'. Varsta medie a animalelor de sex feminin este ', female_total_age / female_number,'.');
CLOSE animal_cursor;
END; //
CALL average_age_per_gender;
===== Gestiunea tranzacțiilor =====
==== Proprietățile ACID ====
MySQL este un sistem de gestiune pentru baze de date tranzacțional, în sensul că respectă proprietățile ACID:
* atomicitate - o tranzacție trebuie executată complet; cu alte cuvinte, dacă una dintre instrucțiunile care o compun eșuează, sistemul de gestiune pentru baze de date trebuie să fie capabil să restaureze starea anterioară încercării de rulare a tranzacției respective; acestă proprietate este asigurată prin folosirea unor zone de memorie tampon al căror conținut este marcat pe discul local în momentul în care operațiile din cadrul tranzacțiilor au fost realizate;
* coerență - se referă la faptul că baza de date nu trebuie să se găsească în nici un moment într-o stare corespunzătoare unei execuții parțiale a unei tranzacții; un astfel de comportament este realizat prin facilitatea de blocare a obiectelor bazei de date realizate până ce s-au realizat toate modificările;
* izolare - specifică faptul că fiecare tranzacție trebuie să beneficieze de propriul spațiu de lucru, autonom și independent de toate celelalte tranzacții, astfel încât rezultatul execuției sale să devină vizibil numai în momentul în care aceasta a fost executată rulată în totalitate; o astfel de funcționalitate nu se obține însă decât prin mecanisme de tipul blocării la nivel de înregistrare, cu anumite costuri asupra performanței;
* durabilitate - indică faptul că rezultatele unei tranzacții trebuie să fie persistente inclusiv în cazul producerii unei erori, recuperarea dintr-o astfel de situație realizându-se prin intermediul jurnalelor în care sunt stocate operațiile realizate.
==== Operații de lucru cu tranzacții ====
În situația în care mai multe instrucțiuni trebuie realizate împreună (fie toate, fie nici una), acestea vor fi grupate în cadrul unei tranzacții.
START TRANSACTION
[transaction_characteristic [, transaction_characteristic] ...]
transaction_characteristic:
WITH CONSISTENT SNAPSHOT
| READ WRITE
| READ ONLY
BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}
O tranzacție este marcată prin intermediul instrucțiunii ''START TRANSACTION'' sau ''BEGIN [WORK]''. Instrucțiunile din cadrul acesteia vor fi executate împreună în momentul în care se apelează instrucțiunea ''COMMIT'' sau se va reveni la starea anterioară acesteia dacă se apelează ''ROLLBACK''.
{{ :laboratoare:laborator00b:10_transaction.png?nolink&300 }}
În cadrul unei tranzacții modul autocommit (marcarea modificărilor pe discul local imediat ce este emisă o anumită instrucțiune) - activat în mod implicit - este dezactivat pe toată perioada acesteia. Modificarea acestui comportament se poate controla prin intermediul variabilei de sesiune ''autocommit''.
Nu se pot realiza tranzacții imbricate. O astfel de tentativă va rezulta în terminarea tranzacției vechi și începerea unei noi tranzacții.
Clauza ''AND [NO] CHAIN'' specifică faptului că se va realiza sau nu încă o tranzacție cu același nivel de izolare odată ce sunt executate instrucțiunile ''COMMIT'' sau ''ROLLBACK''.
Clauza ''[NO] RELEASE'' indică faptul că se dorește sau nu deconectarea sesiunii curente odată ce sunt executate instrucțiunile ''COMMIT'' sau ''ROLLBACK''.
De asemenea, se pot specifica proprietăți ce controlează caracteristicile tranzacției:
* ''WITH CONSISTENT SNAPSHOT'' - realizează o citire consistentă dacă nivelul de izolare al tranzacției permite acest lucru;
* ''READ WRITE'' (implicit) - permite tranzacției să modifice sau să obțină drepturi de blocare (pentru operații de citire sau scriere) asupra tabelelor bazei de date;
* ''READ ONLY'' - restricționează tranzacția de la a modifica sau de a obține drepturi de blocare asupra tabelelor tranzacționale sau non-tranzacționale care sunt vizibile la nivelul altor tranzacții.
O tranzacție poate fi împărțită în mai multe zone, delimitată de așa-numitele puncte de salvare (//eng.// save-points), prin intermediul comenzii ''SAVEPOINT identifier''. Astfel, se reține starea de la un anumit moment de timp la care se poate reveni prin instrucțiunea ''ROLLBACK [WORK] TO [SAVEPOINT] identifier''. Realizarea acestei operații nu este însă echivalentă cu realizarea tranzacției, ci doar revenirea la o anumită situație, cu pierderea punctelor intermediare ulterioare. Eliminarea unui astfel de marcaj se realizează prin comanda ''RELEASE SAVEPOINT identifier''.
==== Controlul comportamentului tranzacțional ====
Controlul comportamentului tranzacțional se realizeză prin intermediul a două variabile de sesiune:
- ''AUTOCOMMIT'' ce gestioneză momentul la care sunt executate instrucțiunile
* ''1'' (implicit): modificările sunt vizibile imediat;
* ''0'': modificările sunt stocate pe discul local în momentul în care s-a apelat una din instrucțiunile ''COMMIT'' sau ''ROLLBACK'';
- ''TRANSACTION ISOLATION LEVEL'' care specifică nivelul de izolare pentru tranzacții (gradul de interacțiune cu alte tranzacții)
* ''READ UNCOMMITTED'' - sunt vizibile modificări nesalvate de către alte tranzacții, ceea ce reprezintă o vulnerabilitate față de datele fantomă și citiri irepetabile
* ''READ COMMITTED'' - se operează cu valorile stocate pe discul local de către alte tranzacții ceea ce ridică probleme legate de citirile irepetabile
* ''REPEATABLE READ'' (implicit) - informațiile actualizate de alte tranzacții sunt disponibile numai în momentul în care s-a realizat și tranzacția din care se solicită datele respective
* ''SERIALIZABLE'' - tranzacțiile concurente sunt implementate ca și când acestea s-ar desfășura secvențial, asigurându-se astfel faptul că datele obținute sunt cele stocate în mod real pe discul local
==== Simularea tranzacțiilor prin operații de blocare ====
Este posibilă și realizarea de pseudo-tranzacții, prin blocarea / deblocarea anumitor privilegii (scriere, citire) pentru alte tranzacții la nivel de:
* tabelă;
* pagină = un anumit număr de înregistrări;
* înregistrare
Instucțiunile corespunzătoare unor astfel de operații sunt ''LOCK TABLES'', respectiv ''UNLOCK TABLES'':
LOCK TABLES
table_name [[AS] alias] lock_type
[, table_name [[AS] alias] lock_type] ...
lock_type:
READ [LOCAL]
| [LOW_PRIORITY] WRITE
UNLOCK TABLES
Se observă că este posibilă blocarea concomitentă a mai multor tabele, fiecare pentru operații diferite:
* blocarea în modul citire: atât tranzacția care a realizat blocarea cât și alte tranzacții au doar drepturi de citire asupra tabelei respective;
* blocarea în modul scriere: tranzacția care a realizat blocarea are atât drepturi de citire cât și drepturi de scriere asupra tabelei respective, în timp ce alte tranzacții nu au nici un fel de drepturi asupra acesteia.
===== Alte elemente ale limbajului MySQL =====
==== Comentarii ====
Comentariile se pot întinde fie pe o linie (fiind precedate de ''#'' sau ''-- '' până la sfârşitul liniei în cauză), fie pe mai multe linii (fiind încadrate între ''%%/*%%'' şi ''%%*/%%''), această variantă putând fi folosită şi pentru comentariile de un singur rând care nu se întind însă până la sfârşitul acesteia.
==== Script-uri specifice ====
Câteodată este util, în special în caz de testare, ca toate comenzile să fie grupate în cadrul unui fişier şi executate secvenţial.
În acest sens, se pot folosi:
* operatorul de indirectare:
mysql -h host -u user -p < filename
* comenzile source sau \.:
mysql> source filename;
mysql> \. filename
===== Activitate de Laborator =====
Se doreşte proiectarea unei baze de date ce va fi folosită în cadrul unui sistem ERP pentru o librărie care comercializează doar cărţi.
O carte poate avea unul, nici unul sau mai mulți autori, un titlu, un subtitlu și o descriere, fiind caracterizată printr-un număr de ediție, anul în care a fost tipărită și colecția din care face parte. Cartea poate fi disponibilă în mai multe formate (tipărit, electronic). Pentru fiecare format se reține un isbn, limba în care este scrisă cartea, stocul existent precum și prețul. De asemenea, unei cărți i se pot specifica una, nici una sau mai multe categorii. O categorie este definită printr-un nume și o descriere.
Este obligatoriu ca informațiile număr de ediție, stoc existent și preț să fie numere pozitive.
De asemenea, pentru fiecare autor se va reţine şi o scurtă biografie.
O colecție aparține unei edituri, pentru aceasta reținându-se un nume și o descriere. Pentru fiecare editură sunt stocate denumirea, codul unic de identificare, descrierea, adresa poștală, codul zip, țara și pagina Internet de unde pot fi obținute informații suplimentare.
Librăria emite comenzi de aprovizionare către edituri şi facturi la clienţi. Atât comanda de aprovizionare cât şi factura vor fi identificate printr-un cod (unic) – spre exemplu serie şi număr –, data la care a fost emisă, starea precum şi entitatea căreia i se adresează (editură / client). Ele pot conţine mai multe produse, pentru fiecare reţinându-se cantitatea.
Un client este identificat prin cod numeric personal, nume, prenume, adresa, telefon, email. Presupunând că librăria dispune şi de o platformă on-line pentru achiziţii, pentru fiecare client se vor reţine tipul (administrator / client) și credențialele sub forma unui nume de utilizator și a unei parole. Cu excepţia numărului de telefon, nici un câmp nu poate fi vid. Pentru email trebuie să se respecte formatul ''username@domain.country''.
O schemă conceptuală a bazei de date descrisă mai sus din punct de vedere funcțional poate fi următoarea:
{{ :laboratoare:laborator00b:aipi2015-lab00b-conceptualscheme.png?nolink&800 }}
**1.** Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la [[https://www.github.com/aipi2015/Laborator00|]]. În urma acestei operații, directorul ''Laborator00'' va trebui să conțină subdirectoarele ''samples'' și ''labtasks'', fișierele ''README.md'' și ''LICENSE''.
student@aipi2015:~$ git clone https://www.github.com/aipi2015/Laborator00
**2.** Să se ruleze, în ordine, script-urile:
* ''aipi2015-lab00b-DDL.sql'' care conține instrucțiunile necesare pentru definirea bazei de date ''bookstore'';
* ''aipi2015-lab00b-DML.sql'' care conține instrucțiunile necesare pentru popularea tabelelor din baza de date ''bookstore''.
Se poate folosi utilitarul MySQL Workbench sau fișierele se pot încărca cu ajutorul unui client MySQL, fie prin redirectarea intrării, fie prin folosirea comenzilor ''source'' sau ''\.''.
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]]
[SEPARATOR str_val])
**4.** Să se majoreze cu 15% preţul cărţilor care sunt disponibile în mai mult de trei formate diferite. Să se afișeze cărțile pentru care s-au majorat prețurile, grupate pe formate. Informațiile care vor fi vizualizate sunt identificatorul cărții, titlul și subtitlul, denumirea formatului, prețul actualizat.
**5.** Să se afişeze toate cărţile unui autor, fie că le-a redactat singur, fie că acestea au mai mulţi autori.\\ Să se afișeze, în cadrul aceleiași interogări, numărul de volume elaborate, precizându-se numărul celor la care e unic autor şi al celor pe care le-a realizat colaborând cu alţi autori.\\ Vor fi incluși doar autorii care au elaborat mai mult de 4 titluri din care mai mult de 2 sunt individuale.
**6.** Să se șteargă din baza de date acei scriitori pentru care nu se găsește nici o carte în baza de date.
**7.** Să se creeze o rutină stocată pe server (funcție) care determină valoarea unei facturi.\\ Folosind această rutină stocată, să se identifice primii trei clienţi în funcţie de volumul tranzacţiilor din librărie.
**8.** Să se creeze o rutină stocată pe server (procedură) care calculează suma tuturor facturilor pentru un client, aceasta fiind întoarsă sub forma unui parametru.
**9.** Folosind doar rutina stocată și având acces doar la tabela de utilizatori, să se determine utilizatorul pentru care suma facturilor este cea mai mare.