Oracle PL/SQL - Dipartimento di Ingegneria dell`Informazione
Transcript
Oracle PL/SQL - Dipartimento di Ingegneria dell`Informazione
Oracle PL/SQL Motivazioni • Supponiamo che nella gestione del database “Azienda” ci venga chiesto di apportare le modifiche necessarie a far sì che ad ogni impiegato possa essere assegnato, alla fine di ogni anno, un bonus da calcolarsi così: • Per ciascun progetto a cui un impiegato partecipa, l’impiegato riceve un bonus pari all’1% del budget di progetto • Gli impiegati che partecipano a più progetti hanno un bonus pari alla somma dei bonus relativi a ciascun progetto • Ogni volta che vengono portate a termine le operazioni per il calcolo del bonus, si vuole anche conoscere la somma totale dei bonus assegnati agli impiegati 2 1 Motivazioni • Le operazioni richieste, difficilmente possono essere eseguite attraverso un unico comando SQL • • • • Inizializzare a zero il valore del bonus di ciascun impiegato Conoscere il numero di progetti a cui partecipa ciascun impiegato Incrementare il valore del bonus sulla base del budget di progetto Tenere traccia di tutti gli incrementi effettuati per calcolare la somma di tutti I bonus • È dunque conveniente il ricorso a più comandi SQL per elaborare in cascata i dati dell’archivio, possibilmente facendo uso di opportune variabili di appoggio • Tutto questo richiede una estensione delle funzionalità base di SQL !!! 3 PL/SQL • Senza PL/SQL, le istruzioni SQL vengono trasmesse e processate una alla volta • Programmi che prevedono molte istruzioni SQL devono effettuare molteplici chiamate al DBMS, con ricadute su traffico di rete e tempi complessivi di elaborazione • Con PL/SQL, un intero blocco di istruzioni può essere inviato al DBMS • L’elaborazione dei risultati intermedi non deve essere trasmessa all’applicazione che riceve solo il risultato finale dell’elaborazione • Con PL/SQL si possono anche definire “stored procedures” che sono compilate e memorizzate in forma eseguibile per ridurre i tempi di esecuzione • Con le “stored procedures” il traffico di rete è ulteriorimente ridotto dato che si passano solo i parametri, non l’intero codice. Inoltre le “stored procedures” possono essere condivise tra utenti diversi riducendo i requisiti complessivi di memoria 4 2 Struttura • Ogni programma PL/SQL è costituito da blocchi logici (in cascata o nidificati) ciascuno dei quali raggruppa tre sezioni: • dichiarativa (DECLARE) • istruzioni d’esecuzione (BEGIN…END) • gestione eccezioni (EXCEPTION) • In un blocco, possono essere dichiarate costanti e variabili. Le variabili possono essere utilizzate per memorizzare i risultati di una query • Un blocco PL/SQL può contenere istruzioni SQL, strutture di controllo del flusso d’esecuzione (for-loop, while-loop, if-then-else, casewhen), manipolazione delle eccezioni (controllo errori), e chiamate ad altri blocchi PL/SQL • I blocchi PL/SQL che specificano procedure e funzioni possono essere raggruppati in packages 5 Struttura • Un’altra importante caratteristica di PL/SQL è che offre un meccanismo per processare i risultati delle query in un modo “orientato alle tuple” • una tupla alla volta • A questo scopo, vengono utilizzati i cursori • Un cursore è fondamentalmente un puntatore al risultato di una query ed è impiegato per leggere i valori degli attributi delle tuple selezionate, inserendoli in variabili • Un cursore è tipicamente usato in combinazione con un costrutto loop per elaborare, riga per riga, i risultati di una query 6 3 Dichiarazioni • Costanti, variabili, cursori ed eccezioni usate in un blocco PL/SQL devono essere dichiarati nella sezione di dichiarazione di quel blocco • La dichiarazione di variabili e costanti ha la seguente sintassi: <nome variabile> [costant] <tipo di dati> [not null] [:= <espressione>]; DECLARE wages NUMBER; --Inizializzazione con null hours_worked CONSTANT NUMBER := 40; hourly_salary NUMBER := 22.50; bonus NUMBER := 150; country VARCHAR2(128); --Inizializzazione con null … BEGIN … END; 7 / Dichiarazioni • PL/SQL supporta la dichiarazione ancorata del tipo di dato di una variabile • Si può richiedere che la variabile abbia lo stesso tipo di una particolare colonna di una tabella IMPIEGATO.Matricola%TYPE • Oppure abbia un tipo in grado di memorizzare una tupla completa di una determinata tabella (o risultato di query) DIPARTIMENTO%ROWTYPE 8 4 Dichiarazioni • La dichiarazione di un cursore specifica un insieme di tuple (come risultato di una query) in modo che le tuple possano essere processate individualmente, una alla volta, usando l’istruzione fetch • La dichiarazione di un cursore ha la seguente sintassi: cursor <nome cursore> [(<lista parametri>)] is <istruzione select>; • Se le tuple selezionate dal cursore devono essere modificate all’interno del blocco PL/SQL, deve essere aggiunta la clausola for update [(<colonna(e)>)] alla fine della dichiarazione del cursore • In questo caso le tuple vengono bloccate e gli altri utenti non vi possono accedere finché non è stato eseguito un comando commit 9 Dichiarazioni • Le eccezioni vengono usate per processare errori e avvisi che si incontrano durante l’esecuzione delle istruzioni PL/SQL • Alcune eccezioni sono definite internamente: • ZERO_DIVIDE (divisione per 0) • Altre eccezioni possono essere specificate dall’utente alla fine di un blocco PL/SQL • Le eccezioni definite dall’utente devono essere dichiarate usando la sintassi <nome eccezione> exception 10 5 Variabili • Ci sono tre modi per assegnare un valore ad una variabile: • Attraverso l’operatore := seguito dal valore DECLARE wages NUMBER; --Inizializzazione con null hours_worked CONSTANT NUMBER := 40; hourly_salary NUMBER := 22.50; bonus NUMBER := 150; country VARCHAR2(128); --Inizializzazione con null … BEGIN wages := (hours_worked * hourly_salary) + bonus; country := 'France'; country := UPPER('Canada'); … 11 END; / Variabili • Ci sono tre modi per assegnare un valore ad una variabile: • Attraverso l’operatore := seguito dal valore • Attraverso il costrutto SELECT … INTO • In questo caso bisogna che l’istruzione select recuperi almeno una tupla, altrimenti verrà notificato un errore! • Se l’istruzione recupera più di una tupla, allora deve essere utilizzato un cursore. DECLARE employee_rec EMP%ROWTYPE; max_sal EMP.SAL%TYPE; BEGIN select EMPNO, ENAME, JOB, MGR, SAL, COMM, HIREDATE, DEPTNO into employee_rec from EMP where EMPNO = 5698; select max(SAL) into max_sal from EMP; … 12 END; / 6 Variabili • Ci sono tre modi per assegnare un valore ad una variabile: • Attraverso l’operatore := seguito dal valore • Attraverso il costrutto SELECT … INTO • Attraverso passaggio di parametri ad una subroutine con i costrutti IN, OUT ed IN OUT DECLARE new_sal NUMBER(8,2); emp_id NUMBER(6) := 126; PROCEDURE adjust_salary(emp_id NUMBER, sal IN OUT NUMBER) IS emp_job VARCHAR2(10); avg_sal NUMBER(8,2); BEGIN … END; BEGIN SELECT AVG(salary) INTO new_sal FROM employees; adjust_salary(emp_id, new_sal); -- assigns a new value to new_sal 13 END; / Costrutti per il controllo di flusso • Costrutti per il controllo condizionale: • IF-THEN-ELSE • CASE-WHEN • Costrutti per il controllo di iterazione: • LOOP • FOR-LOOP • WHILE-LOOP • Costrutti per l’interruzione di flusso: • GOTO 14 7 Costrutti per il controllo di flusso • Per il controllo condizionale, PL/SQL offre costrutti if-then-else nella forma: if <condizione> then <sequenza di istruzioni> [elsif] <condizione> then <sequenza di istruzioni> … [else] <sequenza di istruzioni> end if; 15 Cursori • I cursori sono necessari per gestire i dati recuperati da istruzioni di select • Fanno eccezione le query che restituiscono un solo dato o una singola tupla • Prima che un cursore (già dichiarato nella sezione DECLARE) possa essere usato, deve essere aperto utilizzando l’istruzione open: open <nome cursore> [(<lista di parametri>)]; • L’istruzione select associata viene quindi processata e il cursore punta alla prima tupla restituita dalla select 16 8 Cursori • Le tuple selezionate possono essere processate una alla volta utilizzando il comando fetch fetch <nome cursore> [(<lista di parametri>)]; • Tipicamente la lista di parametri specifica alcune variabili dove memorizzare i valori dalla tupla corrente del cursore • le variabili devono avere lo stesso tipo di dati dei risultati della select • Dopo un comando fetch, il cursore avanza alla successiva tupla nell’insieme del risultato dell’istruzione select • Dopo che tutte le tuple sono state processate, si utilizza il comando close per chiudere e disabilitare il cursore close <nome cursore>; 17 Cursori • L’avanzamento del cursore all’interno del risultato può essere gestito attraverso un loop semplice: DECLARE cursor emp_cur is select * from EMP; emp_rec EMP%ROWTYPE; emp_sal EMP.SAL%TYPE; BEGIN open emp_cur; loop fetch emp_cur into emp_rec; exit when emp_cur%NOTFOUND; emp_sal := emp_rec.sal; … end loop; close emp_cur; … END; 18 / 9 Cursori • L’avanzamento del cursore all’interno del risultato può essere semplificato con un loop for: [<< <nome etichetta> >>] for <nome record> in <nome cursore> [(lista di parametri>)] loop <sequenza di istruzioni> end loop; • Un record utilizzabile per memorizzare una tupla recuperata da un cursore viene implicitamente dichiarato • Inoltre, questo loop esegue implicitamente una fetch ad ogni iterazione, una open prima dell’ingresso nel loop e una close dopo che il loop è terminato • Se ad un’iterazione nessuna tupla viene recuperata, il loop viene automaticamente terminato senza un exit 19 Cursori [<< <nome etichetta> >>] for <nome record> in <nome cursore> [(lista di parametri>)] loop <sequenza di istruzioni> end loop; • In un loop for è anche possibile specificare una query al posto di <nome cursore> : for <nome record> in (<istruzione select>) loop <sequenza di istruzioni> end loop; DECLARE … sal_rec EMP.SAL%TYPE; BEGIN … for sal_rec in (select SAL + COMM total from EMP) loop …; end loop; … END; 20 / 10 Esempio • Il seguente codice aumenta del 5% lo stipendio degli impiegati che hanno KING come loro responsabile DECLARE • Si usa il costrutto cursor-for-update che integra la funzione di update all’interno del cursore manager EMP.MGR%TYPE; cursor emp_cur (mgr_no number) is select SAL from EMP where MGR = mgr_no for update of SAL; BEGIN select EMPNO into manager from EMP where ENAME = 'KING'; for emp_rec in emp_cur(manager) loop update EMP set SAL = emp_rec.sal*1.05 where current of emp_cur; end loop; commit; END; 21 / Esercizio • Dopo aver abilitato l’output sul terminale SQLPLUS (set serveroutput on) definire ed eseguire una procedura che stampi matricola, cognome e stipendio di ciascun BEGIN impiegato for emp_rec in (select * from IMPIEGATO) loop DBMS_OUTPUT.PUT_LINE('Matricola: ' || emp_rec.matricola || ' Cognome: ' || emp_rec.cognome || ' Stipendio: ' || TO_CHAR(emp_rec.stipendio)); end loop; END; / DECLARE emp_mat IMPIEGATO.MATRICOLA%TYPE; emp_cog IMPIEGATO.COGNOME%TYPE; emp_sti IMPIEGATO.STIPENDIO%TYPE; BEGIN for emp_rec in (select * from IMPIEGATO) loop emp_mat := emp_rec.matricola; emp_cog := emp_rec.cognome; emp_sti := emp_rec.stipendio; DBMS_OUTPUT.PUT_LINE ('Matricola: ' || emp_mat || ' Cognome: ' || emp_cog || ' Stipendio: ' || TO_CHAR(emp_sti)); end loop; END; / 22 11 DECLARE cursor emp_cur is select * from IMPIEGATO; emp_rec IMPIEGATO%ROWTYPE; emp_mat IMPIEGATO.MATRICOLA%TYPE; emp_cog IMPIEGATO.COGNOME%TYPE; emp_sti IMPIEGATO.STIPENDIO%TYPE; BEGIN open emp_cur; loop fetch emp_cur into emp_rec; exit when emp_cur%NOTFOUND; emp_mat := emp_rec.matricola; emp_cog := emp_rec.cognome; emp_sti := emp_rec.stipendio; DBMS_OUTPUT.PUT_LINE ('Matricola: ' || emp_mat || ' Cognome: ' || emp_cog || ' Stipendio: ' || TO_CHAR(emp_sti)); end loop; close emp_cur; END; / DECLARE cursor emp_cur is select * from IMPIEGATO; emp_mat IMPIEGATO.MATRICOLA%TYPE; emp_cog IMPIEGATO.COGNOME%TYPE; emp_sti IMPIEGATO.STIPENDIO%TYPE; BEGIN for emp_rec in emp_cur loop emp_mat := emp_rec.matricola; emp_cog := emp_rec.cognome; emp_sti := emp_rec.stipendio; DBMS_OUTPUT.PUT_LINE ('Matricola: ' || emp_mat || ' Cognome: ' || emp_cog || ' Stipendio: ' || TO_CHAR(emp_sti)); end loop; END; 23 / Esercizio Esercizio • Con riferimento alla base dati impiegatodipartimento-progetto-pp, definire una procedura che individui i responsabili di progetto che guadagnano meno degli impiegati che supervisionano ed aumentarne lo stipendio in modo che guadagnino almeno quanto gli impiegati che supervisionano: • Per ogni supervisore calcolare lo stipendio massimo dei suoi supervisionati • Se maggiore del proprio stipendio aggiornare il proprio stipendio a tale valore 24 12 Esercizio • Per ogni supervisore calcolare lo stipendio massimo dei suoi supervisionati: select s.matricola, s.stipendio, max(i.stipendio) from impiegato s, progetto p, pp, impiegato i where s.matricola=p.responsabile AND p.codice=pp.progetto AND pp.impiegato=i.matricola group by s.matricola, s.stipendio 25 Esercizio • Nota la matricola del supervisore aggiornarne il valore di stipendio: update impiegato set stipendio = new_stipendio where matricola=mat_leader 26 13 Esercizio DECLARE cursor emp_cur is select s.matricola, s.stipendio, max(i.stipendio) as max_stip from impiegato s, progetto p, pp, impiegato i where s.matricola=p.responsabile AND p.codice=pp.progetto AND pp.impiegato=i.matricola group by s.matricola, s.stipendio; emp_mat IMPIEGATO.MATRICOLA%TYPE; emp_sti_s IMPIEGATO.STIPENDIO%TYPE; emp_sti_i IMPIEGATO.STIPENDIO%TYPE; BEGIN for emp_rec in emp_cur loop emp_mat := emp_rec.matricola; emp_sti_s := emp_rec.stipendio; emp_sti_i := emp_rec.max_stip; if emp_sti_s < emp_sti_i then update impiegato set stipendio = emp_sti_i where matricola=emp_mat; commit; end if; end loop; END; / 27 Stored procedures • Il comando CREATE PROCEDURE (o CREATE FUNCTION) consente di definire procedure (o funzioni) che vengono memorizzate nel server e richiamate attraverso il comando EXECUTE • Procedure e funzioni possono ricevere parametri di ingresso che vengono passati alla loro chiamata • Le funzioni possono anche restituire dei valori al termine della loro esecuzione 28 14 Stored procedures • Sintassi per la definizione di una procedura… create [or replace] procedure <nome procedura> [(<lista di parametri>)] is <dichiarazione> begin <sequenza di istruzioni> [exception <routine di gestione dell’eccezione>] end [<nome procedura>]; • …e di una funzione: create [or replace] function <nome funzione> [(<lista di parametri>)] return <tipo di dati> is <dichiarazione> begin … 29 Stored procedures • Una volta create, procedure e funzioni possono essere cancellate attraverso i comandi drop procedure <nome procedura> drop function <nome funzione> 30 15 Stored procedures • La specifica dei parametri prevede la seguente sintassi: <nome parametro> [IN | OUT | IN OUT] <tipo di dato> [{:= | DEFAULT} <espressione>] • La clausola opzionale IN, OUT, e IN OUT specifica il modo nel quale il parametro è utilizzato (se omessa il parametro è considerato IN) • IN significa che il parametro è accessibile in lettura ma non in scrittura • OUT significa che il parametro è accessibile in scrittura ma non in lettura • IN OUT permette di accedere al parametro sia in lettura che scrittura 31 Stored procedures • Procedura che aumenta lo stipendio degli affiliati ad un dipartimento di una percentuale variabile • Una volta creata, la procedura può essere chiamata con execute raise_salary('D002', 0.3) • dal terminale sqlplus… Se la procedura viene chiamata dall’interno di un blocco PL/SQL, la parola chiave execute viene omessa create procedure raise_salary(dno dipartimento.codice%TYPE, percentage number DEFAULT 0.5) is cursor emp_cur (dept_no dipartimento.codice%TYPE) is select STIPENDIO from IMPIEGATO where DIPARTIMENTO = dept_no for update of STIPENDIO; empsal number(8); begin for emp_rec in emp_cur(dno) loop update IMPIEGATO set STIPENDIO = emp_rec.STIPENDIO*((100+percentage)/100) where current of emp_cur; end loop; commit; 32 end raise_salary; 16 Stored procedures • …oppure con begin raise_salary('D002', 0.3); end; • dall’interno di un blocco PL/SQL • Questa è la modalità da adottare per eseguire la procedura dal pannello ‘SQL commands’ dell’interfaccia web create procedure raise_salary(dno dipartimento.codice%TYPE, percentage number DEFAULT 0.5) is cursor emp_cur (dept_no dipartimento.codice%TYPE) is select STIPENDIO from IMPIEGATO where DIPARTIMENTO = dept_no for update of STIPENDIO; empsal number(8); begin for emp_rec in emp_cur(dno) loop update IMPIEGATO set STIPENDIO = emp_rec.STIPENDIO*((100+percentage)/100) where current of emp_cur; end loop; commit; 33 end raise_salary; Stored procedures • Da notare che la procedura poteva essere scritta molto più semplicemente senza ricorrere al costrutto del cursor for update create procedure quick_raise_salary(dno dipartimento.codice%TYPE, percentage number DEFAULT 0.5) is begin update IMPIEGATO set STIPENDIO = STIPENDIO*((100+percentage)/100) where DIPARTIMENTO=dno; end quick_raise_salary; • Il costrutto del cursor for update è tuttavia necessario quando l’elaborazione richiede di operare su una riga alla volta di una tabella 34 17 Stored procedures • Per poter richiamare una funzione da una shell di SQL*Plus, è necessario prima definire una variabile alla quale assegnare il risultato della funzione • In SQL*Plus, una variabile può essere definita utilizzando il comando variable <nome variabile> <tipo di dati>; • variable salary number. La funziona sopra può quindi essere chiamata usando il comando • execute :salary := get_dept_salary(20); • Da notare i due punti che devono essere messi davanti alla variabile salary 35 Stored procedures • La seguente funzione incrementa gli stipendi e restituisce la somma degli incrementi effettuati create or replace function raise_salary(dno varchar2, percentage number DEFAULT 0.5) return number is delta_sal number; cursor emp_cur (dept_no varchar2) is select STIPENDIO from IMPIEGATO where DIPARTIMENTO = dept_no for update of STIPENDIO; empsal IMPIEGATO.STIPENDIO%TYPE; begin delta_sal := 0.0; open emp_cur(dno); loop fetch emp_cur into empsal; exit when emp_cur%NOTFOUND; if empsal IS NOT NULL then delta_sal := delta_sal + empsal*(percentage/100); update IMPIEGATO set STIPENDIO = empsal*((100+percentage)/100) where current of emp_cur; end if; end loop; close emp_cur; commit; 36 return delta_sal; end raise_salary; 18 Stored procedures • Il funzionamento della funzione può essere verificato eseguendo dalla shell SQLPLUS i seguenti comandi: declare dato number(12,2); begin dato := raise_salary('D001'); dbms_output.put_line(to_char(dato)); end; / 37 Esercizio • Aggiungere alla tabella impiegato un campo bonus (number) • Definire una funzione che imposti il valore di bonus in base al seguente criterio: • Per ciascun progetto a cui un impiegato partecipa, l’impiegato riceve un bonus pari all’1% del budget di progetto • Gli impiegati che partecipano a più progetti hanno un bonus pari alla somma dei bonus relativi a ciascun progetto • Ogni volta che viene chiamata la funzione restituisce la somma dei bonus assegnati agli impiegati 38 19 Soluzione alter table IMPIEGATO add BONUS number default 0.0; create or replace function calcola_bonus return IMPIEGATO.BONUS%TYPE is total_bonus IMPIEGATO.BONUS%TYPE; current_bonus IMPIEGATO.BONUS%TYPE; cursor emp_cur is select * from IMPIEGATO for update of BONUS; cursor proj_budget (emp_mat IMPIEGATO.MATRICOLA%TYPE) is select p.budget from progetto p, pp where IMPIEGATO=emp_mat AND PROGETTO=CODICE; begin total_bonus := 0.0; for emp_rec in emp_cur loop current_bonus := 0.0; for cur_bud in proj_budget(emp_rec.matricola) loop current_bonus := current_bonus + cur_bud.budget*0.01; end loop; update IMPIEGATO set BONUS = current_bonus where current of emp_cur; total_bonus := total_bonus + current_bonus; end loop; commit; return total_bonus; end calcola_bonus; 39 Soluzione declare bingo impiegato.bonus%type; begin bingo := calcola_bonus; DBMS_OUTPUT.PUT_LINE(to_char(bingo)); end; / 40 20