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