Le Langage PL/SQL de Oracle (Brève Introduction)
Najib Tounsi
Lien permanent http://www.mescours.ma/DB/Polys/SQL/PLSQL/PLSQL-tdm.html
Sommaire
SELECT
tuple
par tuple.[...]
signifie optionnel, <...>
pour partie programmeur[<Enête de bloc>]
[DECLARE
<Constantes>
<Variables>
<Cursors>
<Exceptions utilisateurs>]
BEGIN
<Instruction PL/SQL>
[EXCEPTION <Traitement d'exception>]
END;
SQL> SET SERVEROUTPUT ON
SQL> BEGIN
2 dbms_output.put_line('Bonjour');
3 END;
4 /
Bonjour
BEGIN <une
insctruction> END;
dbms_output.put_line()
permet d'écrire sur la console
SQLPlus.SET SERVEROUTPUT ON
sous SQLPlus pour
basculer sur le mode sortie console./
. C'est la demande
d'exécution du programme tapée (donc la fin de saisie).E2
',
cherché avec SELECT
. Un seul tuple donc.DECLARE
-- Partie optionnelle
vEname employee.ename%TYPE; vSalary employee.Salary%TYPE;
BEGIN
-- Partie obligatoire
SELECT ename , salary INTO vEname , vSalary FROM employee WHERE enum = ’E2’; DBMS_OUTPUT.PUT_LINE('Nom = ' || vEname ); DBMS_OUTPUT.PUT_LINE('Salaire = ' || vSalary); EXCEPTION
-- Partie optionnelle WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erreur : ' || TO_CHAR(sqlcode)); DBMS_OUTPUT.PUT_LINE('Erreur : ' || sqlerrm); END; /
DECLARE
dateEmbauche date; /* initialisation implicite avec null */
nom varchar2(80) := ’Benali’;
trouve boolean; /* initialisation implicite avec null aussi*/
incrSalaire constant number(3,2) := 1.5; /* constante */
. . .
BEGIN . . . END;
NOT NULL
spécifié)NB. Les exemples dans la suite portent sur la table :
Variable de type colonne
DECLARE
maxSal employee.salary%TYPE;
INTO
, voir plus bas)Variable de type ligne
DECLARE
employeeRec employee%ROWTYPE;
SELECT *
... (voir plus loin)DECLARE
CURSOR empCursor IS
SELECT * FROM employee WHERE dept = 123
empCursor
désigne la zone mémoire recevant le résultat de SELECT..DECLARE
CURSOR empCursor (dno number ) IS
SELECT * FROM employee WHERE dept = dno;
dno
sera passé lors de OPEN.OPEN empCursor (123);
DECLARE
CURSOR empCursor IS
SELECT * FROM employee WHERE dept = 123
FOR UPDATE OF salary;
DECLARE
quantite integer := 0;
...
BEGIN
quantite := quantite + 5;
...
END
SELECT
à une structure ou à une liste de variable (cf. SELECT ... INTO
...)DECLARE
employeeRec employee%ROWTYPE;
maxSal employee.salary%TYPE;
BEGIN
SELECT * INTO employeeRec
FROM employee WHERE enum='E7';
dbms_output.put_line(employeeRec.ename||' '||employeeRec.Salary);
END;
SELECT
mono-tuple! La
clause WHERE
porte sur une valeur de clé enum
(ligne 6). (Pour plusieurs tuples il faut une variable CURSOR
)ROWTYPE
(ligne 2) dans cet exemple. La
variable contient donc un tuple complet.*
dans la clause SELECT
(ligne 5).employeeRec.ename
(variableTuple.attribut) pour accéder aux
différents composants qui sont donc ceux déclarés dans le schéma de
la relation.BEGIN
SELECT ename, salary INTO employeeRec.ename, employeeRec.Salary
FROM employee
WHERE enum = 'E8';
dbms_output.put_line(employeeRec.ename||' '||employeeRec.Salary);
END;
BEGIN
SELECT max(salary) INTO maxSal
FROM employee;
dbms_output.put_line('Salaire Maximum: '|| maxsal);
END;
IF <condition> THEN <séquence d'instructions>
[ELSIF <condition> THEN <séquence d'instructions> ]
. . .
[ELSE <séquence d'instructions> ]
END IF ;
ELSIF
pour suite de tests et END
IF
pour finir le IF
.[<label>]
WHILE <condition> LOOP
<séquence d'instructions>;
END LOOP [<label>] ;
DECLARE
i number;
BEGIN
i:=0;
WHILE i<8 LOOP
dbms_output.put_line(i);
i:= i+1;
END LOOP ;
END;
[<label name>]
FOR <index> IN [reverse] <lower bound>..<upper bound> LOOP
<séquence d'instructions> ;
END LOOP [<label name>] ;
BEGIN
FOR i IN 4..7 LOOP
dbms_output.put_line(i);
END LOOP ;
END;
LOOP ... END LOOP ;
EXIT WHEN ...
DECLARE
CURSOR empCursor IS
SELECT * FROM EMPLOYEE;
employeeRec employee%ROWTYPE;
maxSal employee.SALary%TYPE := 0;
BEGIN
OPEN empCursor;
LOOP
/* Accès à chacun des tuples */
FETCH empCursor INTO employeeRec;
EXIT WHEN empCursor%NOTFOUND;
/* traitement du tuple */
IF maxSal < employeeRec.salary THEN
maxSal := employeeRec.salary;
END IF;
/* fin traitement tuple */
END LOOP ;
dbms_output.put_line('Salaire Maximum: '|| maxsal);
CLOSE empCursor;
END;
FETCH
(au lieu de select
.
Pourquoi?)%NOTFOUND
est un attribut boolean du CURSOR
empCursor.OPEN,
empCursor%NOTFOUND
est évaluée à null. Après un FETCH
elle est
évaluée à false si un tuple est retrouvé, à true
sinon.EXIT WHEN
est vérifiée (aucun tuple retrouvé par FETCH
).CLOSE
empCursor;
.OPEN
...%FOUND
, %ISOPEN
,
%NOTFOUND
, et %ROWCOUNT
(le nombre de
lignes déjà retrouvés par FETCH).EXIT WHEN (empCursor%ROWCOUNT > 5) OR
(empCursor%NOTFOUND)
. Usage se %FOUND
, pour boucler. Mêmes déclarations.
BEGIN
OPEN empCursor;
FETCH empCursor INTO employeeRec;
WHILE empCursor%FOUND LOOP
/* traitement du tuple */
IF maxSal < employeeRec.salary THEN
maxSal := employeeRec.salary;
END IF;
/* fin traitement tuple */
FETCH empCursor INTO employeeRec;
END LOOP ;
dbms_output.put_line('Salaire Maximum: '|| maxsal);
CLOSE empCursor;
END;
BEGIN
FOR employeeRec IN empCursor LOOP
/* traitement du tuple */
IF maxSal < employeeRec.salary THEN
maxSal := employeeRec.salary;
END IF;
/* fin traitement tuple */
END LOOP;
dbms_output.put_line('Salaire Maximum: '|| maxsal);
END;
employeeRec
est
implicitement déclarée du type du cursor.FETCH
à
chaque itération (A chaque itération, un seul tuple est retrouvé.)FOR
exécute aussi automatiquement un OPEN
avant d'entrer en boucle et un CLOSE
en fin de
boucle. EXIT
)
dès qu'aucun tuple n'est trouvé.BEGIN
FOR untel IN (SELECT * FROM employee )
LOOP
dbms_output.put_line('Num = ' || untel.enum ||
', Nom = ' || untel.ename ||
', Salaire = '|| untel.salary);
END LOOP;
END;
untel
,
implicitement déclarée, reçoit le résultat accessible par untel.enum
etc.SELECT
calculée.FOR salVar IN (SELECT salary * 1.07 nouveau FROM employee) LOOP
/* ... */
END LOOP ;
SELECT
.
Résultat accessible par salVar.nouveau
.Usage du mot clé TYPE
avec REF CURORSOR
.
Declare
-- le curseur
TYPE EmpCurType IS REF CURSOR;
empCur EmpCurType;
-- les variables
nom employee.ename%TYPE ;
Begin
OPEN empCur FOR 'SELECT ename FROM employee' ;
FETCH empCur INTO nom ; -- On FETCH le premier...
dbms_output.put_line( nom ) ;
CLOSE empCur;
End ;
DECLARE CURSOR empCur is SELECT Salary FROM employee
WHERE DEPT = 'D1' FOR UPDATE OF salary;
BEGIN
FOR empRec IN empCur LOOP
UPDATE employee
SET salary = empRec.salary * 1.05
WHERE current of empCur ;
END LOOP ;
COMMIT;
END;
WHEN <nom d'exception> THEN <séquence d'instructions>;
DECLARE
employeeRec employee%ROWTYPE;
BEGIN
-- chercher les noms et salaires d'employés d'un département donné
SELECT ename, salary INTO employeeRec.ename, employeeRec.Salary
FROM EMPLOYEE
WHERE dept = '&dnum'; -- à lire avant --
dbms_output.put_line(employeeRec.ename||' '||employeeRec.Salary);
EXCEPTION
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('Trop d employés.');
END;
CURSOR_ALREADY_OPEN: tentative d'ouverture de CURSOR déjà ouvert
INVALID_CURSOR: par exemple FETCH sur un CURSOR déjà fermé
NO_DATA_FOUND: aucun tuple retourné (SELECT INTO ou FETCH)
TOO_MANY_ROWS: SELECT INTO retourne plus d'un tuple
...
ZERO_DIVIDE: tentative de division par zéro
WHEN NO DATA FOUND THEN rollback;
RAISE <nom d'exception>
DECLARE
sal employee.salary%TYPE;
num employee.enum%TYPE;
tropGrand exception;
CURSOR empCurseur is SELECT enum , salary
FROM EMPLOYEE
WHERE DEPT = '&dept'
FOR UPDATE OF SALary;
BEGIN
OPEN empCurseur;
LOOP
FETCH empCurseur INTO num, sal;
EXIT WHEN empCurseur%NOTFOUND;
IF sal * 1.05 > 4000 THEN RAISE tropGrand;
ELSE
UPDATE EMPLOYEE SAT salary = sal * 1.05
WHERE CURRENT OF empCurseur ;
dbms_output.put_line (num || ' mis a jour.');
END IF ;
END LOOP ;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('pas trouve');
rollback;
WHEN tropgrand THEN
INSERT INTO veterans VALUES (num, sal);
dbms_output.put_line(num||' '||Sal || ' Trop grand');
COMMIT;
END;
exception
.tropGrand
est déclarée de type exception
,
pour être utilisée dans raise
.Voir aussi PL/SQL par l'exemple.