n capitolul anterior am prezentat cteva exemple de tabele create i populate prin SELECT-uri mai mult sau mai puin complicate. O bun parte dintre acestea ne-au folosit la stocarea temporar a unor rezultate de form tabelar, rezultate ce ar putea constitui sursa unor rapoarte sau chiar a unor controale din formulare. Pentru rapoarte sau popularea controalelor de tip list, combo, mult mai nimerit este folosirea tabelelor temporare i/sau virtuale. Acestea, spre deosebire de tabelele obinuite, nu ocup spaiu pe disc, dect pe parcursul unei sesiuni de lucru, sau chiar mai puin, ntruct n schema bazei se stocheaz permanent doar structura lor, nu i coninutul. 14.1. Tabele temporare Tabelele temporare se creaz prin aceeai comand - CREATE TABLE cu cele dou variante majore list de atribute i/sau restricii sau consultri (SELECT). Exist dou tipuri de tabele temporare 1 , locale i globale. Coninutul ambelor tipuri nu este vizibil dect utilizatorului curent (sesiunii curente, pentru c ntre sesiunile de lucru coninutul unei tabele temporare poate fi stocat doar prin copierea ntr-o tabel obinuit). Gulutzan i Pelzer le numesc tabele-dependente-de-sesiune. 2
Tabelele temporare locale sunt cele mai invizibile, coninutul lor fiind disponibil doar nuntrul aceluiai modul de cod executat n cadrul unei sesiuni de lucru. Doar cele globale pot fi transmise ntre modulele executate ntr-o aceeai sesiune de lucru, ns i aici trebuie avut grij, pentru c tabelele temporare pot pstra coninutul lor la finalizarea unei tranzacii (ON COMMIT PRESERVE ROWS), dar l i pot pierde automat (ON COMMIT DELETE ROWS). Cele dou clauze funcioneaz i pentru tabelele virtuale locale.
Prima utilizare major a tabelelor temporare intete simplificarea interogrilor. Ne referim, pentru nceput, la frustrarea pe care au trit-o utilizatorii de PostgreSQL n paragraful 9.6 dedicat expresiilor tabel (WITH... SELECT...), paragraf n care numai PostgreSQL-itii au stat pe tu. S lum problema att de
1 [Melton & Simon 2002], pp.100-102, 2 [Gulutzan & Pelzer 1999], p.350 650 Capitolul 14 ndrgit: Care este judeul n care berea s-a vndut cel mai bine ? Folosind o tabel temporar, i n acest dialect lucrurile pot fi simplificate sensibil: CREATE LOCAL TEMPORARY TABLE judete_bere AS (SELECT Judet, SUM(Cantitate*PretUnit*(1+ProcTVA)) AS Vnz_Bere FROM judete j INNER JOIN coduri_postale cp ON j.Jud=cp.Jud INNER JOIN clienti c ON cp.CodPost=c.CodPost INNER JOIN facturi f ON c.CodCl=f.CodCl INNER JOIN liniifact lf ON f.NrFact= lf.NrFact INNER JOIN produse p ON lf.CodPr=p.CodPr WHERE Grupa='Bere' GROUP BY Judet) ;
SELECT * FROM judete_bere WHERE Vnz_Bere = (SELECT MAX(Vnz_Bere) FROM judete_bere) ;
Figura 14.1. O (alt) tulburare de personalitate a PostgreSQL-ului Practic, expresia-tabel a devenit tabel temporar. Acum, dac tot a venit vremea confidenelor, trebuie s v mrturisesc c PostgreSQL-ul iese din frontul standardului, deoarece: PostgreSQL-ul nu pstreaz definiia tabelelor temporare ntre sesiuni; Nu face diferenierea dintre tabelele temporare globale i locale; Dac, n lipsa clauzei ON COMMIT, standardul prevede tergerea liniilor, n PostgreSQL este invers cnd lipsete ON COMMIT coninutul se pstreaz; SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 651 Clauza ON COMMIT are, pe lng cele dou opiuni standard PRESERVE ROWS i DELETE ROWS, clauza DROP care semnalizeaz c, la finalul trazaciei, tabela urmeaz s fie tears. n plus (de fapt, n minus!), nu am reuit s folosesc clauza ON COMMIT, mesajul de eroare fiind, de fiecare dat de genul celui din figura 14.1 3 .
Care sunt cei mai mari trei 3 datornici ? n varianta scriptural PostgreSQL pe care o vom nira mai jos vom crea trei tabele temporare locale/globale: tFACTURAT care conine vnzrile pentru fiecare client, tNCASAT ce conine totalul ncasrilor de la fiecare client i tDE_INCASAT, cu restul de plat al fiecrui client: CREATE LOCAL TEMPORARY TABLE tFacturat AS ( SELECT CodCl, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Facturat FROM facturi F INNER JOIN liniifact LF ON F.NrFact=LF.NrFact INNER JOIN produse P ON LF.CodPr=P.CodPr GROUP BY CodCl ; CREATE LOCAL TEMPORARY TABLE tIncasat AS SELECT CodCl, SUM(Transa) AS Incasat FROM facturi F INNER JOIN incasfact I ON F.NrFact=I.NrFact GROUP BY CodCl ; CREATE LOCAL TEMPORARY TABLE tDe_INCASAT AS SELECT DenCl, tFACTURAT.CodCl, Facturat, COALESCE(Incasat,0) AS Incasat, Facturat - COALESCE(Incasat,0) AS De_Incasat FROM tFACTURAT INNER JOIN clienti ON tFacturat.CodCl=clienti.CodCl LEFT OUTER JOIN Tincasat ON tFACTURAT.CodCl=tINCASAT.CodCl ;
Interogarea final face apel doar la ultima tabel temporar: SELECT * FROM tDe_INCASAT WHERE De_Incasat >= (SELECT MAX(De_Incasat) FROM tDe_INCASAT WHERE De_Incasat < (SELECT MAX(De_Incasat) FROM tDe_INCASAT WHERE De_Incasat <
3 Vezi i documentaia PostgreSQL la pagina: http://www.postgresql.org/docs/8.3/interactive/ sql-createtable.html 652 Capitolul 14 (SELECT MAX(De_Incasat) FROM tDe_INCASAT) ) ) ; Silii fiind de sintaxa rigid a funciei CONNECTBY(), n paragraful 12.5 am creat o tabel virtual PERSONAL2_MODIF01. Puteam, la fel de bine, folosi i o tabel temporar n locul celei virtuale. Analog simplificm obinerea traseelor Iai- Focani (i distanei fiecrei rute vezi figura 12.33), dac recurgem la o tabel temporar X. CREATE LOCAL TEMPORARY TABLE x AS SELECT ierarhie.Loc2 AS Loc1, ierarhie.Loc1 AS Loc2, distanta, ierarhie.Cale FROM ( SELECT * FROM CONNECTBY('distante', 'Loc2', 'Loc1', 'Iasi', 0, '**') AS t2 (Loc1 TEXT, Loc2 TEXT, LEVEL INT, cale text) ) ierarhie, distante d WHERE ierarhie.Loc2 =d.Loc1 AND ierarhie.Loc1=d.Loc2 ;
SELECT x1.cale, SUM(x2.distanta) AS Km FROM x x1, x x2 WHERE x1.cale LIKE 'Iasi%Focsani' AND x1.cale LIKE x2.cale || '%' GROUP BY x1.cale ORDER BY Km n capitolul 17 vom vedea la lucru tabelele temporare PostgreSQL, dar n alt ipostaz cea de substitut al variabilelor publice.
i n Oracle apar cteva diferene fa de evangheliile SQL n materie de tabele temporare. Poate cea mai important este c n Oracle nu pot fi create tabele temporare locale, ci numai globale. ncercm s simplificm interogarea din paragraful 13.1 care acum produce rezultatul din figura 13.4: CREATE GLOBAL TEMPORARY TABLE tab ON COMMIT PRESERVE ROWS AS SELECT SYS_CONNECT_BY_PATH (Loc1, '**') || '**' || Loc2 AS Traseu, LEVEL AS Nivel, Loc1, Loc2, EXTRACT (HOUR FROM Durata) * 60 + EXTRACT (MINUTE FROM durata) AS Durata_Min, ROWNUM AS Ord FROM distante d START WITH Loc1='Iasi' CONNECT BY PRIOR Loc2 = Loc1 AND Loc1<>'Focsani' AND Level < 20 ORDER BY Ord ;
SELECT 10001 AS IdCursa, 1 AS "StatieNr", 'Iasi' AS "Statie", TIMESTAMP'2008-04-01 09:05:00' AS "Data/Ora" FROM dual UNION SELECT 10001, t2.Nivel + 1, t2.Loc2, TIMESTAMP'2008-04-01 09:05:00' + TO_DSINTERVAL ('0 ' || FLOOR( SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 653 (SELECT SUM(Durata_Min) FROM tab WHERE t2.Traseu LIKE Traseu || '%' ) / 60) || ':' || MOD ( (SELECT SUM( Durata_Min ) FROM tab WHERE t2.Traseu LIKE Traseu || '%' ), 60) || ':00') FROM tab t1 INNER JOIN tab t2 ON t1.Traseu LIKE '**Iasi**Vaslui%Focsani' AND t1.traseu LIKE t2.Traseu || '%' ORDER BY 1,2
Interesant este i modul n care tergem o tabel temporar n Oracle. Astfel, comanda DROP fr o pregtire prealabil genereaz mesajul de eroare din figura 14.2.
Figura 14.2. Tentativ euat de tergere a unei tabele temporare n Oracle Din mesaj ar rezulta c tabela temporar ar trebui, mai nti tears cu TRUNC. ntr-adevr, prin succesiunea celor dou comenzi: TRUNCATE TABLE tab ; DROP TABLE tab ; tergerea decurge fr probleme.
DB2 face (ca i SQL Server, dar n alt direcie) opinie separat n materie de tabele temporare. n DB2 o tabel temporar este definit la nivel de sesiune, ns descrierea sa nu apare n dicionarul de date (catalogul de sistem). De aceea, o tabel temporar nu poate fi partajat cu alte sesiuni. La terminarea sesiunii, orice urm a tabelei temporare dispare. Comanda nu este CREATE TABLE, ci DECLA- 654 Capitolul 14 RE GLOBAL TEMPORARY TABLE care este ns destul de limitat. Copiem atributele tabelei CURSE din paragraful 13.1 n tabela temporar tCURSE: DECLARE GLOBAL TEMPORARY TABLE tCurse AS ( SELECT 1001 AS IdCursa, CAST (' ' AS VARCHAR(200)) AS Traseu, CAST ('2008-04-01 09:05:00' AS TIMESTAMP) AS DataOra_Plecare FROM sysibm.sysdummy1 ) DEFINITION ONLY ;
Figura 14.3. Tabele extrem de temporare n DB2 n schimb, cnd ncercm s populm tabela temporar cu INSERT-ul din paragraful 13.3.1 avem parte de un mesaj destul de ciudat, potrivit cruia tCURSE este un obiect necunoscut (un fel de OZN) - vezi figura 14.3. Explicaia este c DB2-ul uit extrem de repede definiia tabelei temporare. Firete c v, ntrebai, ca i mine, la ce bune tabelele temporare n DB2. Rspunsul va fi oferit n urmtoarea ediie a crii (care, dac ar urma trendul acesteia, ar aprea prin 2015).
i SQL Server este departe de sintaxa din standardul SQL. Cele dou tipuri de tabele temporare sunt specificate prin simboluri # plasate naintea numelui. Astfel, SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 655 dac, la creare, numele tabelei este prefixat de un singur simbol #, atunci tabela temporar este una local, vizibil deci numai n sesiunea curent. Dac prefixul numelui tabelei este format din dou semne # (##), atunci tabela temporar este global. Relum exemplul celor trei datornici prezentat i pentru PostgreSQL: SELECT CodCl, SUM(Cantitate * PretUnit * (1+ProcTVA)) AS Facturat INTO #tFacturat FROM facturi F INNER JOIN liniifact LF ON F.NrFact=LF.NrFact INNER JOIN produse P ON LF.CodPr=P.CodPr GROUP BY CodCl ; SELECT CodCl, SUM(Transa) AS Incasat INTO #tIncasat FROM facturi F INNER JOIN incasfact I ON F.NrFact=I.NrFact GROUP BY CodCl ; SELECT DenCl, #tFacturat.CodCl, Facturat, COALESCE(Incasat,0) AS Incasat, Facturat - COALESCE(Incasat,0) AS De_Incasat INTO #tDe_INCASAT FROM #tFacturat INNER JOIN clienti ON #tFacturat.CodCl=clienti.CodCl LEFT OUTER JOIN #tIncasat ON #tFacturat.CodCl = #tIncasat.CodCl ;
SELECT * FROM #tDe_INCASAT WHERE De_Incasat >= (SELECT MAX(De_Incasat) FROM #tDe_INCASAT WHERE De_Incasat < (SELECT MAX(De_Incasat) FROM #tDe_INCASAT WHERE De_Incasat < (SELECT MAX(De_Incasat) FROM #tDe_INCASAT) ) ) ; 14.2. Tabele virtuale n interogri Ca i cele temporare, tabelele virtuale (view-urile) sunt construite prin subconsultri aplicate asupra tabelelor de baz i/sau altor tabele temporare sau view-uri. Tot similar celor temporare, numai definiia (structura) este persistent i public (accesibil altor sesiuni/utilizatori), coninutul nu ! Exist ns i diferene majore. O tabel virtual nu are coninut propriu-zis, fiind instaniat doar la invocarea acesteia (invocare ce nseamn, de fapt, execuia interogrii-definiie). Dar cea mai important deosebire este c numai o parte dintre view-uri sunt actualizabile, iar modificarea lor se propag automat n tabelele surs. Inserarea, modificarea sau tergerea unei linii dintr-un view actualizabil se va traduce, n fapt, n inserarea, modificarea sau tergerea de linii n/din una sau mai multe dintre tabelele surs. Avantajele tabelelor virtuale sunt multiple: uurarea interogrilor, prin salvarea rezultatelor intermediare; 656 Capitolul 14 construirea unor surse de date pentru rapoarte i controale ale formu- larelor (liste, combo-uri sau chiar grid-uri); facilitatea vizualizrii informaiilor presrate n multe tabele; mai bun securitate a datelor; anumitor categorii de utilizatori, n locul accesului la tabelele bazei, li se poate acorda acces exclusiv la view-uri n care pot vedea datele pe care sunt autorizai s le consulte/modifice. Standardul SQL-92 a introdus view-urile ca tabele virtuale ce sunt materializate la invocarea numelui lor. Materializarea nseamn execuia frazei SELECT ce constituie definiia tabelei virtuale i popularea cu nregistrrile-rezultat ale interogrii. Formatul simplist al comenzii de creare a unei tabele virtuale este: CREATE VIEW <nume-tabel-virtual> [<list-coloane>] AS <fraz SELECT> [WITH [<clauz de niveluri>] CHECK OPTION] unde <clauz de niveluri> ::= CASCADED | LOCAL.
Odat creat tabela virtual, definiia sa este salvat n schema bazei. Ulterior, ori de cte ori este necesar, la deschiderea/remprosptarea tabelei virtuale, aceasta este (re) populat cu nregistrri extrase din cele ale tabelelor propriu-zise ce apar n clauza FROM a interogrii. O tabel virtual poate fi deci privit i ca o expresie de subconsultare a unei tabele persistente, stocat n baz i invocat prin numele su. Potrivit standardelor SQL, unei tabele virtuale nu i se pot asocia indeci i nici defini restricii, ns unele SGBD-uri optimizeaz lucrul cu view-urile folosind indecii tabelelor persistente. Numele tabelelor virtuale este unic, i nu se poate auto-referi, dei un view poate fi creat pe baza unei combinaii de tabele persistente i/sau alte view-uri. tergerea unei tabele viruale din schema bazei de date se realizeaz prin comanda DROP VIEW: DROP VIEW <nume-tabel-virtual> <comportament> unde <comportament> ::= CASCADE | RESTRICT. Prin clauza CASCADE, se terg att tabela virtual curent, ct i toate cele create pe baza acesteia, n timp ce RESTRICT interzice operaiunea, atta timp ct exist mcar un view construit pe baza tabelei virtuale curente. De regul, formatul general al comenzii de creare a tabelelor vrtuale este mai generos dect cel al unei tabele temporare. Spre exemplu, n DB2 la definirirea unei tabele temporare nu putem include o expresie tabel, n timp ce la o tabel virtual da. Comanda urmtoare definete o tabel virtual, NUMERE_NATURALE, ce va conine numere naturale (atributul Numar) de la 0 la 9999: CREATE VIEW numere_naturale AS WITH temp1 (Numar) AS ( VALUES (0) UNION ALL SELECT Numar + 1 FROM temp1 WHERE Numar < 10000 ) SELECT * FROM temp1; SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 657 Reformulm, astfel, o problem pentru care am formulat soluia n paragraful 12.6: Care sunt numerele de facturi nefolosite (n tabela FACTURI)? SELECT Numar AS Numar_nefolosit FROM (SELECT Numar FROM numere_naturale WHERE Numar BETWEEN (SELECT MIN(NrFact) FROM facturi) AND (SELECT MAX(NrFact) FROM facturi ) ) LEFT OUTER JOIN facturi f ON Numar = NrFact WHERE f.Nrfact IS NULL ORDER BY 1
Tabelele virtuale sunt preferabile celor temporare i datorit modului de rem- prosptare a coninutului. n cadrul aceleai sesiuni, o tabel temporar global nu- i schimb coninutul (dac a fost definit cu clauza ON COMMIT PRESERVE ROWS). Dac sesiunea este lung, este probabil ca tabelele din care provine tabela temporar s fi fost actualizate. Modificrile sunt reflectate automat la urmtoarea invocare a view-ului, n timp ce tabela temporar va fi remprosptat abia la urmtoarea iniializare (urmtoarea sesiune).
Figura 14.4. Actualizarea dinamic a tabelelor virtuale din tabelele-surs Figura 14.4 prezint o sesiune n care sunt create o tabel temporar (tFacturatCl) i una virtual (vFacturatCl) care, iniial, au coninut identic. n aceeai sesiune, se adaug o factur (3120) cu dou linii pentru Clientul 1 SQL. Dup inserare, coninutul tabelei temporare este identic celui de dup crearea sa, n 658 Capitolul 14 timp ce prima linie din tabela virtual se mprospteaz cu noua valoare a atributului Facturat pentru Client 1 SRL. 14.3. Probleme ale actualizrilor tabelelor surs pe baza modificrii tabelelor virtuale Am vzut n figura 14.4 c o tabel virtual se mprospteaz automat cu modificrile operate n tabelele-Surs. Ei, bine, n unele cazuri i reciproca este valabil, adic o serie de view-uri sunt modificabile (d.p.d.v. al coninutului), iar modificrile se propag n tabela sau tabelele surs. Numai c aici exist o serie ntreag de restricii. Pentru vFacturatCl discuia se simplific din start deoarece la creare s-a folosit opiunea READ ONLY, ceea ce nseamn c orice tentativ de inserare, modificare i tergere va fi automat tratat cu dumnie. Pentru a fi actualizabil, fiecare nregistrare a unei tabele virtuale trebuie s poat fi asociat unei singure linii dintr-o tabel surs. Astfel, modificarea unei linii din view poate fi propagat fr probleme de ambiguitate. Firete, o tabel derivat de genul: CREATE VIEW vJudete1 AS SELECT * FROM Judete WHERE regiune = 'Moldova' OR regiune = 'Dobrogea' nu va crea probleme insolubile la actualizare. n schimb, dei cu un coninut identic vJudete1, tabela virtual vJudete2, creat prin comanda care urmeaz, nu este actualizabil nici n SQL, nici n majoritatea SGBD-urilor importante. CREATE VIEW vJudete2 AS SELECT * FROM Judete WHERE regiune = 'Moldova' UNION SELECT * FROM Judete WHERE regiune = 'Dobrogea'
Pentru a intra n cteva detalii, s imaginm o tabel derivat denumit INCA- SARI_CLIENTI figura 14.5 - ce conine documentul de ncasare, suma ncasat (corespunztoare documentului) i clientul care a efectuat plata. Comanda de creare a tabelei virtuale este: CREATE VIEW vINCASARI_CLIENTI AS ( SELECT DenCl, CodDoc, NrDoc, DataDoc, SUM(Transa) AS SumaPlatita FROM clienti c INNER JOIN facturi f ON c.CodCl = f.CodCl INNER JOIN incasfact incf ON f.NrFact = incf.NrFact INNER JOIN incasari inc ON incf.CodInc = inc.CodInc GROUP BY DenCl, CodDoc, NrDoc, DataDoc ); SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 659
Figura 14.5. O tabel virtual Utilitatea unei asemenea relaii virtuale poate fi certificat de un angajat al com- partimentului financiar care se ocup, printre altele, i cu evidena ncasrilor de la clieni. Pe ct de util, pe att de problematic devine INCASARI_CLIENTI atunci cnd se pune problema actualizrii sale i propagrii modificrilor n tabelele de baz din care provine, CLIENTI, FACTURI, INCASARI i INCASFACT. Iat cteva dintre probleme: modificarea atributului SumaPlatit este imposibil de operat n tabela de baz, INCASFACT. O linie din tabela virtual corespunde uneia sau mai multor linii din tabela de baz (depinde cte facturi sunt achitate prin documentul de plat respectiv). Dac pentru ordinul de plat 111 din 10 august 2007 se dorete modificarea (corecia) sumei din 155 971 n 156 000, nu se poate cunoate trana i factura unde s-a comis eroarea i, normal, unde trebuie operat modificarea. modificarea celui de-al patrulea tuplu din (Client 2 SA, OP, 555, 10-Aug- 2007, 106275) n (Client 5 SRL, OP, 555, 10-Aug-2007, 106275), adic modificarea clientului pentru Ordinul de plat nr. 555 din 10.08.2007 poate fi operat n trei moduri n tabele de baz: o fie se modific n FACTURI pentru factura 1113 valoarea atributului CodCl din 1002 n 1005; o fie se modific n linia din INCASFACT codul ncasrii (CodInc) din 1238 n 1235; o fie se modific n INCASFACT valoarea atributului NrFact din 1113 n 1112, pstrndu-se codul ncasrii neschimbat; chiar dac nu la fel de plauzibile, cele trei variante genereaz o stare de confuzie pentru SGBD. inserarea unei linii n view este o aciune temerar, dar fr prea muli sori de izbnd: oricare ar fi cele patru tabele surs n care s-ar face inserarea, cel puin un atribut important (ce nu poate avea valori NULL) nu are valori specificate, astfel nct se ncalc una dintre restricii (de entitate sau comportament). Spre exemplu, convenim c inserarea trebuie s se propage numai n INCASFACT. Nu se cunoate ns numrul facturii ncasate. Cum NrFact este un component al cheii primare a tabelei, este clar c operaiunea va fi interzis de SGBD. tergerea unei linii din tabela virtual ridic problema identificrii liniei sau liniilor dintr-una sau mai multe tabele de baz. 660 Capitolul 14
n plus, sursele unei tabele virtuale pot fi att tabele propriu-zise, ct i alte tabele virtuale, situaie n care vorbim de mai multe niveluri ale tabelelor virtuale. Spre exemplu, pentru a construi o tabel virtual vCLIENTI n care pot fi modificate: denumirea clientului, adresa sa, denumirea localitii n care i are sediul i numele judeului, sunt necesare urmtoarele view-uri: CREATE VIEW vJudete AS SELECT * FROM Judete ; CREATE VIEW vCoduri_Postale AS SELECT CodPost, Loc, vJudete.Jud, Judet, Regiune FROM coduri_postale cp INNER JOIN vJUDETE ON cp.Jud = vJUDETE.Jud ; CREATE VIEW vClienti AS SELECT CodCl, DenCl, Adresa, vCoduri_Postale.CodPost, Loc, Jud, Judet, Regiune FROM clienti INNER JOIN vCoduri_Postale ON clienti.CodPost = vCoduri_Postale.CodPost ;
Tabelele virtuale trebuie s includ toate coloanele cheilor primare/alternative ale tabelelor de baz. O linie dintr-o tabel virtual trebuie s corespund unei singure linii din tabela de baz. Toate coloanele neincluse n view trebuie s permit valori NULL sau s prezinte valori implicite. Altminteri, inserarea unei linii ntr-o tabel derivat ar fi imposibil. Nu poate fi actualizat o tabel virtual creat prin fraze SELECT n care apar: clauze GROUP BY / HAVING, funcii agregat, coloane calculate, operatorii: UNION, INTESECT, EXCEPT (MINUS), SELECT DISTINCT.
SQL:1999 definete civa termeni legai de tabelele virtuale sau consultrile- argument ale comenzilor de actualizare 4 : - potenial actualizabil; - actualizabil; - banal-actualizabil; - inserabil-n.
4 [Melton & Simon 2002], pp.111-112, 283-284 SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 661 O specificaie de interogare (care poate fi expresia de definire a unui view, sau o expresie ce urmeaz comenzii UPDATE vezi paragraful 13.3.4) este potenial actualizabil dac i numai dac 5 : nu se folosete clauza DISTINCT; toate coloanele din clauza SELECT apar n list o singur dat; nu se folosesc clauze GROUP BY/HAVING. Dac specificaia de interogare este potenial actualizabil, iar n clauza FROM apare o singur tabel, atunci toate coloanele din aceast tabel care apar n specificaie sunt actualizabile. Dac specificaia este potenial actualizabil, ns n clauza FROM apar dou sau mai multe tabele, atunci o coloan din specificaie este actualizabil numai dac: provine dintr-o singur tabel dintre cele din clauza FROM; ntre tabela respectiv i specificaia de interogare exist o relaie de coresponden a unicitii, adic tabela i pstreaz n interogare cheia primar sau cheile alternative. Altfel spus, valoarea unui atribut dintr-o tabel virtual este actualizabil doar dac aceasta corespunde unei singure valori dintr-o tabel surs. O specificaie (expresie) de interogare este actualizabil dac mcar una dintre coloane este actualizabil, i banal-actualizabil dac: este actualizabil, clauza FROM conine o singur tabel, i toate coloanele sale sunt actualizabile. Termenul (destul de nenorocit) inserabil-n desemneaz o specificaie/expresie actualizabil, dac toate tabelele din clauza FROM accept adugri de linii (INSERT-uri) i dac expresia nu include operatorii UNION, INTERSECT i EXCEPT. SGBD-urile actuale prezint cteva diferene n materie de reguli privind actualizarea tabelelor virtuale. Cel mai flexibil mecanism este, ns, cel al declana- toarelor de tip INSTEAD OF, despre care vom discuta pe scurt n capitolul 17. 14.3.1. Tabele virtuale n DB2 Documentaia IBM i bibliografia DB2 6 delimiteaz tabelele virtuale DB2 n funcie de operaiunile pe care le accept, dup cum urmeaz: tabele virtuale ce permit tergerea (deletable);
5 [Melton & Simon 2002], pp.111-112, 284 6 Vezi spre exemplu [Baklarz & Zikopoulos 2008] 662 Capitolul 14 tabele virtuale ce permit modificarea (updatable); tabele virtuale ce permit inserarea (insertable); tabele virtuale ce permit doar citirea, non-modificabile (read-only); Cele din prima categorie accept comanda DELETE, comand pe baza creia tergerea se propag ntr-o tabel-surs. Pentru a putea propaga tergerea, fraza SELECT de creare a tabelei virtuale nu trebuie s conin clauze precum DIS- TINCT, GROUP BY/HAVING, VALUES, UNION, INTERSECT, EXCEPT, iar fiecare linie din view corespunde unei singure linii dintr-o tabel surs. Singurul operator ansamblist acceptabil n unele situaii este UNION ALL. Un view este actualizabil (updatable) dac, n plus, are mcar un atribut al crui valoare poate fi modificat i inserabil (insertable) dac toate coloanele sale sunt actualizabile. Astfel, vClienti, creat ceva mai sus, nu permite tergerea unei linii vezi figura 14.6. Cum nici mcar tergerile nu sunt admise, cu att mai puin modificrile i inserrile. Tabela virtual vLiniiFact7001, fiind definit printr-o selecie aplicat unei singure tabele LINIIFACT: CREATE VIEW vLiniifact7001 AS SELECT NrFact, Linie, CodPr, Cantitate, PretUnit FROM liniifact WHERE NrFact=7001 ;
Figura 14.6. O tabel virtual din care nu pot fi terse linii accept orice gen de modificare, fiind inserabil, suport oricare dintre comenzile urmtoare: DELETE FROM vLiniifact7001 WHERE Linie=3 ; UPDATE vLiniifact7001 SET Cantitate = 100 WHERE Linie=2 ; INSERT INTO vLiniifact7001 VALUES (7001, 3, 4, 30, 730) ;
Nici tabela virtual vLiniifact7001_7002 nu accept tergerea de nregistrri, dei operatorul folosit este UNION ALL: CREATE VIEW vLiniifact7001_7002 AS SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 663 SELECT * FROM liniifact WHERE NrFact=7001 UNION ALL SELECT * FROM liniifact WHERE NrFact=7002 ; Comanda urmtoare se va solda cu un mesaj de eroare similar celui din figura 14.6, deoarece, pentru a funciona, UNION ALL trebuie s conecteze tabele-surs diferite: DELETE FROM vLiniifact7001_7002 WHERE NrFact=7001 AND Linie=3 14.3.2. Tabele virtuale n Oracle Sintaxa Oracle pentru definirea tabelelor virtuale este un pic mai generoas dect cea din DB2. Dac n DB2, pentru modificarea frazei SELECT ce definete un view aveam nevoie de o combinaie DROP VIEW/CREATE VIEW, n Oracle se poate folosi CREATE OR REPLACE VIEW. De asemenea, n DB2, o tabel virtual era READ ONLY n funcie de modul su de definire. n Oracle se poate folosi clauza READ ONLY pentru a bloca modificarea coninului unui view actualizabil. CREATE OR REPLACE VIEW vLiniifact7001 AS SELECT NrFact, Linie, CodPr, Cantitate, PretUnit FROM liniifact WHERE NrFact=7001 ; Regulile privind actualizarea coninutului unei tabele virtuale n vederea propagrii n tabelele surs sunt apropiate de cele din DB2, aa c nu mai insistm. 14.3.3. Tabele virtuale n PostgreSQL PostgreSQL are, probabil, cea mai srac sintax (dintre cele patru dialecte) n materie de CREATE VIEW: CREATE VIEW vJudete AS SELECT * FROM judete ORDER BY Judet; Dup creare, un view este destul de apatic n PostgreSQL. Astfel, dac se ncearc inserarea unei noi nregistrri corespunztoare judeului Galai, mesajul este cel din figura 14.7. Asta nseamn c toate tabelele virtuale sunt READ ONLY n PostgreSQL. Totui, din explicaii deducem c trebuie s apelm la mecanismul de reguli. 664 Capitolul 14
Figura 14.7. Probleme PostgreSQL chiar i la inserri ntr-o tabel virtual banal- actualiza- bil Acest mecanism de reguli prefaeaz destul de bine scurta discuie din capitolul 17 dedicat declanatoarelor de tip INSTEAD OF. Tabela virtual vJudete este creat dintr-o singur tabel, JUDETE, aa c inserarea unei linii n view trebuie s atrag dup sine adugarea nregistrrii respective n tabel: CREATE RULE vJudete_ins AS ON INSERT TO vJudete DO INSTEAD INSERT INTO Judete VALUES (NEW.Jud, NEW.Judet, NEW.Regiune) ; Numele fiecrui atribut este prefixat n clauza VALUES cu NEW., ceea ce semnalizeaz c este vorba despre valorile atributelor de pe noua linie a tabelei virtuale. Acum comanda INSERT funcioneaz: INSERT INTO vJudete VALUES ('GL', 'Galati', 'Moldova') ; La modificare unei nregistrri, vom folosi i clauza OLD. pentru a califica valoarea dinaintea eventualei modificri a atributului cheie primar Jud. CREATE RULE vJudete_upd AS ON UPDATE TO vJudete DO INSTEAD UPDATE judete SET Jud = NEW.Jud, Judet = NEW.Judet, Regiune = NEW.Regiune WHERE Jud = OLD.Jud; Redactarea regulii pentru tergere devine acum o treab simpl: CREATE RULE vJudete_del AS ON DELETE TO vJudete DO INSTEAD DELETE FROM judete WHERE Jud = OLD.Jud;
Firete, o modificare a unei valori din view care ncalc restricii definite n tabela de baz, va genera un mesaj de eroare vezi figura 14.8. SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 665
Figura 14.8. nclcarea unei restricii la inserarea ntr-o tabel virtual Mecanismul regulilor ne permite s ncercm rezolvarea problemelor de actualizare chiar i pentru tabele virtuale cu dou sau mai multe tabele surs joncionate. Iat cazul view-ului vFacturi: CREATE VIEW vFacturi AS SELECT lf.NrFact, DataFact, DenCl, Linie, lf.CodPr, DenPr, UM, ProcTVA, Grupa, Cantitate, PretUnit, Cantitate * PretUnit AS ValFaraTVALinie, Cantitate * PretUnit * ProcTVA AS TVALinie, Cantitate * PretUnit * (1+ProcTVA) AS ValTotLinie FROM liniifact lf INNER JOIN produse p ON lf.CodPr=p.CodPr INNER JOIN facturi f ON lf.NrFact=f.NrFact INNER JOIN clienti c ON f.CodCl=c.CodCl ORDER BY lf.NrFact, Linie; Dei sunt nu mai puin de patru tabele surs, tabela cea mai de jos este LINIIFACT, aa c suntem tentai s scriem regula de inserare astfel: CREATE OR REPLACE RULE vFacturi_ins AS ON INSERT TO vFacturi DO INSTEAD INSERT INTO liniifact VALUES ( NEW.NrFact, NEW.Linie, NEW.CodPr, NEW.Cantitate, NEW.PretUnit ) ; Regula funcioneaz dac adugm o linie unei facturi existente, ca de exemplu a cincea linie din factura 3119: INSERT INTO vFacturi (NrFact, Linie, CodPr, Cantitate, PretUnit) VALUES (3119, 5, 6, 1000, 1200) ; Dar ns adugm o linie ntr-o factur nou - s zicem factura cu nr. 3120 -, firete c vom nclca restricia referenial vezi figura 14.9. 666 Capitolul 14
Figura 14.9. nclcarea restriciei refereniale la inserarea ntr-un view cu mai multe tabele- surs Avem soluii i pentru acest gen de probleme. S redefinim tabela virtual, prelund i codul clientului: DROP VIEW vFacturi ; CREATE VIEW vFacturi AS SELECT lf.NrFact, DataFact, DenCl, f.CodCl, Linie, lf.CodPr, DenPr, UM, ProcTVA, Grupa, Cantitate, PretUnit, Cantitate * PretUnit AS ValFaraTVALinie, Cantitate * PretUnit * ProcTVA AS TVALinie, Cantitate * PretUnit * (1+ProcTVA) AS ValTotLinie FROM liniifact lf INNER JOIN produse p ON lf.CodPr=p.CodPr INNER JOIN facturi f ON lf.NrFact=f.NrFact INNER JOIN clienti c ON f.CodCl=c.CodCl ORDER BY lf.NrFact, Linie;
Problema cea mai delicat la rescrierea regulii de inserare este c un INSERT n vFacturi poate s se propage n tabele surs n mai multe moduri: inserarea unei linii numai n tabela LINIIFACT; inserare a cte o linie n tabelele LINIIFACT i FACTURI; inserare a cte o linie n tabelele LINIIFACT i PRODUSE; inserare a cte o linie n tabelele LINIIFACT, FACTURI i PRODUSE. Din fericire, putem formula mai multe reguli pentru aceeai operaiune ( inserare), astfel: DROP RULE IF EXISTS vFacturi_ins ON vFacturi ; CREATE OR REPLACE RULE vFacturi_ins1 AS ON INSERT TO vFacturi WHERE NOT EXISTS (SELECT 1 FROM facturi WHERE NrFact=NEW.NrFact DO INSTEAD INSERT INTO facturi VALUES (NEW.NrFact, NEW.DataFact, NEW.CodCl) ; SQL. Dialecte DB2, Oracle, PostgreSQL i SQL Server 667 CREATE OR REPLACE RULE vFacturi_ins2 AS ON INSERT TO vFacturi WHERE NOT EXISTS (SELECT 1 FROM produse WHERE CodPr=NEW.CodPr) DO INSTEAD INSERT INTO produse VALUES (NEW.CodPr, NEW.DenPr, NEW.UM, NEW.Grupa, NEW.ProcTVA ) ; CREATE OR REPLACE RULE vFacturi_ins3 AS ON INSERT TO vFacturi DO INSTEAD INSERT INTO liniifact VALUES ( NEW.NrFact, NEW.Linie, NEW.CodPr, NEW.Cantitate, NEW.PretUnit ) ;
Figura 14.10. Propagarea inserrii n tabelele PRODUSE i LINIIFACT Testarea funcionalitii o vom face printr-un INSERT n care vom aduga o linie dintr-o factur nou i un produs la fel de nou: INSERT INTO vFacturi (NrFact, Linie, CodPr, Cantitate, PretUnit, DataFact, CodCl, DenPr, UM, Grupa, ProcTVA) VALUES (3120, 1, 8, 400, 1200, DATE'2007-10-01', 1001, 'Produs 8', 'buc', 'Cafea', 0.19) ;
Figura 14.10 demonstreaz c s-au fcut inserrile n cele trei tabele (n tabela FACTURI inserarea este implicit, altminteri restricia referenial ar fi fost nclcat). 14.3.4. Tabele virtuale n SQL Server SQL Server este comparabil n materie de tabele virtuale cu dialectele din DB2 i Oracle. Comenzile rulate n DB2 descrise n paragraful 14.3.1 sunt funcionale i n SQL Server, aa c nu mai insistm. 668 Capitolul 14 14.4. Restricii n tabele virtuale Dup cum am vzut la nceputul paragrafului 14.2, formatul general al comenzii CREATE VIEW are i o clauz important pe care o vom discuta pe scurt n continuare WITH CHECK OPTION. Aceast clauz permite ca la orice inserare sau modificare, nregistrrile din tabela virtual s respecte predicatul formulat n clauza WHERE. vJudeteMoldova conine nregistrrile tabelei JUDEE pentru care valoarea atributului Regiune este Moldova: CREATE VIEW vJudeteMoldova AS SELECT * FROM judete WHERE Regiune='Moldova' WITH CHECK OPTION
Comanda INSERT urmtoare: INSERT INTO vJudeteMoldova VALUES ('BC', 'Bacau', 'Moldova') se execut, fr probleme, ceea ce nu este valabil i pentru (vezi figura 14.11): INSERT INTO vJudeteMoldova VALUES ('AR', 'Arad', 'Banat')
Figura 14.11. Blocarea unei inserri de ctre clauza WITH CHECK OPTION Dac sursa unei tabele virtuale este tot o tabel virtual care, la rndul su, este tot o tabel virtual s.a.m.d., clauza CHECK OPTION poate folosi opiunea CASCADED pentru a verifica dac inserarea/modificarea respect predicatele din clauza WHERE ale tuturor view-urilor (de pe toate nivelurile) sau LOCAL pentru a verifica numei respectarea predicatului din clauza WHERE a tabelei virtuale curente. Dintre cele patru dialecte SQL, numai PostgreSQL nu are implementat clauza WITH CHECK OPTION, aa c i pentru acest gen de restricii sunt necesare declanatoare sau reguli. Standardele SQL nu prevd declararea de restricii clasice (cheie primar, cheie alternativ, restricie referenial etc.) ntr-o tabel virtual. Nici dialectele DB2, SQL Server i PostgreSQL. Oracle permite trei tipuri de restricii declarabile pentru un view: chei primare, chei alternative i restricii refereniale.