PROGRAMACIÓN EN PL/SQL
INTRODUCCIÓN
Oracle es una base de datos relacional y el lenguaje utilizado para acceder a las bases de datos relacionales es SQL. Muchas aplicaciones de bases de datos se construyen utilizando la tecnología cliente-servidor y Oracle nos permite utilizar esta tecnología.
Para trabajar con este temario nos basaremos en las siguientes tablas:
Tabla Students:
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- -------------- ID NOT NULL NUMBER(5) FIRST_NAME VARCHAR2(20) LAST_NAME VARCHAR2(20) MAJOR VARCHAR2(30) CURRENT_CREDITS NUMBER(3) |
También tendremos la tabla Major_Stats:
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- ---------------------------- MAJOR VARCHAR2(30) TOTAL_CREDITS NUMBER TOTAL_STUDENTS NUMBER |
Otra tabla que tendremos es la tabla Rooms:
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- ---------------------------- ID NUMBER(5) BUILDING VARCHAR2(15) ROOM_NUMBER NUMBER(4) NUMBER_SEATS NUMBER(4) DESCRIPTION VARCHAR2(50) |
También tendremos la tabla Classes:
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- ---------------------------- DEPARTMENT CHAR(3) COURSE NUMBER(3) DESCRIPTION VARCHAR2(2000) MAX_STUDENTS NUMBER(3) CURRENT_STUDENTS NUMBER(3) NUM_CREDITS NUMBER(1) ROOM_ID NUMBER(5) |
Tenemos la tabla Registered_students
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- ------------ STUDENT_ID NOT NULL NUMBER(5) DEPARTMENT NOT NULL CHAR(3) COURSE NOT NULL NUMBER(3) GRADE CHAR(1) |
Tendremos también la tabla RS_Audit, que almacenará los cambios registrados en registered_students.
| Nombre ¿Nulo? Tipo ----------------------------------------- -------- ---------------- CHANGE_TYPE NOT NULL CHAR(1) CHANGED_BY NOT NULL VARCHAR2(8) TIMESTAMP NOT NULL DATE OLD_STUDENT_ID NUMBER(5) OLD_DEPARTMENT CHAR(3) OLD_COURSE NUMBER(3) OLD_GRADE CHAR(1) NEW_STUDENT_ID NUMBER(5) NEW_DEPARTMENT CHAR(3) NEW_COURSE NUMBER(3) NEW_GRADE CHAR(1) |
Tenemos tres tablas más: log_table (que registrará los errores de Oracle), temp_table (que almacenará datos temporales) y debug_table (que almacenará la información de depuración).
IDENTIFICADORES
Los identificadores se utilizan para dar nombre a los objetos PL/SQL, tales como variables, cursores, tipos y subprogramas. Constan de una letra seguida por una secuencia opcional de caracteres. Pero si queremos usar identificadores usando espacios, diferenciando mayúsculas y minúsculas, etc. tendremos que poner estos identificadores entre comillas.
DECLARACIÓN DE VARIABLES
La comunicación en la base de datos tiene lugar mediante el uso de variables incluidas en los bloques de PL/SQL. La declaración de variables tiene lugar en la sección declarativa del bloque.
ESTRUCTURA DE CONTROL PL/SQL
Los bloques de control permiten que se realicen diferentes porciones de código según se cumpla una u otra condición.
| DECLARE v_NumberSeats rooms.number_seats%TYPE; v_Comment VARCHAR2(35); BEGIN /* Retrieve the number of seats in the room identified by ID 99999. Store the result in v_NumberSeats. */ SELECT number_seats INTO v_NumberSeats FROM rooms WHERE room_id = 99999; IF v_NumberSeats < 50 THEN v_Comment := 'Fairly small'; ELSIF v_NumberSeats < 100 THEN v_Comment := 'A little bigger'; ELSE v_Comment := 'Lots of room'; END IF; END; |
BUCLES.
Como habíamos visto en la introducción tenemos varios tipos de bucles, vamos a verlos con unos ejemplos:
| DECLARE v_Counter BINARY_INTEGER := 1; BEGIN LOOP -- Insert a row into temp_table with the current value of the -- loop counter. INSERT INTO temp_table VALUES (v_Counter, 'Loop index'); v_Counter := v_Counter + 1; -- Exit condition - when the loop counter > 50 we will -- break out of the loop. IF v_Counter > 50 THEN EXIT; END IF; END LOOP; END; / DECLARE
DECLARE
DECLARE
DECLARE |
ÓRDENES GOTO Y ETIQUETAS
Las órdenes GOTO envían el proceso del código que se está ejecutando a una etiqueta. Veamos un ejemplo de orden GOTO.
| DECLARE v_Counter BINARY_INTEGER := 1; BEGIN LOOP INSERT INTO temp_table VALUES (v_Counter, 'Loop count'); v_Counter := v_Counter + 1; IF v_Counter > 50 THEN GOTO l_EndOfLoop; END IF; END LOOP; <<l_EndOfLoop>> INSERT INTO temp_table (char_col) VALUES ('Done!'); END; / |
REGISTROS Y TABLAS
Los registros de PL/SQL son similares a las estructuras de lenguaje C. Un registro proporciona un mecanismo para tratar con variables diferentes pero relacionadas como si fueran una unidad.
Por ejemplo:
| DECLARE
v_studentID NUMBER(5); v_firstName VARCHAR2(20); v_LastName VARCHAR2(20); |
Estas tres variable tienen una relación lógica entre sí, las tres hacen referencia a campos de la tabla STUDENTS. Lo que podemos hacer es declarar un tipo con estas tres variables para que actúen de forma conjunta.
| TYPE t_StudentRecord IS RECORD (
StudentId NUMBER(5), FirstName VARCHAR2(20), LastName VARCHAR2(20)); v_studentInfo t_studentRecord; |
Para poder asignar un registro a otro ambos registros deben ser del mismo tipo. La asignación de los registros se haría como indicamos en el código siguiente:
| DECLARE TYPE t_Rec1Type IS RECORD ( Field1 NUMBER, Field2 VARCHAR2(5)); TYPE t_Rec2Type IS RECORD ( Field1 NUMBER, Field2 VARCHAR2(5)); v_Rec1 t_Rec1Type; v_Rec2 t_Rec2Type; BEGIN /* Even though v_Rec1 and v_Rec2 have the same field names and field types, the record types themselves are different. This is an illegal assignment which raises PLS-382. */ v_Rec1 := v_Rec2; /* However, the fields are the same type, so the following are legal assignments. */ v_Rec1.Field1 := v_Rec2.Field1; v_Rec2.Field2 := v_Rec2.Field2; END; / |
Aunque también podemos asignar valores mediante una orden SELECT:
DECLARE -- Define a record to match some fields in the students table. -- Note the use of %TYPE for the fields. TYPE t_StudentRecord IS RECORD ( FirstName students.first_name%TYPE, LastName students.last_name%TYPE, Major students.major%TYPE); -- Declare a variable to receive the data. v_Student t_StudentRecord; BEGIN -- Retrieve information about student with ID 10,000. -- Note how the query is returning columns which match the -- fields in v_Student. SELECT first_name, last_name, major INTO v_Student FROM students WHERE ID = 10000; END; / |
TABLAS
Las tablas se asemejan a lo que se denominan matrices, se tratan de la misma manera aunque tienen una implementación distinta. Para poder declarar una tabla tenemos que definir primero su tipo y posteriormente una variable del mismo tipo. Una vez definido el tipo y la variable tendremos que referenciarla con la sintaxis:
nombreTabla(índice)
Estas tablas tienen una serie de atributos que son:
| ATRIBUTO | TIPO DEVUELTO | DESCRIPCIÓN |
| COUNT | NUMBER | Devuelve el nº de filas de una tabla |
| DELETE | Borra las filas de una tabla | |
| EXISTS | BOOLEAN | Devuelve TRUE si existe en la tabla el elemento especificado |
| FIRST | BINARY_INTEGER | Devuelve el índice de la primera fila de la tabla |
| LAST | BINARY_INTEGER | |
| NEXT | BINARY_INTEGER | |
| PRIOR | BINARY_INTEGER |
SEUDOCOLUMNAS
Las seudocolumnas son funciones adicionales que sólo se pueden llamar desde una orden SQL. Sintácticamente se las trata como una columna de una tabla. Las seudocolumnas se emplean con las secuencias, que son un objeto que se emplea para crear series de números diferentes.
CURSORES
¿Qué es un cursor?
Para poder procesar una orden SQL oracle le asigna una área de memoria que recibe el nombre de área de contexto. Esta área contiene informaciones necesarias para completar el proceso, incluyendo el número de filas procesadas por la orden, el conjunto activo de filas,...
Un cursor es un puntero en el área de contexto que permite controlar lo que sucede en ese área, .... Veamos un ejemplo que lo que va a hacer es una extracción mediante cursor en que una consulta devuelve múltiples filas de datos.
DECLARE /* Output variables to hold the results of the query */ v_StudentID students.id%TYPE; v_FirstName students.first_name%TYPE; v_LastName students.last_name%TYPE; /* Bind variable used in the query */ v_Major students.major%TYPE := 'Computer Science'; /* Cursor declaration */ CURSOR c_Students IS SELECT id, first_name, last_name FROM students WHERE major = v_Major; BEGIN /* Identify the rows in the active set, and prepare for further processing of the data */ OPEN c_Students; LOOP /* Retrieve each row of the active set into PL/SQL variables */ FETCH c_Students INTO v_StudentID, v_FirstName, v_LastName; /* If there are no more rows to fetch, exit the loop */ EXIT WHEN c_Students%NOTFOUND; END LOOP; /* Free resources used by the query */ CLOSE c_Students; END; / |
Los cursores pueden ser de dos tipos:
* Implícitos: no los declaramos nosotros, se declaran automáticamente cuando ocurre una transacción.
* Explícitos: como el ejemplo anterior, lo declaramos nosotros y podemos decir que su proceso consta de cuatro pasos:
1. Declaración del cursor:
| CURSOR c_Students IS SELECT id, first_name, last_name FROM students WHERE major = v_Major; |
2. Apertura del cursor para una consulta:
| OPEN c_Students; LOOP |
3. Extracción de los datos de un cursor:
| FETCH c_Students INTO v_StudentID, v_FirstName, v_LastName; |
4. Cierre del cursor:
| CLOSE c_Students; |
ATRIBUTOS DE LOS CURSORES
Existen cuatro atributos de los cursores que podemos aplicar, se añaden dentro de un bloque PL/SQL al nombre del cursor, y estos atributos son:
PROCESAMIENTO DE CURSORES IMPLÍCITOS
Los cursores implícitos sirven para procesar órdenes SELECT que devuelven más de una fila. El cursor implícito sirve para procesar las órdenes INSERT, UPDATE y DELETE así como las de SELECT...INTO de una sola fila. Las órdenes OPEN, FETCH y CLOSE no son relevantes para este tipo de cursores, aunque si que permiten que se les asigne los atributos de cursor mencionados arriba. Veamos un bloque:
BEGIN UPDATE rooms SET number_seats = 100 WHERE room_id = 99980; -- If the previous UPDATE statement didn't match any rows, -- insert a new row into the rooms table. IF SQL%NOTFOUND THEN INSERT INTO rooms (room_id, number_seats) VALUES (99980, 100); END IF; END; / |
BUCLES DE EXTRACCIÓN MEDIANTE CURSOR
La operación más común que se puede realizar con cursores consiste en extraer todas las filas del conjunto activo, y para hacerlo usaremos un bucle de extracción. Tenemos diferentes tipos de bucle:
1. Bucles simples: el bucle más simple con el que podemos trabajar es el LOOP ... END LOOP. Su sintaxis en un ejemplo sería:
DECLARE -- Declare variables to hold information about the students -- majoring in History. v_StudentID students.id%TYPE; v_FirstName students.first_name%TYPE; v_LastName students.last_name%TYPE; -- Cursor to retrieve the information about History students CURSOR c_HistoryStudents IS SELECT id, first_name, last_name FROM students WHERE major = 'History'; BEGIN -- Open the cursor and initialize the active set OPEN c_HistoryStudents; LOOP -- Retrieve information for the next student FETCH c_HistoryStudents INTO v_StudentID, v_FirstName, v_LastName; -- Exit loop when there are no more rows to fetch EXIT WHEN c_HistoryStudents%NOTFOUND; -- Process the fetched rows. In this case sign up each -- student for History 301 by inserting them into the -- registered_students table. Record the first and last -- names in temp_table as well. INSERT INTO registered_students (student_id, department, course) VALUES (v_StudentID, 'HIS', 301); INSERT INTO temp_table (num_col, char_col) VALUES (v_StudentID, v_FirstName || ' ' || v_LastName); END LOOP; -- Free resources used by the cursor CLOSE c_HistoryStudents; -- Commit our work COMMIT; END; / |
2. Bucles WHILE.
Podemos utilizar un bucle WHILE, el código quedaría como sigue:
DECLARE -- Cursor to retrieve the information about History students CURSOR c_HistoryStudents IS SELECT id, first_name, last_name FROM students WHERE major = 'History'; -- Declare a record to hold the fetched information. v_StudentData c_HistoryStudents%ROWTYPE; BEGIN -- Open the cursor and initialize the active set OPEN c_HistoryStudents; -- Retrieve the first row, to set up for the WHILE loop FETCH c_HistoryStudents INTO v_StudentData; -- Continue looping while there are more rows to fetch WHILE c_HistoryStudents%FOUND LOOP -- Process the fetched rows, in this case sign up each -- student for History 301 by inserting them into the -- registered_students table. Record the first and last -- names in temp_table as well. INSERT INTO registered_students (student_id, department, course) VALUES (v_StudentData.ID, 'HIS', 301); INSERT INTO temp_table (num_col, char_col) VALUES (v_StudentData.ID, v_StudentData.first_name || ' ' || v_StudentData.last_name); -- Retrieve the next row. The %FOUND condition will be checked -- before the loop continues again. FETCH c_HistoryStudents INTO v_StudentData; END LOOP; -- Free resources used by the cursor CLOSE c_HistoryStudents; -- Commit our work COMMIT; END; / |
3. Bucles de cursor FOR
Los bucles FOR se caracterizan porque hacen un procesamiento implícito del cursor. El código sería:
| DECLARE -- Cursor to retrieve the information about History students CURSOR c_HistoryStudents IS SELECT id, first_name, last_name FROM students WHERE major = 'History'; BEGIN -- Begin the loop. An implicit OPEN of c_HistoryStudents -- is done here. FOR v_StudentData IN c_HistoryStudents LOOP -- An implicit FETCH is done here. -- Process the fetched rows, in this case sign up each -- student for History 301 by inserting them into the -- registered_students table. Record the first and last -- names in temp_table as well. INSERT INTO registered_students (student_id, department, course) VALUES (v_StudentData.ID, 'HIS', 301); INSERT INTO temp_table (num_col, char_col) VALUES (v_StudentData.ID, v_StudentData.first_name || ' ' || v_StudentData.last_name); -- Before the loop will continue, an implicit check of -- c_HistoryStudents%NOTFOUND is done here. END LOOP; -- Now that the loop is finished, an implicit CLOSE of -- c_HistoryStudents is done. -- Commit our work. COMMIT; END; / |
SUBPROGRAMAS, PROCEDIMIENTOS Y FUNCIONES
Hasta ahora todos los bloques que hemos visto eran bloques anónimos, pero puede suceder que tengamos que generar bloques de código que tendremos que almacenar y utilizar posteriormente. Así, por ejemplo, podemos tener el siguiente bloque de código que creará un nuevo registro en la tabla de estudiantes:
CREATE OR REPLACE PROCEDURE AddNewStudent ( p_FirstName students.first_name%TYPE, p_LastName students.last_name%TYPE, p_Major students.major%TYPE) AS BEGIN -- Insert a new row in the students table. Use -- student_sequence to generate the new student ID, and -- 0 for current_credits. INSERT INTO students (ID, first_name, last_name, major, current_credits) VALUES (student_sequence.nextval, p_FirstName, p_LastName, p_Major, 0); COMMIT; END AddNewStudent; / |
Una vez cerrado el procedimiento podemos invocarlo desde otro procedimiento mediante el código:
| BEGIN
AddNewStudent('Pepe','Lopez','Música'); END; |
La sintaxis para la creación de un procedimiento sería como sigue:
| CREATE OR REPLACE PROCEDURE nombre_proc
argumentos AS procedimiento |
Los parámetros formales pueden tener tres modos: IN, OUT o IN OUT. Si no se especifica su modo cogerá por defecto el modo IN. Veamos estos modos:
Veamos un ejemplo:
| CREATE OR REPLACE PROCEDURE ModeTest ( p_InParameter IN NUMBER, p_OutParameter OUT NUMBER, p_InOutParameter IN OUT NUMBER) IS v_LocalVariable NUMBER; BEGIN /* Assign p_InParameter to v_LocalVariable. This is legal, since we are reading from an IN parameter and not writing to it. */ v_LocalVariable := p_InParameter; -- Legal --------> parte derecha de la asignación /* Assign 7 to p_InParameter. This is ILLEGAL, since we are writing to an IN parameter. */ p_InParameter := 7; -- Illegal /* Assign 7 to p_OutParameter. This is legal, since we are writing to an OUT parameter and not reading from it. */ p_OutParameter := 7; -- Legal ---------------------------->parte izquierda de la asignación /* Assign p_OutParameter to v_LocalVariable. This is ILLEGAL, since we are reading from an OUT parameter. */ v_LocalVariable := p_outParameter; -- Illegal /* Assign p_InOutParameter to v_LocalVariable. This is legal, since we are reading from an IN OUT parameter. */ v_LocalVariable := p_InOutParameter; -- Legal /* Assign 7 to p_InOutParameter. This is legal, since we are writing to an IN OUT parameter. */ p_InOutParameter := 7; -- Legal END ModeTest; / |
EL CUERPO DEL PROCEDIMIENTO
El cuerpo de un procedimiento es un bloque PL/SQL con sus secciones declarativa, ejecutable y de manejo de excepciones. Cuando trabajamos con variables, tenemos que tener en cuenta:
* Parámetros reales: DECLARE v_variable
* Parámetros formales: van dentro del cuerpo del procedimiento y no tengo que usar el DECLARE.
Los parámetros formales de un procedimiento o función pueden tener valores predeterminados. Si tiene un valor predeterminado, no tiene por que ser pasado desde el entorno que realiza la llamada.
Por ejemplo, para asignar como valor predeterminado la especialidad de economía a todos los nuevos estudiantes a menos que se le indique lo contrario, tendremos que usar la notación:
| CREATE OR REPLACE PROCEDURE AddNewStudent ( p_FirstName students.first_name%TYPE, p_LastName students.last_name%TYPE, p_Major students.major%TYPE DEFAULT 'Economics') AS BEGIN -- Insert a new row in the students table. Use -- student_sequence to generate the new student ID, and -- 0 for current_credits. INSERT INTO students VALUES (student_sequence.nextval, p_FirstName, p_LastName, p_Major, 0); COMMIT; END AddNewStudent; / |
El parámetro predeterminado se empleará siempre que el parámetro formal p_major no tenga un parámetro real asociado en la llamada al procedimiento.
| BEGIN
AddNewStudent('Pepe', 'Blues'); END; |
Al emplear esta notación todos los parámetros asociados que utilicen un default tendrán que pasarse al final del procedimiento.
En cambio, podemos usar también la notación nominal para pasar los datos:
| BEGIN
AddNewStudent(p_firstName =>'Pepe', p_lastName='Blues'); END; |
CREACIÓN DE FUNCIONES.
Una función es bastante similar a un procedimiento, ambos aceptan argumentos, ambos se pueden almacenar dentro de las bases de datos o ser declarados dentro de un bloque. Una llamada a una función es un RVALOR. Por ejemplo, la función que pasaremos a continuación devuelve el valor TRUE si la clase especificada tiene una ocupación mayor que 90% y false en caso contrario:
| CREATE OR REPLACE FUNCTION AlmostFull ( p_Department classes.department%TYPE, p_Course classes.course%TYPE) RETURN BOOLEAN IS v_CurrentStudents NUMBER; v_MaxStudents NUMBER; v_ReturnValue BOOLEAN; v_FullPercent CONSTANT NUMBER := 90; BEGIN -- Get the current and maximum students for the requested -- course. SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = p_Department AND course = p_Course; -- If the class is more full than the percentage given by -- v_FullPercent, return TRUE. Otherwise, return FALSE. IF (v_CurrentStudents / v_MaxStudents * 100) > v_FullPercent THEN v_ReturnValue := TRUE; ELSE v_ReturnValue := FALSE; END IF; RETURN v_ReturnValue; END AlmostFull; / |
Podemos llamar a la función AlmostFull desde el siguiente bloque PL/SQL, en la que veremos que se usa como parte de la orden IF situada dentro del bucle:
DECLARE CURSOR c_Classes IS SELECT department, course FROM classes; BEGIN FOR v_ClassRecord IN c_Classes LOOP -- Record all classes which don't have very much room left -- in temp_table. IF AlmostFull(v_ClassRecord.department, v_ClassRecord.course) THEN INSERT INTO temp_table (char_col) VALUES (v_ClassRecord.department || ' ' || v_ClassRecord.course || ' is almost full!'); END IF; END LOOP; END; / |
Dentro del cuerpo de la función la orden RETURN se emplea para devolver el control y un valor al entorno que realizó la llamada. Puede haber más de una orden RETURN aunque sólo se ejecutará una de las mismas.
Así podríamos tener un código como el siguiente:
| CREATE OR REPLACE FUNCTION ClassInfo ( /* Returns 'Full' if the class is completely full, 'Some Room' if the class is over 80% full, 'More Room' if the class is over 60% full, 'Lots of Room' if the class is less than 60% full, and 'Empty' if there are no students registered. */ p_Department classes.department%TYPE, p_Course classes.course%TYPE) RETURN VARCHAR2 IS v_CurrentStudents NUMBER; v_MaxStudents NUMBER; v_PercentFull NUMBER; BEGIN -- Get the current and maximum students for the requested -- course. SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = p_Department AND course = p_Course; -- Calculate the current percentage. v_PercentFull := v_CurrentStudents / v_MaxStudents * 100; IF v_PercentFull = 100 THEN RETURN 'Full'; ELSIF v_PercentFull > 80 THEN RETURN 'Some Room'; ELSIF v_PercentFull > 60 THEN RETURN 'More Room'; ELSIF v_PercentFull > 0 THEN RETURN 'Lots of Room'; ELSE RETURN 'Empty'; END IF; END ClassInfo; / |
Las funciones comparten muchas de las características de los procedimientos. Así tendremos entre otras las siguientes características:
EXCEPCIONES GENERADAS DENTRO DE UN SUBPROGRAMA
Si ocurre un error dentro de un subprograma se genera una excepción, que puede ser predefinida o definida por el usuario. Si el procedimiento no dispone de ninguna tarea para el manejo de este error, se devuelve el control al entorno que realizó la llamada la procedimiento. Aunque, nosotros podemos gestionar estos errores para tratar dichos errores.
| CREATE OR REPLACE PROCEDURE RaiseError ( /* Illustrates the behavior of unhandled exceptions and OUT variables. If p_Raise is TRUE, then an unhandled error is raised. If p_Raise is FALSE, the procedure completes successfully. */ p_Raise IN BOOLEAN := TRUE, p_ParameterA OUT NUMBER) AS BEGIN p_ParameterA := 7; IF p_Raise THEN /* Even though we have assigned 7 to p_ParameterA, this unhandled exception causes control to return immediately without returning 7 to the actual parameter associated with p_ParameterA. */ RAISE DUP_VAL_ON_INDEX; ELSE /* Simply return with no error. This will return 7 to the actual parameter. */ RETURN; END IF; END RaiseError; / PROMPT Calling RaiseError... DECLARE v_TempVar NUMBER := 1; BEGIN INSERT INTO temp_table (num_col, char_col) VALUES (v_TempVar, 'Initial value'); RaiseError(FALSE, v_TempVar); INSERT INTO temp_table (num_col, char_col) VALUES (v_TempVar, 'Value after successful call'); v_TempVar := 2; INSERT INTO temp_table (num_col, char_col) VALUES (v_TempVar, 'Value before 2nd call'); RaiseError(TRUE, v_TempVar); EXCEPTION WHEN OTHERS THEN INSERT INTO temp_table (num_col, char_col) VALUES (v_TempVar, 'Value after unsuccessful call'); END; / SELECT * FROM temp_table; |
PAQUETES
Los paquetes son el tercer tipo de bloques PL/SQL nominados después de los procedimientos y de las funciones. Resultan muy útiles porque permiten un mecanismo para la extensión del lenguaje.
Un paquete es en esencia una sección declarativa nominada. La inclusión de objetos en paquetes proporciona la ventaja de que permite referenciarlos desde otros bloques PL/SQL. Los paquetes están formados por:
Así tendremos:
| CREATE OR REPLACE PACKAGE ClassPackage AS -- Add a new student into the specified class. PROCEDURE AddStudent(p_StudentID IN students.id%TYPE, p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE); -- Removes the specified student from the specified class. PROCEDURE RemoveStudent(p_StudentID IN students.id%TYPE, p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE); -- Exception raised by RemoveStudent. e_StudentNotRegistered EXCEPTION; -- Table type used to hold student info. TYPE t_StudentIDTable IS TABLE OF students.id%TYPE INDEX BY BINARY_INTEGER; -- Returns a PL/SQL table containing the students currently -- in the specified class. PROCEDURE ClassList(p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE, p_IDs OUT t_StudentIDTable, p_NumStudents IN OUT BINARY_INTEGER); END ClassPackage; / CREATE OR REPLACE PACKAGE BODY ClassPackage AS -- Add a new student for the specified class. PROCEDURE AddStudent(p_StudentID IN students.id%TYPE, p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE) IS BEGIN INSERT INTO registered_students (student_id, department, course) VALUES (p_StudentID, p_Department, p_Course); COMMIT; END AddStudent; -- Removes the specified student from the specified class. PROCEDURE RemoveStudent(p_StudentID IN students.id%TYPE, p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE) IS BEGIN DELETE FROM registered_students WHERE student_id = p_StudentID AND department = p_Department AND course = p_Course; -- Check to see if the DELETE operation was successful. If -- it didn't match any rows, raise an error. IF SQL%NOTFOUND THEN RAISE e_StudentNotRegistered; END IF; COMMIT; END RemoveStudent; -- Returns a PL/SQL table containing the students currently -- in the specified class. PROCEDURE ClassList(p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE, p_IDs OUT t_StudentIDTable, p_NumStudents IN OUT BINARY_INTEGER) IS v_StudentID registered_students.student_id%TYPE; -- Local cursor to fetch the registered students. CURSOR c_RegisteredStudents IS SELECT student_id FROM registered_students WHERE department = p_Department AND course = p_Course; BEGIN /* p_NumStudents will be the table index. It will start at 0, and be incremented each time through the fetch loop. At the end of the loop, it will have the number of rows fetched, and therefore the number of rows returned in p_IDs. */ p_NumStudents := 0; OPEN c_RegisteredStudents; LOOP FETCH c_RegisteredStudents INTO v_StudentID; EXIT WHEN c_RegisteredStudents%NOTFOUND; p_NumStudents := p_NumStudents + 1; p_IDs(p_NumStudents) := v_StudentID; END LOOP; END ClassList; END ClassPackage; / |
Cualquier objeto que hayamos declarado en la cabecera del paquete diremos que está dentro del ámbito y que es visible fuera del paquete. Podemos efectuar una llamada a cualquier procedimiento incluido en el paquete simplemente indicando nombrePaquete.Objeto
Dentro del cuerpo del paquete podemos hacer referencia a otros objetos generados en el paquete.
DISPARADORES
Es el cuarto bloque PL/SQL nominado, comparten muchas de las características de los subprogramas, pero tienen diferencias significativas, como puede ser el hecho de que se disparen automáticamente cuando se genere una sentencia DML.
Los disparadores los podemos utilizar para, entre otras cosas:
Por ejemplo si queremos mantener una serie de estadísticas referentes a las diferentes especialidades en que se matriculan los alumnos, usaremos estos datos almacenándolos en la tabla de major_stats, y para actualizar las estadísticas de esta tabla podríamos generar un trigger que lo que haría sería modificar esta tabla cada vez que se modifique la tabla de students, el trigger quedaría de la siguiente manera:
| CREATE OR REPLACE TRIGGER UpdateMajorStats /* Keeps the major_stats table up-to-date with changes made to the students table. */ AFTER INSERT OR DELETE OR UPDATE ON students DECLARE CURSOR c_Statistics IS SELECT major, COUNT(*) total_students, SUM(current_credits) total_credits FROM students GROUP BY major; BEGIN /* Loop through each major. Attempt to update the statistics in major_stats corresponding to this major. If the row doesn't exist, create it. */ FOR v_StatsRecord in c_Statistics LOOP UPDATE major_stats SET total_credits = v_StatsRecord.total_credits, total_students = v_StatsRecord.total_students WHERE major = v_StatsRecord.major; /* Check to see if the row exists. */ IF SQL%NOTFOUND THEN INSERT INTO major_stats (major, total_credits, total_students) VALUES (v_StatsRecord.major, v_StatsRecord.total_credits, v_StatsRecord.total_students); END IF; END LOOP; END UpdateMajorStats; / |
Este trigger se divide en:
1. Creación del trigger:
| CREATE OR REPLACE TRIGGER UpdateMajorStats AFTER INSERT OR DELETE OR UPDATE ON students |
2. Declaración de las variables que formarán parte del trigger, estas variables almacenarán los datos antes de pasarlos a la tabla definitiva que sería la de major_stats, en este caso los datos se almacenarán en un cursor:
| DECLARE CURSOR c_Statistics IS SELECT major, COUNT(*) total_students, SUM(current_credits) total_credits FROM students GROUP BY major; |
3. Iniciaremos el proceso de lo que va a tener que realizar este trigger:
| BEGIN /* Hacemos un bucle para cada major, intentaremos updatar las estadísticas de la tabla major_stats con el registro correspondiente (v_statsRecordMajor), y si no existe insertaremos la fila.*/ FOR v_StatsRecord in c_Statistics LOOP UPDATE major_stats SET total_credits = v_StatsRecord.total_credits, total_students = v_StatsRecord.total_students WHERE major = v_StatsRecord.major; /* Check to see if the row exists. */ IF SQL%NOTFOUND THEN INSERT INTO major_stats (major, total_credits, total_students) VALUES (v_StatsRecord.major, v_StatsRecord.total_credits, v_StatsRecord.total_students); END IF; END LOOP; END UpdateMajorStats; |
COMPONENTES DE UN TRIGGER
Los componentes requeridos en un disparador son:
1. Nombre del disparador: es el conjunto de identificadores válidos que pueden emplearse como nombres de un objeto. Los disparadores existen en un espacio de nombre diferente con lo cual pueden tener el mismo nombre que una tabla, un procedimiento, aunque no puede existir dos triggers con el mismo nombre dentro de un mismo esquema.
2. Tipos de disparadores: implica cuando se va a ejecutar este disparador, podemos tener doce tipos posibles de trigger: 3 órdenes (insert, delete o update), dos opciones de temporización (before o update) y 2 niveles (a nivel de fila -se caracterizan porque llevan la cláusula FOR EACH ROW- o a nivel de orden).
3. Cuerpo del disparador: es un bloque PL/SQL, Pero tiene una serie de restricciones, ya que no podemos emitir ninguna orden de control de transacciones (commit, rollback o savepoint).
UTILIZACIÓN DE :OLD Y :NEW EN LOS DISPARADORES CON NIVEL DE FILA
Un disparador con nivel de fila se ejecuta una vez por cada fila procesada por la orden que provoca el disparo. Dentro del disparador podemos acceder a la fila que se está procesando actualmente mediante el uso de dos seudo-registros que son :old y :new.
La utilización del seudo-registro :new lo que hace es llamar al registro modificado para poder actual con él, mientras que el :old lo que hace es coger los valores antiguos del registro.
En los disparadores con nivel de fila podemos utilizar la cláusula WHEN, que ejecutará las órdenes correspondientes sólo en el caso en que se cumpla una condición. Por ejemplo:
| CREATE OR REPLACE TRIGGER CheckCredits
BEFORE INSERT OR UPDATE OF Current_credits ON students FOR EACH ROW WHEN (new.current_credits>20) BEGIN ...... END; |
TABLAS MUTANTES
Hay ciertas restricciones sobre las tablas y columnas a las que puede acceder el cuerpo de un disparador. Para poder definir estas restricciones es necesario entender los conceptos de tablas mutantes y de tabla de restricción.
Una tabla mutante es una tabla que está modificándose actualmente por una sentencia DML. Para un disparador es la tabla a la que éste está asignado. Una tabla de restricción es una tabla de la que puede ser necesario leer para una restricción de integridad referencial. La tabla registered_students, que habíamos creado previamente, tenía dos restricciones de integridad referencial, si creamos un trigger sobre esta tabla tenemos que tener en cuenta que las órdenes SQL en el cuerpo del disparador no pueden leer o modificar ninguna tabla mutante de la orden que provoca el disparo, ni leer ni modificar las columnas de clave primaria, única o externa de una tabla de restricción de la tabla del disparador.
Podríamos modificar aquellas columnas que no formaran parte de claves primarias o externas, como en el ejemplo siguiente:
CREATE OR REPLACE TRIGGER CascadeRSInserts /* Keep the registered_students, students, and classes tables in synch. */ BEFORE INSERT ON registered_students FOR EACH ROW DECLARE v_Credits classes.num_credits%TYPE; BEGIN -- Determine the number of credits for this class. SELECT num_credits INTO v_Credits FROM classes WHERE department = :new.department AND course = :new.course; -- Modify the current credits for this student. UPDATE students SET current_credits = current_credits + v_Credits WHERE ID = :new.student_id; -- Add one to the number of students in the class. UPDATE classes SET current_students = current_students + 1 WHERE department = :new.department AND course = :new.course; END CascadeRSInserts; / |
Veamos ahora un ejemplo útil de trigger de tabla mutante, supongamos que queremos como máximo 5 alumnos por clase:
CREATE OR REPLACE TRIGGER LimitMajors /* Limits the number of students in each major to 5. If this limit is exceeded, an error is raised through raise_application_error. */ BEFORE INSERT OR UPDATE OF major ON students FOR EACH ROW DECLARE v_MaxStudents CONSTANT NUMBER := 5; v_CurrentStudents NUMBER; BEGIN -- Determine the current number of students in this -- major. SELECT COUNT(*) INTO v_CurrentStudents FROM students WHERE major = :new.major; -- If there isn't room, raise an error. IF v_CurrentStudents + 1 > v_MaxStudents THEN RAISE_APPLICATION_ERROR(-20000, 'Too many students in major ' || :new.major); END IF; END LimitMajors; / |
TRATAMIENTO DE ERRORES
Cualquier programa bien escrito debe ser capaz de tratar los errores de manera inteligente y de recuperarse de ellos. Los errores en PL/SQL los manejaremos a través de lo que se denominan excepciones que nos permitirán reconstruir el programa en el caso en que se produzca un error. Los errores los podemos clasificar en:
Errores de compilación: los detecta PL/SQL cuando iniciamos la compilación del código.
De ejecución: se detecta cuando estemos ejecutando el código, lo solucionaremos a través del manejo de excepciones.
Las excepciones se declaran en la sección declarativa de un bloque, se generan en la sección ejecutable y se tratan en la sección de excepciones. Existen dos tipos de excepciones:
Definidas por el usuario:
Es un error cuya definición se realiza en el programa; el error no tiene porque ser necesariamente un error de Oracle, podría ser un error relativo a los datos.
Se declaran en la sección declarativa del bloque PL/SQL y se les tiene que especificar el tipo:
| DECLARE
e_DemasiadosEstudiantes EXCEPTION; |
Excepciones predefinidad
Tenemos diferentes excepciones provocadas por errores de Oracle, estas excepciones pueden ser:
| ORA-0001 | DUP_VAL_ON_INDEX | Violación de una restricción de unicidad |
| ORA-0051 | TIMEOUT_ON_RESOURCE | Se produjo el fin de un intervalo mientras se esperaba un cierto recurso |
| ORA-1001 | INVALID_CURSOR | Operación ilegal con un cursor |
| ORA-1012 | NOT_LOGGED_ON | No existe conexión con Oracle |
| ORA-1017 | LOGIN_DENIED | Nombre de usuario o contraseña inválidos |
| ORA-1403 | NO_DATA_FOUND | No se ha encontrado ningún dato |
| ORA-1422 | TOO_MANY_ROWS | Hay más de una fila que corresponde a la orden SELECT...INTO |
| ORA-1476 | ZERO_DIVIDE | |
| ORA-1722 | INVALID_NUMBER | |
| ORA-6500 | STORAGE_ERROR | SQL se queda sin memoria |
| ORA-6501 | PROGRAM_ERROR | Error interno de PL/SLQ |
| ORA-6502 | VALUE_ERROR | Error de conversión |
| ORA-6511 | CURSOR_ALREADY_OPEN | Se ha intentado abrir un cursor que ya estaba abierto. |
Existen muchos más errores definidos por Oracle que nosotros podremos gestionar.
GENERACIÓN DE EXCEPCIONES
Cuando se produce el error generado por una excepción esta excepción se crea. Para generarlas, tendremos que indicarle la opción RAISE,. Veremos un ejemplo de manejo de excepciones:
| DECLARE e_TooManyStudents EXCEPTION; -- Exception to indicate an error condition v_CurrentStudents NUMBER(3); -- Current number of students registered -- for HIS-101 v_MaxStudents NUMBER(3); -- Maximum number of students allowed for -- HIS-101 BEGIN /* Find the current number of registered students, and the maximum number of students allowed. */ SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = 'HIS' AND course = 101; /* Check the number of students in this class. */ IF v_CurrentStudents > v_MaxStudents THEN /* Too many students registered - raise exception. */ RAISE e_TooManyStudents; END IF; EXCEPTION WHEN e_TooManyStudents THEN /* Handler which executes when there are too many students registered for HIS-101. We will insert a log message explaining what has happened. */ INSERT INTO log_table (info) VALUES ('History 101 has ' || v_CurrentStudents || 'students: max allowed is ' || v_MaxStudents); END; / |
Las excepciones predefinidas se generan automáticamente cuando tiene lugar el error asociado a esa excepción.
Cuando se genera una excepción el control pasa a la sección de excepciones del bloque, que está compuesta por gestores que contienen el código que se ejecutará cuando ocurra el error asociado con la excepción y ésta sea generada. Cada gestor de excepciones está compuesto por la cláusula WHEN y las órdenes que se ejecutarán cuando la excepción sea generada. La cláusula WHEN identifica la excepción correspondiente a cada gestor. Un mismo gestor puede utilizarse para más de una excepción enumerando sus nombres usando el operador OR: Así por ejemplo tendremos:
| EXCEPTION
WHEN NOT_DATA_FOUNDOR TOO_MANY:ROWS THEN INSERT INTO log_table (info) VALUES ('Ha ocurrido un error en la SELECT'); END; |
Tenemos un gestor de excepciones especial que es el gestor OTHERS que se ejecutará para todas las excepciones generadas. Siempre tiene que ser el último gestor del bloque. Este gestor como tal, sólo determina que ha ocurrido un error, pero no determina que error ha ocurrido, para determinar que error ha ocurrido usaremos las funciones predefinidas SQLCODE y SQLERRM, que nos devolverán el número y el texto del error que se ha generado. Veamos unos ejemplos:
| DECLARE e_TooManyStudents EXCEPTION; -- Exception to indicate an error condition v_CurrentStudents NUMBER(3); -- Current number of students registered -- for HIS-101 v_MaxStudents NUMBER(3); -- Maximum number of students allowed for -- HIS-101 BEGIN /* Find the current number of registered students, and the maximum number of students allowed. */ SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = 'HIS' AND course = 101; /* Check the number of students in this class. */ IF v_CurrentStudents > v_MaxStudents THEN /* Too many students registered - raise exception. */ RAISE e_TooManyStudents; END IF; EXCEPTION WHEN e_TooManyStudents THEN /* Handler which executes when there are too many students registered for HIS-101. We will insert a log message explaining what has happened. */ INSERT INTO log_table (info) VALUES ('History 101 has ' || v_CurrentStudents || 'students: max allowed is ' || v_MaxStudents); WHEN OTHERS THEN /* Handler which executes for all other errors. */ INSERT INTO log_table (info) VALUES ('Another error occurred'); END; / |
Aquí tenemos el manejo del gestor OTHERS sin especificar ni el número ni el texto del mensaje de error que se ha generado, si queremos especificarlo, podremos usar la sentencia:
| DECLARE e_TooManyStudents EXCEPTION; -- Exception to indicate an error condition v_CurrentStudents NUMBER(3); -- Current number of students registered -- for HIS-101 v_MaxStudents NUMBER(3); -- Maximum number of students allowed for -- HIS-101 v_ErrorCode NUMBER; -- Variable to hold the error message code v_ErrorText VARCHAR2(200); -- Variable to hold the error message text BEGIN /* Find the current number of registered students, and the maximum number of students allowed. */ SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE department = 'HIS' AND course = 101; /* Check the number of students in this class. */ IF v_CurrentStudents > v_MaxStudents THEN /* Too many students registered - raise exception. */ RAISE e_TooManyStudents; END IF; EXCEPTION WHEN e_TooManyStudents THEN /* Handler which executes when there are too many students registered for HIS-101. We will insert a log message explaining what has happened. */ INSERT INTO log_table (info) VALUES ('History 101 has ' || v_CurrentStudents || 'students: max allowed is ' || v_MaxStudents); WHEN OTHERS THEN /* Handler which executes for all other errors. */ v_ErrorCode := SQLCODE; v_ErrorText := SUBSTR(SQLERRM, 1, 200); -- Note the use of SUBSTR here. INSERT INTO log_table (code, message, info) VALUES (v_ErrorCode, v_ErrorText, 'Oracle error occurred'); END; / |
En este ejemplo hemos trabajado con el SQLCODE, podemos ver como usaríamos el SQLERRM en el siguiente bloque:
DECLARE v_ErrorText log_table.message%TYPE; -- Variable to hold error message text BEGIN /* SQLERRM(0) */ v_ErrorText := SUBSTR(SQLERRM(0), 1, 200); INSERT INTO log_table (code, message, info) VALUES (0, v_ErrorText, 'SQLERRM(0)'); /* SQLERRM(100) */ v_ErrorText := SUBSTR(SQLERRM(100), 1, 200); INSERT INTO log_table (code, message, info) VALUES (100, v_ErrorText, 'SQLERRM(100)'); /* SQLERRM(10) */ v_ErrorText := SUBSTR(SQLERRM(10), 1, 200); INSERT INTO log_table (code, message, info) VALUES (10, v_ErrorText, 'SQLERRM(10)'); /* SQLERRM with no argument */ v_ErrorText := SUBSTR(SQLERRM, 1, 200); INSERT INTO log_table (code, message, info) VALUES (NULL, v_ErrorText, 'SQLERRM with no argument'); /* SQLERRM(-1) */ v_ErrorText := SUBSTR(SQLERRM(-1), 1, 200); INSERT INTO log_table (code, message, info) VALUES (-1, v_ErrorText, 'SQLERRM(-1)'); /* SQLERRM(-54) */ v_ErrorText := SUBSTR(SQLERRM(-54), 1, 200); INSERT INTO log_table (code, message, info) VALUES (-54, v_ErrorText, 'SQLERRM(-54)'); END; / |
La longitud máxima de un mensaje de error de Oracle es de 512 caracteres.
También podemos asociar una excepción nominada con un error de Oracle determinado lo que nos permitirá interceptar de forma específica dicho error. Para hacerlo usaremos el pragma EXCEPTION_INIT, que se emplea de la siguiente forma:
| PRAGMA EXCEPTION_INIT (nombre_excepción, número_error_oracle); |
Por ejemplo si queremos asignar al hecho de insertar una columna con valores NULL donde no se permiten estos valores podremos generar el código como sigue:
| DECLARE e_MissingNull EXCEPTION; PRAGMA EXCEPTION_INIT(e_MissingNull, -1400); BEGIN INSERT INTO students (id) VALUES (NULL); EXCEPTION WHEN e_MissingNull then INSERT INTO log_table (info) VALUES ('ORA-1400 occurred'); END; / |
Podemos utilizar también la función predefinida RAISE_APPLICATION_ERROR, para crear nuestros propios mensajes de error que pueden ser más descriptivos que los mensajes generados por las excepciones. Veamos un ejemplo de su funcionamiento:
| CREATE OR REPLACE PROCEDURE Register ( /* Registers the student identified by the p_StudentID parameter in the class identified by the p_Department and p_Course parameters. Before calling ClassPackage.AddStudent, which actually adds the student to the class, this procedure verifies that there is room in the class, and that the class exists. */ p_StudentID IN students.id%TYPE, p_Department IN classes.department%TYPE, p_Course IN classes.course%TYPE) AS v_CurrentStudents NUMBER; -- Current number of students in the class v_MaxStudents NUMBER; -- Maximum number of students in the class BEGIN /* Determine the current number of students registered, and the maximum number of students allowed to register. */ SELECT current_students, max_students INTO v_CurrentStudents, v_MaxStudents FROM classes WHERE course = p_Course AND department = p_Department; /* Make sure there is enough room for this additional student. */ IF v_CurrentStudents + 1 > v_MaxStudents THEN RAISE_APPLICATION_ERROR(-20000, 'Can''t add more students to ' || p_Department || ' ' || p_Course); END IF; /* Add the student to the class. */ ClassPackage.AddStudent(p_StudentID, p_Department, p_Course); EXCEPTION WHEN NO_DATA_FOUND THEN /* Class information passed to this procedure doesn't exist. Raise an error to let the calling program know of this. */ RAISE_APPLICATION_ERROR(-20001, p_Department || ' ' || p_Course || ' doesn''t exist!'); END Register; / |
En este procedimiento comprobamos si existe suficiente espacio en un curso antes de permitir la inscripción de los alumnos al mismo. Si no hay espacio suficiente levantamos el error ORA-20000, donde indicamos que no hay dicho espacio.
PROPAGACIÓN DE EXCEPCIONES
Las excepciones pueden producirse en la sección declarativa, ejecutable o de excepciones de un bloque PL/SQL. Entonces, ¿cómo controlamos las excepciones que se producen fuera del bloque?. El proceso que gobierna estas situaciones se conoce como propagación de excepciones.
Excepciones generadas en la sección ejecutable.
Cuando se genera una excepción en la sección ejecutable de un bloque PL/SQL utiliza el siguiente proceso:
1. Si el bloque actual tiene un gestor para la excepción, lo ejecuta y termina el bloque con éxito, el control pasa entonces al bloque de nivel superior.
2. Si no hay gestor para la excepción actual propaga la excepción generándola en el bloque del nivel superior.
Ámbito de las excepciones
El ámbito de las excepciones es igual que el de las variables, si una excepción se propaga fuera de su ámbito, no podrá ser referenciada por su nombre. En general si es necesario propagar un error definido por el usuario fuera de un bloque, es mejor definir la excepción en un paquete para que continúe siendo visible fuera del bloque.
OBJETOS
Los objetos son una de las principales características de Oracle 8 y PL/SQL. Oracle presenta un tipo de programación orientada a objetos. Así, por ejemplo, en una escuela tendremos diferentes objetos: alumnos, secretarias, profesores,... Cada uno de estos objetos tiene atributos y entre los objetos existen relaciones.
Los objetos se crean con tipos de objetos que describen los atributos y los métodos asociados con una clase de objeto en particular. Los objetos tienen que estar instalados y para saber si están instalados cuando entremos a SQL tendremos que ver lo siguiente:
| Oracle8 Enterprise Edition....
With the Partitioning and Objects Option ... |
Un tipo de objeto es similar a un paquete con una especificación y un cuerpo. La especificación del tipo contiene los atributos y las declaraciones formales para los métodos, el cuerpo contiene el código real de los métodos. Así. por ejemplo podemos crear un objeto para el tipo Estudiantes de la siguiente manera:
| CREATE OR REPLACE TYPE EstudianteObj AS OBJECT (
ID NUMBER(5), First_name VARCHAR2(20), Last_Name VARCHAR2(30), Major VARCHAR2(30), current_credits number(3)); |
Al igual que cualquier otra variable PL/SQL un objeto se declara simplemente incluyéndolo en la sección declarativa del bloque. Para inicializar un objeto se requiere un constructor que es una función que devuelve un objeto inicializado y toma como argumentos los valores de los atributos del objeto.
DECLARE -- Creates the object instance, with the attributes set. v_Student StudentObj := EstudianteObj(10020, 'Chuck', 'Choltry', NULL, 0); BEGIN -- Modifies the major attribute to 'Music'. Note the use of dot -- notation to refer to the attribute. v_Student.major := 'Music'; END; / |
Los métodos se declaran en la especificación del tipo de objeto, detrás de los atributos y se implementan en el cuerpo del tipo. Para declararlo, es necesario usar una especificación muy similar a la estándar excepto que va precedido por la palabra MEMBER:
| CREATE OR REPLACE TYPE StudentObj AS OBJECT ( ID NUMBER(5), first_name VARCHAR2(20), last_name VARCHAR2(20), major VARCHAR2(30), current_credits NUMBER(3), -- Returns the first and last names, separated by a space. MEMBER FUNCTION FormattedName RETURN VARCHAR2, PRAGMA RESTRICT_REFERENCES(FormattedName, RNDS, WNDS, RNPS, WNPS), -- Updates the major to the specified value in p_NewMajor. MEMBER PROCEDURE ChangeMajor(p_NewMajor IN VARCHAR2), PRAGMA RESTRICT_REFERENCES(ChangeMajor, RNDS, WNDS, RNPS, WNPS), -- Updates the current_credits by adding the number of -- credits in p_CompletedClass to the current value. MEMBER PROCEDURE UpdateCredits(p_CompletedClass IN ClassObj), PRAGMA RESTRICT_REFERENCES(UpdateCredits, RNDS, WNDS, RNPS, WNPS), -- ORDER function used to sort students. ORDER MEMBER FUNCTION CompareStudent(p_Student IN StudentObj) RETURN NUMBER ); / |
Tenemos que aclarar unas cosas respecto a los objetos:
Los tipos de objetos son muy similares a los paquetes en muchos detalles:
Sin embargo existen diferencias significativas:
Para llamar a un método tendremos que usar la sintaxis nombre_objeto.nombre_método