Support de cours Base de données - Le PL/SQL 1 Le PL/SQL Généralité .............................................................................................................................................................................................................................. 1 La notion de bloc PL/SQL (anonyme ou non) ................................................................................................................................................................... 1 Les types ................................................................................................................................................................................................................................ 1 Les variables et leur affectation ..................................................................................................................................................................................... 2 Les constantes ..................................................................................................................................................................................................................... 2 Les structures de contrôle ............................................................................................................................................................................................... 2 Les procédures et les fonctions ....................................................................................................................................................................................... 3 Les packages ......................................................................................................................................................................................................................... 5 La restitution de l’information ......................................................................................................................................................................................... 6 La récupération des valeurs d’un requête récupérant un seul enregistrement ..................................................................................................... 7 Les curseurs ......................................................................................................................................................................................................................... 7 Les triggers basés .............................................................................................................................................................................................................. 9 Les exceptions ..................................................................................................................................................................................................................... 9 L’auto indendation de la clé primaire .............................................................................................................................................................................. 11 L’utilisation d’une fonction PL/SQL dans une requête SQL ...................................................................................................................................... 11 Le SQL dynamique .............................................................................................................................................................................................................. 11 Généralité Le PL/SQL est un langage procédural, compilé, intégré au noyau Oracle. Il est intégré à la base de données (procédure ou fonction stockées dans la base) et le code est exécuté sur le serveur. Comme le langage Ada, les procédures et les fonctions peuvent être dans des packages constituant un ensemble logique (par exemple, le pakage codage va contenir toutes les fonctionnalités des codages). Ce langage est non case sensitive, l’indentation est libre et les commentaires sont placés entre /* …. */ ou -– commentaire sur 1 ligne. Il est possible d’y intégrer les instructions de manipulation de données dans le code PL/SQL. Cependant, il est possible de contourner le problème en utilisant le SQL dynamique. La notion du curseur permet de lire enregistrement par enregistrement le résultat d’une requête. Ce langage étant très riche, nous ne verrons que les principaux aspects de celui-ci. La notion de bloc PL/SQL (anonyme ou non) DECLARE … (optionnel) BEGIN … EXCEPTION … (optionnel) END; / pour l’activer et me compiler Afficher les erreurs de compilation SHOW ERRORS ou SHO ERR en SQL*Plus Voici, le plus petit bloc PL/SQL anonyme : BEGIN NULL; END; Les types Ils sont identiques aux types d’Oracle plus (liste non exhaustive): - BOOLEAN (opérateur AND OR NOT) - le type d’un champ (prend en compte les éventuelles modifications de type du champ d’où réduction du coût de maintenance) ex : DEBIT CREDIT%TYPE; OU DEBIT USER.CREDIT%TYPE; - le record ex : TYPE MEETINGTYP IS RECORD (DAY DATE, NAME VARCHAR2(20), PURPOSE VARCHAR2(80)); Support de cours Base de données - Le PL/SQL - tous les types d’une table (prend en compte les éventuelles modifications de la table d’où réduction du coût de maintenance) ex : EMP_REC EMP%ROWTYPE; MY_DEPTNO := DEPT_REC.DEPTNO; -- POUR ACCÉDER AU CHAMP DEPTNO Les variables et leur affectation Dans le bloc de déclaration BLOOD_TYPE CHAR := 'O'; VALID BOOLEAN := FALSE; ou BLOOD_TYPE CHAR DEFAULT 'O'; VALID BOOLEAN DEFAULT FALSE; L’affectation BONUS := CURRENT_SALARY * 0.10; VALID := FALSE; la non présence du non null EMP_COUNT NATURAL NOT NULL := 0; L’opérateur de concaténation de chaîne de caractères : || Les constantes credit_limit CONSTANT REAL := 5000.00; Les structures de contrôle Le si IF CHOICE = 1 THEN … END IF; IF CHOICE <> ‘FIN’ THEN -- OU != OU ~= … ELSE … END IF; IF VARIABLE IS NULL THEN ... Une variante du si pour les si imbriqués IF CHOICE = 1 THEN … ELSIF CHOICE = 4 … END IF; La structure algorithmique “selon » est disponible à partir de la version 9i. Les boucles (itératives) LOOP 2 Support de cours Base de données - Le PL/SQL … END LOOP; Quitter une boucle LOOP ... TOTAL := TOTAL + SALARY; EXIT WHEN TOTAL > 25000; -- EXIT LOOP IF CONDITION IS TRUE END LOOP; Le tant que WHILE SALARY < 4000 LOOP SELECT SAL, MGR, ENAME INTO SALARY, MGR_NUM, LAST_NAME FROM EMP WHERE EMPNO = MGR_NUM; END LOOP; La boucle FOR COUNTER IN [REVERSE] MIN..MAX LOOP – MIN <= MAX SEQUENCE_OF_STATEMENTS; END LOOP; avec MIN < MAX ex1 : Affichage de 1 à 5 FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; ex2 : Affichage de 5 à 1 FOR i IN REVERSE 1..5 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; Le branchement inconditionnel ( déconseillé !!!) IF RATING > 90 THEN GOTO BRANCHEMENT; -- BRANCH TO LABEL END IF; <<BRANCHEMENT>> … Les procédures et les fonctions Les procédures Création d’une procédure CREATE PROCEDURE NOM_PROC IS [bloc PL/SQL sans define] ; -- LE IS PEUT ETRE REMPLACE PAR AS Ou CREATE PROCEDURE NOM_PROC IS … [déclaration des variables] BEGIN … END NOM_PROC; Modification d’une procédure : CREATE OR REPLACE PROCEDURE NOM_PROC IS IS [bloc PL/SQL sans define]; Destruction d’une procédure : DROP PROCEDURE NOM_PROC; CREATE PROCEDURE NOM_PROC(EMP_ID NUMBER) IS BONUS REAL; BEGIN 3 Support de cours Base de données - Le PL/SQL 4 ... EXCEPTION ... END NOM_PROC; Passage de paramètres par valeur, référence (en lecture écriture ou uniquement en écriture) : IN (valeur par défaut), OUT, IN OUT CREATE PROCEDURE NOM_PROC(I OUT NUMBER; B IN FLOAT ) IS [bloc PL/SQL sans define] ; -- LE IS PEUT ETRE REMPLACE PAR AS Les valeurs par défaut des paramètres PROCEDURE CREATE_DEPT (P_NAME VARCHAR DEFAULT 'TEMP') IS [bloc PL/SQL sans define] ; L’appel de la procédure NOM(VAL); NOM; CREATE_DEPT; CREATE_DEPT('MARKETING'); CREATE_DEPT('MARKETING', 'NEW YORK'); CREATE_DEPT( , 'NEW YORK'); -- INTERDIT CREATE_DEPT(NEW_LOC => 'NEW YORK'); Sous SQL*Plus EXECUTE CREATE_DEPT('FINANCE', 'NEW YORK'); ou BEGIN CREATE_DEPT('FINANCE', 'NEW YORK'); END; / La notion de procédure externe existe. Celle-ci peut être codé en C par exemple. Les fonctions CREATE FUNCTION NOM_FONCTION (X FLOAT, Y IN OUT FLOAT) RETURN FLOAT IS -- DÉCLARATION BEGIN … RETURN VAL1; EXCEPTION ... END NOM_FONCTION; Modification d’une fonction : CREATE OR REPLACE FUNCTION NOM_PROC IS [bloc PL/SQL sans define]; Destruction d’une fonction: DROP FUNCTION NOM_PROC; L’appel de la fonction VAL := NUM_FONC(…); La récursivité FUNCTION FAC (N POSITIVE) RETURN INTEGER IS -- RETURNS N! BEGIN Support de cours Base de données - Le PL/SQL 5 IF N = 1 THEN -- TERMINATING CONDITION RETURN 1; ELSE RETURN N * FAC(N - 1); -- RECURSIVE CALL END IF; END FAC La surcharge ou l’overloading Il est possible d’utiliser le même nom de fonction (ou de porocédure) si la signature de celle-ci i.e. le nombre de paramètres et leurs types est différente. La portée des procédures et des fonctions Elle reste classique. Il est possible d’encapsuler des fonctions ou des procédures dans des fonctions ou des procédures. Ci dessous, un exemple de fonction (la fonction B) encapsulée et donc utilisable que dans la procédure A. PROCEDURE A (HIREDATE DATE) IS I INTEGER; FUNCTION B (HIREDATE DATE) RETURN INTEGER IS BEGIN ... END; BEGIN I := NEW_HIRES('10-NOV-96'); ... END; Le prototypage des procédures et des fonctions La déclaration des fonctions ou des procédures permet de spécifier le code après l’avoir appelé. Dans ce cas, il convient en premier lieu de donner la signature de la fonction, i.e de prototyper la fonction ou la procédure. Ex: DECLARE PROCEDURE CALC_RATING ( ... ); -- FORWARD DECLARATION ... PROCEDURE AWARD_BONUS ( ... ) IS BEGIN CALC_RATING( ... ); ... END; PROCEDURE CALC_RATING ( ... ) IS BEGIN ... END; Les packages Les packages correspondent à une encapsulation logique d’un ensemble de procédures et de fonctions. Certaines procédures et fonctions du package peuvent être visibles ou non, i.e. utilisables ou non de l’extérieur. Les procédures et fonctions visibles sont prototypées dans la spécifications du package, tandis que celles privés ne seront spécifiées que dans le corps du package. Deux notions existent donc : les spécifications et le corps CREATE PACKAGE NAME AS – SPECIFICATION OU DECLARATION (VISIBLE PART) -- PUBLIC TYPE AND ITEM DECLARATIONS -- SUBPROGRAM SPECIFICATIONS Support de cours Base de données - Le PL/SQL 6 END [NAME]; CREATE PACKAGE BODY NAME AS -- BODY (HIDDEN PART) -- PRIVATE TYPE AND ITEM DECLARATIONS -- SUBPROGRAM BODIES [BEGIN -- INITIALIZATION STATEMENTS] END [NAME]; Ex: CREATE PACKAGE EMP_ACTIONS AS -- PACKAGE SPECIFICATION PROCEDURE HIRE_EMPLOYEE (EMPNO NUMBER, ENAME CHAR, ...); PROCEDURE FIRE_EMPLOYEE (EMP_ID NUMBER); END EMP_ACTIONS; CREATE PACKAGE BODY EMP_ACTIONS AS -- PACKAGE BODY PROCEDURE HIRE_EMPLOYEE (EMPNO NUMBER, ENAME CHAR, ...) IS – OR AS BEGIN INSERT INTO EMP VALUES (EMPNO, ENAME, ...); END HIRE_EMPLOYEE; PROCEDURE FIRE_EMPLOYEE (EMP_ID NUMBER) IS BEGIN DELETE FROM EMP WHERE EMPNO = EMP_ID; END FIRE_EMPLOYEE; END EMP_ACTIONS; Modification d’un package (specification): CREATE OR REPLACE PACKAGE NOM_PKG AS …; Modification d’un package (corps): CREATE OR REPLACE PACKAGE BODY NOM_PKG AS …; Destruction d’un package (specification): DROP PACKAGE NOM_PKG AS …; Destruction d’un package (corps): DROP PACKAGE BODY NOM_PKG AS …; Recompilation: ALTER PACKAGE NOM_PKG COMPILE; L’appel de procédure d’un package Nom_package.nomproc DBMS_OUTPUT.PUT_LINE(‘hello’); Le package inclus d’origine (le package STANDARD) Un package nommé STANDARD comportant les fonctions classiques existe. Par exemple, la fonction ABS qui retourne la valeur absolue. Nb = STANDARD.ABS(x) ... La plupart de ces fonctions sont surchargées comme la fonction TO_CHAR : FUNCTION (RIGHT DATE) RETURN VARCHAR2; FUNCTION TO_CHAR (LEFT NUMBER) RETURN VARCHAR2; FUNCTION TO_CHAR (LEFT DATE, RIGHT VARCHAR2) RETURN VARCHAR2; FUNCTION TO_CHAR (LEFT NUMBER, RIGHT VARCHAR2) RETURN VARCHAR2; La restitution de l’information Une notion de buffer existe et il n’y pas d’affichage au fil de l’eau. Le contenu du buffer s’affiche à la fin de l’exécution seulement si l’autorisation de l’affichage du buffer a été positionné. Activer le par SET SERVEROUTPUT ON ou OFF pour le désactiver. DBMS_OUTPUT.PUT_LINE('hello'); DBMS_OUTPUT.PUT_LINE('hello il est'|| 10 ||’h’); Support de cours Base de données - Le PL/SQL 7 Le buffer possède une taille : SET SERVEROUTPUT ON SIZE n (2000 n 1000000) La récupération des valeurs d’un requête récupérant un seul enregistrement SELECT ID, NOM INTO VAR, VAR2 FROM CLI; Les curseurs Le curseur est obligatoire lorsque la requête est susceptible de retourner plus de 2 enregistrements. Le déplacement dans le curseur va permettre de lire enregistrement par enregistrement du début jusqu’à la fin. Le curseur peut comporter ou non des paramètres. Pour les instructions insert, update, delete, le code SQL peut s’inclure directement dans un bloc PL/SQL. Ex : BEGIN FOR NUM IN 1..500 LOOP SERT INTO CLI VALUES (NUM, ‘DUPOND’); ND LOOP; END ; / Déclaration du curseur Curseur sans paramètre : CURSOR EMP_CUR IS SELECT * FROM EMP WHERE IDREG =1;; Curseur avec parameter(s) : CURSOR EMP_CUR(P_NAME VARCHAR, P_COMM NUMBER DEFAULT 0) IS SELECT * FROM EMP WHERE .NAME = P_NAME ….; Ex: DECLARE CURSOR MY_CURSOR(P_NB INTEGER) IS SELECT * FROM CAT WHERE ROWNUM < P_NB; MY_REC MY_CURSOR%ROWTYPE; BEGIN FOR MY_REC IN MY_CURSOR(3) LOOP MS_OUTPUT.PUT_LINE(MY_REC.TABLE_NAME); ND LOOP; END; / Ouverture du curseur OPEN EMP_CUR('LEE'); OPEN EMP_CUR('BLAKE', 300); OPEN EMP_CUR(EMPLOYEE_NAME, 150); Fermeture du curseur : CLOSE EMP_CUR; Lecture du curseur (déplacement dans le curseur du début jusque la fin) : FETCH EMP_CUR INTO MY_EMPNO, MY_ENAME, MY_DEPTNO; Plusieurs méthodes de scrutations existent. Méthode 1 : sortie de la boucle par l’instruction exit when DECLARE CURSOR C1 IS SELECT ENAME, SAL, HIREDATE, JOB FROM EMP; EMP_REC C1%ROWTYPE; -- DECLARE RECORD VARIABLE THAT REPRESENTS A ROW IN THE EMP TABLE LOOP FETCH C1 INTO MY_RECORD; EXIT WHEN C1%NOTFOUND; -- Quitter la boucle -- PROCESS DATA RECORD END LOOP; Support de cours Base de données - Le PL/SQL Il est possible de récupérer le “type” du curseur via cur% ROWTYPE ex : DECLARE CURSOR MY_CURSOR IS SELECT SAL + NVL(COMM, 0) WAGES, ENAME FROM EMP; MY_REC MY_CURSOR%ROWTYPE; BEGIN OPEN MY_CURSOR; LOOP FETCH MY_CURSOR INTO MY_REC; EXIT WHEN MY_CURSOR%NOTFOUND; IF MY_REC.WAGES > 2000 THEN INSERT INTO TEMP VALUES (NULL, MY_REC.WAGES, MY_REC.ENAME); END IF; END LOOP; CLOSE MY_CURSOR; END; Il y a positionnement d’indicateurs relatifs au curseur après l’instruction FETCH : - deux booléens indiquant si le curseur arrive à récupérer l’enregistrement suivant (cursor%NOTFOUND et %FOUND) - le nombre courant d’enregistrements récupérés (cursor%ROWCOUNT) - le code erreur de l’erreur oracleSQLCODE (=0 si tout est Ok) pour afficher le message d’erreur associé, utiliser le tableau SQLERRM(num_erreur) Il y a déclenchement de l’exception NO_DATA_FOUND (cette notion sera vu plus loin) Méthode 2 : la boucle Il n’y a plus de gestion de l’ouverture, du fecth et de la fermeture du curseur. Le curseur explicite emp_rec est du “type” du curseur BEGIN FOR emp_rec IN c1 LOOP ... salary_total := salary_total + emp_rec.sal; END LOOP; ex : DECLARE CURSOR C1 IS SELECT ID, NOM_CLI FROM CLIENT2; COUR C1%ROWTYPE; BEGIN FOR COUR IN C1 LOOP DBMS_OUTPUT.PUT_LINE(COUR.NOM_CLI||'=>' || C1%ROWCOUNT); END LOOP; END; Le curseur implicite BEGIN FOR EMP_REC IN (SELECT ID NOM FROM CLI) LOOP ... SALARY_TOTAL := SALARY_TOTAL + EMP_REC.SAL; END LOOP; Méthode 3 : la gestion des exceptions La gestion s’effectue par captation des exceptions en particulier l’exception NO_DATA_FOUND. 8 Support de cours Base de données - Le PL/SQL 9 Les triggers basés Les triggers basés sont des sous-programmes associés à une table. Ils se déclenclent automatiquement (sauf désactivation) avant ou après une insertion, modification ou destruction ( via le delete). L’une des utilisations est la tracabilité des modifications. Il convient d’avoir les droits de créer ou modifier les triggers(privilege CREATE TRIGGER). Il est possible de créer plusieurs triggers sur une même table. CREATE TRIGGER AUDIT_SAL BEFORE/AFTER UPDATE/INSERT OR DELETE OF SAL (ON EMP) FOR EACH ROW BEGIN INSERT INTO EMP_AUDIT VALUES ... END; Modification du trigger : CREATE OR REPLACE TRIGGER AUDIT_SAL BEFORE/AFTER UPDATE/INSERT OR DELETE OF SAL (ON EMP) FOR EACH ROW BEGIN INSERT INTO EMP_AUDIT VALUES ... END; Existence des valeurs précédentes et nouvelles : NEW.chp et :OLD.chp CREATE TABLE CLI (I INT, NOM VARCHAR(10)); CREATE SEQUENCE SEQ_CLI_PK; -- CREATION D’UNE SEQUENCE A UTILISER AVEC SEQ. NEXTVAL ET SEQ. CURRVAL CREATE OR REPLACE TRIGGER TR_CLI_PK BEFORE INSERT ON CLI FOR EACH ROW BEGIN SELECT SEQ_CLI_PK.NEXTVAL INTO :NEW.I FROM DUAL; END; Destruction du trigger : DROP TRIGGER TRG_CLI_PK; Celui-ci est détruit si la table est détruite. Il peut être désactivé via Tous les triggers rattachées à une table peuvent être désactivés via ALTER TABLE NOM_TABLE DISABLE ALL TRIGGERS et réactivées en remplaçant DISABLE par ENABLE. La destruction de la table détruit le trigger CREATE TRIGGER TOTAL_SALARY AFTER DELETE OR INSERT OR UPDATE OF DEPTNO, SAL ON EMP FOR EACH ROW BEGIN IF INSERTING OR DELETING OR (UPDATING AND :OLD.DEPTNO != :NEW.DEPTNO) THEN UPDATE DEPT SET TOTAL_SAL = TOTAL_SAL - :OLD.SAL WHERE DEPTNO = :OLD.DEPTNO; END IF; END; Les exceptions La programmation par exceptions permet de mieux diviser la partie du code nécessaire pour traiter les situations exceptionnelles de celle nécessaire à traiter les cas généraux. C'est une technique assez puissante que l'on rencontre dans de nombreux langages de programmation récents. Deux types d’exception existent : Les exceptions prédéfinies (ou internes ou systèmes), par exemple la division par zéro, un curseur vide, ... et les exceptions définies par l’utilisateur qui sont déclarées dans la partie DECLARE du bloc. La définition a les mêmes règles de portée que celles des variables (une définition locale surcharge une définition globale). Support de cours Base de données - Le PL/SQL 10 La gestion des exceptions comprend trois parties : - la déclaration de l'exception : consiste à nommer l'exception. Les exceptions pré-définies sont définies globalement mais le programmeur peut éventuellement les renommer. Les exceptions définies par le programmeur sont dans la partie DECLARE, - le déclenchement de l'exception : les exceptions pré-définies sont déclenchées automatiquement ou explicitement par le programmeur, alors que les exceptions définies par le programmeur sont déclenchées explicitement. Le déclenchement explicite se fait au moyen de l'instruction RAISE nom-exception. - le traitement de l'exception : les exceptions sont traitées dans le gestionnaire d'exceptions (partie EXCEPTION du bloc PL/SQL). A chaque nom d'exception on associe un traitement (séquence d'opérations PL/SQL). On dispose également du mot clé OTHERS qui permet de récupérer les autres exceptions. Lorsqu'une exception est déclenchée dans un bloc, on cherche le traitement associé dans le bloc. S'il n'existe pas on va voir l'exception OTHERS et si elle n'est pas présente on cherche dans les blocs englobants (et récursivement). Le traitement d'une exception ne peut revenir dans le bloc qui l'a déclenchée, on revient toujours au bloc englobant. Si le traitement d'une exception déclenche une autre exception, elle est propagée dans le bloc englobant (et récursivement). Les exceptions prédéfinies : EXCEPTION WHEN ZERO_DIVIDE THEN ROLLBACK; WHEN VALUE_ERROR THEN INSERT INTO ERRORS VALUES ... COMMIT; WHEN OTHERS THEN -- EXCEPTION POUBELLE NULL; END; Liste non exhaustive des exceptions prédéfinies Exception Erreur Oracle Valeur SQLCODE CURSOR_ALREADY_OPEN ORA-06511 -6511 DUP_VAL_ON_INDEX ORA-00001 -1 NO_DATA-FOUND ORA-01403 +100 NOT_LOGGED_ON ORA-01012 -1012 TOO_MANY_ROWS ORA-01422 -1422 VALUE_ERROR ORA-06502 -6502 ZERO_DIVIDE ORA-01476 -1476 Il est possible d’utiliser le code de retour SQLCODE au lieu de “trapper” l’exception par son nom Les exceptions utilisateurs Elles sont définies par l’utilisateur. DECLARE COMM_MISSING EXCEPTION; -- DECLARE EXCEPTION BEGIN ... IF COMMISSION IS NULL THEN RAISE COMM_MISSING; -- RAISE EXCEPTION ELSE BONUS := (SALARY * 0.10) + (COMMISSION * 0.15); END IF; EXCEPTION WHEN COMM_MISSING OR …. THEN -- PROCESS ERROR END; Support de cours Base de données - Le PL/SQL L’auto indendation de la clé primaire CREATE SEQUNCE SEQINCPK; CREATE TRIGGER INCPK BEFORE INSERT OF CLI FOR EACH ROW BEGIN SELECT SEQINCPK.NEXTAL INTO NEW:ID_CLI FROM DUAL; END; L’utilisation d’une fonction PL/SQL dans une requête SQL SELECT FONCT(ID) FROM CLI; Le SQL dynamique Le SQL dynamique consiste à générer le code SQL dans une chaîne de caractères avant de la faire exécuter par le moteur. Il est possible d’inclure une commande de définition de données. L’exécution immédiate Il convient d’utiliser des ‘bind variables’ pour gérer les paramètres EXECUTE IMMEDIATE dynamic_string [INTO {define_variable[, define_variable]... | record}] [USING [IN | OUT | IN OUT] bind_argument [, [IN | OUT | IN OUT] bind_argument]...] [{RETURNING | RETURN} INTO bind_argument[, bind_argument]...]; EXECUTE IMMEDIATE 'CREATE TABLE bonus (id NUMBER, amt NUMBER)'; sql_stmt := 'INSERT INTO dept VALUES (:1, :2, :3)'; EXECUTE IMMEDIATE sql_stmt USING dept_id, dept_name, location; Le curseur dynamiqsue CREATE PROCEDURE DROP_TABLE (TABLE_NAME IN VARCHAR2) AS CID INTEGER; BEGIN CID := DBMS_SQL.OPEN_CURSOR; /* OPEN NEW CURSOR AND RETURN CURSOR ID. */ /* PARSE AND IMMEDIATELY EXECUTE DYNAMIC SQL STATEMENT BUILT BY CONCATENATING TABLE NAME TO DROP TABLE COMMAND. */ DBMS_SQL.PARSE(CID, 'DROP TABLE ' || TABLE_NAME, DBMS_SQL.V7); DBMS_SQL.CLOSE_CURSOR(CID); /* CLOSE CURSOR. */ EXCEPTION /* IF AN EXCEPTION IS RAISED, CLOSE CURSOR BEFORE EXITING. */ WHEN OTHERS THEN DBMS_SQL.CLOSE_CURSOR(CID); END DROP_TABLE; 11