Le SQL dynamique permet de construire dans un programme une requête SQL avant de lexécuter.
Lutilité principale est de fabriquer un code « générique » et réutilisable.
Sans cela, le paramétrage dune requête se limite aux valeurs de remplacement de la clause where :
CURSOR C_CURSEUR( LN$Id IN EMP.empid%TYPE ) ISSELECT
empno
FROM
EMP
WHERE
empid = LN$Id
;
FOR C_EMP IN C_CURSEUR( 1024 ) LOOP
.
Dans cet exemple on voit que seul le numéro de lemployé donne son caractère paramétrable à la requête.
Ni les colonnes ramenées, ni le nom de la table ne peuvent être paramétrées, pas plus que les termes
de la clause where.
CURSOR C_CURSEUR( LN$Id IN EMP.empid%TYPE ) ISSELECTCOUNT(*)
FROM
EMP
WHERE
empid > LN$Id
;
OPEN C_CURSEUR( 1024 ) ;
FETCH C_CURSEUR INTOLN$Total ;
CLOSE C_CURSEUR
.
Dans cet autre exemple qui permet un comptage denregistrements, on voit que cette requête ne compte
que les enregistrements de la table EMP sur un critère figé (empid > LN$Id).
Dans loptique dune procédure qui doit généraliser ce type de comptage sur plusieurs tables, il faut écrire
autant de curseurs que de tables visées.
Le SQL dynamique permet de saffranchir de cette limitation en permettant de « construire » lintégralité
des termes dune requête.
FUNCTION Compte_rec
(
PC$TableINVARCHAR2,
PC$ClauseWhere INVARCHAR2
) RETURNPLS_INTEGERIS
LC$Requete VARCHAR2(512) ;
LN$Total PLS_INTEGER ;
BEGIN
LC$Requete := Selectcount(*) From || PC$Table || where || PC$ClauseWhere ;
EXECUTEIMMEDIATE LC$Requete INTOLN$Total ;
returnLN$Total ;
END ;
Cette fonction utilisant le SQL dynamique est totalement générique puisquelle permet
dinterroger nimporte quelle table passée en paramètre avec des clauses where totalement
différentes dun appel à lautre.
LN$Nombre := Compte_rec( EMP, empid > 1024 ) ;
LN$Nombre := Compte_rec( EMP, empname like MART% ) ;
LN$Nombre := Compte_rec( DEPT, deptno between 1 and 15 ) ;
Etc
Le SQL dynamique permet également décrire et de compiler des programmes manipulant des objets
qui ne sont pas encore existants au moment de la compilation et dexécuter des ordres de description de données (CREATE TABLE ).
2. Les avantages du SQL dynamique natif
Codage simplifié par rapport au package DBMS_SQL qui impose de nombreuses fonctions dans un ordre strict.
En moyenne, le SQL dynamique natif sexécute de 1.5 à 3 fois plus rapidement.
Support des types définis
Le SQL dynamique natif supporte tous les types définis par lutilisateur, Objets, collections, etc
Support des types RECORD pour les ordres Select
Le SQL dynamique natif permet dutiliser un objet de type RECORD dans une clause INTO
3. Les avantages du package DBMS_SQL
Supporté par le client
Les fonctions du package DBMS_SQL sont supportées par les applications clientes,
A linverse du SQL dynamique natif.
Support de la clause RETURNING pour les opération UPDATE et DELETE
DBMS_SQL supporte la clause RETURNING pour des opérations UPDATE ou DELETE effectuées sur plusieurs lignes.
Le SQL dynamique natif ne supporte une clause RETURNING que sur les opérations ne retournant quune seule ligne.
4. EXECUTE IMMEDIATE
Cette fonction est disponible depuis la version Oracle8i
4.1. Dynamic_string
Chaîne de caractères, variable ou expression contenant un ordre SQL ou un block PL/SQL
Dans le cas dun ordre SQL, le caractère de terminaison( ;) ne doit pas figurer dans la chaîne, alors quil est nécessaire pour terminer un block PL/SQL
La chaîne contenant lordre peut contenir des paramètres de substitution ( :1, :2, etc.). Cela permet la réutilisation du curseur par Oracle
DECLARE
LC$Requete VARCHAR2(256) ;
Emp_id emp.emp_id%TYPE := 1214 ;
BEGIN
LC$Requete := CREATETABLE XX ( ID NUMBER, LIBELLE VARCHAR2(100) );
EXECUTEIMMEDIATE LC$Requete ;
LC$Requete := BEGIN Mon_package.Ma_procedure( :1 ); END;;
EXECUTEIMMEDIATE LC$Requete USING Emp_id;
END ;
4.2. INTO
Nest utilisé que pour les requêtes ne ramenant quune ligne.
A chaque colonne ramenée par la requête doit correspondre une variable de type compatible dans la clause INTO.
Une variable de type RECORD peut également être utilisée.
DECLARE
Emp_id emp.emp_id%TYPE := 1214 ;
Emp_name emp.name_id%TYPE ;
Emp_rec emp%ROWTYPE ;
LC$Requete VARCHAR2(256) ;
BEGIN
LC$Requete:= SELECT emp_name from EMP WHERE empno = :1;
EXECUTEIMMEDIATE LC$Requete INTO Emp_name USING Emp_id ;
LC$Requete:= SELECT * from EMP WHERE empno = :1;
EXECUTEIMMEDIATE LC$Requete INTO Emp_rec USING Emp_id ;
END ;
Dans la première requête, la valeur de la colonne emp_name de la table EMP est copiée
dans la variable locale Emp_name.
Dans la deuxième requête, les valeurs de toutes les colonnes de la table EMP sont copiées
dans la variable locale Emp_rec.
4.3. USING
Cette clause définit la liste des arguments de substitution (bind arguments).
Sans spécification de mode de paramètre, le défaut est IN.
Ces arguments ne peuvent pas être de type Boolean (TRUE, FALSE) ni de type NULL.
Pour passer un argument de type NULL, il faut déclarer une variable et lui affecter la valeur NULL.
DECLARE
Emp_id emp.emp_id%TYPE := 1214 ;
Salary NUMBER(7,2) ;
LN$NullNUMBER := NULL ;
LC$Requete VARCHAR2(256) ;
BEGIN
LC$Requete:= SELECT salary from EMP WHERE empno = :1;
EXECUTEIMMEDIATE LC$Requete INTO salary USING Emp_id ;
LC$Requete:= UPDATE EMP SET salary = :1 WHERE empno = :2;
EXECUTEIMMEDIATE LC$Requete USINGNULL, Emp_id ; -- Incorrect
LC$Requete:= UPDATE EMP SET salary = :1 WHERE empno = :2;
EXECUTEIMMEDIATE LC$Requete USINGLN$Null, Emp_id ; -- CorrectEND ;
Dans cet exemple, le paramètre de substitution :1 prend la valeur de la variable Emp_id.
4.4. RETURNING INTO
Nest utilisé que pour les ordres de manipulation (DML) dotés dune clause RETURNING.
Cette clause définit la ou les variables dans lesquelles les valeurs de colonnes sont retournées.
DECLARE
Emp_id emp.emp_id%TYPE := 1214 ;
Salary NUMBER(7,2) ;
LC$Requete VARCHAR2(256) ;
BEGIN
LC$Requete:= UPDATE EMP SET sal = sal * 1.1 WHERE empno = :1 RETURNING sal INTO :2;
EXECUTEIMMEDIATE LC$Requete USING Emp_id RETURNING INTO Salary;
END ;
Le SQL dynamique supporte tous les types SQL (Collections, Objets LOB, Instances de types objets, refs)
mais aucun type spécifique PL/SQL à lexception du type RECORD dans une clause INTO.
Lutilisation de paramètres de substitution permet la réutilisation de linstruction SQL.
LC$Requete:= DELETEFROM EMP WHERE empno = || LN$Id ; -- non réutilisé
LC$Requete:= DELETEFROM EMP WHERE empno = :2; -- réutilisé
La chaîne contenant lordre peut être entièrement constituée par programmation.
Seul le nom de la table ne peut pas être passé via un argument de substitution.
DECLARE
Emp_id emp.emp_id%TYPE := 1214 ;
LC$TableVARCHAR2(30) := EMP ;
LC$Requete VARCHAR2(256) ;
BEGIN
LC$Requete:= UPDATE :1 SET salary = 1000 WHERE empno = :2;
EXECUTEIMMEDIATE LC$Requete USING LC$Table, Emp_id ; -- Incorrect
LC$Requete:= UPDATE || LC$Table || SET salary = 1000 WHERE empno = :2;
EXECUTEIMMEDIATE LC$Requete USING Emp_id ; -- CorrectEND ;
Pour assurer la compatibilité avec les versions précédentes, les valeurs retournées dans la clause RETURNING INTO
peuvent lêtre également dans la clause USING.
DECLARE
LC$Requete VARCHAR2(256);
my_empno NUMBER(4) := 7902;
my_ename VARCHAR2(10);
my_job VARCHAR2(9);
my_sal NUMBER(7,2) := 3250.00;
BEGIN
LC$Requete := 'UPDATE emp SET sal = :1 WHERE empno = :2 RETURNING ename, job INTO :3, :4';
/* valeurs retournées dans la clause USING */
EXECUTEIMMEDIATE LC$Requete USING my_sal, my_empno, OUT my_ename, OUT my_job;
/* valeurs retournées dans la clause RETURNING INTO */
EXECUTEIMMEDIATE LC$Requete USING my_sal, my_empno RETURNING INTO my_ename, my_job;
END;
Si vous utilisez EXECUTE IMMEDIATE pour exécuter une procédure acceptant un paramètre OUT :
Il est possible dutiliser linstruction EXECUTE IMMEDIATE pour renseigner une collection
(index-by table, nested table, varay).
Les éléments de ces collections doivent être de type SQL (CHAR, DATE,NUMBER, )
Les 3 instructions supportant le BULK COLLECT dynamique sont :
EXECUTE IMMEDIATE
EXECUTEIMMEDIATE dynamic_string
[[BULK COLLECT] INTO define_variable[, define_variable ...]]
[USING bind_argument[, bind_argument ...]]
[{RETURNING | RETURN}
BULK COLLECT INTO bind_argument[, bind_argument ...]];
DECLARETYPE T_EMP_NAME isTABLEof EMP.emp_name%TYPE;
TYPE T_EMP_SAL isTABLEof EMP.salary%TYPE;
T_Names T_EMP_NAME ;
T_Sal T_EMP_SAL ;
LC$Requete VARCHAR2(256);
BEGIN
LC$Requete:= SELECT emp_name, salary from EMP;
EXECUTEIMMEDIATE LC$Requete BULK COLLECT INTO T_Names, T_sal;
END;
Seules les instructions INSERT, UPDATE ou DELETE peuvent avoir des valeurs retournées dans la clause RETURNING INTO
DECLARETYPE T_EMP_NAME isTABLEof EMP.emp_name%TYPE;
TYPE T_EMP_SAL isTABLEof EMP.salary%TYPE;
T_Names T_EMP_NAME ;
T_Sal T_EMP_SAL ;
Bonus NUMBER := 500 ;
LC$Requete VARCHAR2(256);
BEGIN
LC$Requete := 'UPDATE emp SET bonus = :1 RETURNING emp_name INTO :2';
EXECUTEIMMEDIATE LC$Requete USING bonus RETURNING BULK COLLECT INTO T_Names;
END ;
FETCH
FETCH dynamic_cursor BULK COLLECT INTO define_variable[, define_variable ...];
DECLARETYPE T_EMP_ID isTABLEof EMP.empno%TYPE;
TYPE T_EMP_NAME isTABLEof EMP.ename%TYPE;
TYPE EmpCurTyp isREFCURSOR;
Emp_cv EmpCurTyp ;
T_Id T_EMP_ID ;
T_Nom T_EMP_NAME ;
LC$Requete VARCHAR2(256);
BEGIN
LC$Requete:= SELECT emp_no, ename from EMP ;
OPEN Emp_cv for LC$Requete ;
FETCH Emp_cv BULK COLLECT INTO T_id, T_Nom ;
CLOSE Emp_cv ;
END;
FORALL
FORALL indexINlower bound..upper bound
EXECUTEIMMEDIATE dynamic_string
USING bind_argument | bind_argument(index)
[, bind_argument | bind_argument(index)] ...
[{RETURNING | RETURN} BULK COLLECT
INTO bind_argument[, bind_argument ... ]];
DECLARETYPE T_EMP_ID isTABLEof EMP.empno%TYPE;
TYPE T_EMP_NAME isTABLEof EMP.ename%TYPE;
T_Id T_EMP_ID ;
T_Nom T_EMP_NAME ;
LC$Requete VARCHAR2(256);
BEGIN
LC$Requete:= SELECT emp_no, ename from EMP;
EXECUTEIMMEDIATE LC$Requete BULK COLLECT INTO T_Id, T_Nom;
FORALL i IN T_Id.first..T_Id.last
EXECUTEIMMEDIATE 'UPDATE emp SET sal = sal * 1.1 WHERE empno = :1 RETURNING ename INTO :2'
USING T_Id(i) RETURNING BULK COLLECT INTO T_Nom;
END;
4.5. Attributs de curseur
Les 4 attributs de curseur %FOUND, %ISOPEN, %NOTFOUND, et %ROWCOUNT sont bien évidement valorisés par
linstruction EXECUTE IMMEDIATE.
function Supprime_lignes ( LN$MaxSal INNUMBER ) returnNUMBERIS
LC$Requete VARCHAR2(256) ;
BEGIN
LC$Requete:= DELETEFROM EMP WHERE Sal > :1 ;
EXECUTEIMMEDIATE LC$Requete USINGLN$MaxSal;
returnSQL%ROWCOUNT ;
END ;