La programación
Introducción
La programación permite crear procedimientos almacenados, funciones y triggers, incluso realizar aplicaciones más o menos complejas.
Oracle ha creado su propio lenguaje estructurado: el PL/SQL. Permite asociar comandos SQL con comandos de un lenguaje procedural.
Los elementos creados en PL/SQL se deben compilar antes de ejecutarse.
Todas las instrucciones SQL se pueden utilizar en un bloque PL/SQL. Un « bloque » es un trozo de código PL/SQL, equivalente a una función o un procedimiento en otro lenguaje.
PostgreSQL propone PL/pgSQL, SQL Server, el Transact SQL.
Sintaxis general
Un programa se puede descomponer en tres partes:
-
una parte declarativa,
-
una parte de tratamiento,
-
una parte de gestión de errores.
La parte declarativa permite declarar e inicializar todas las variables utilizadas en la parte de tratamiento. En un programa PL/SQL, se pueden utilizar los tipos Oracle para las variables, así como crear sus propios tipos.
La parte de gestión de errores permite indicar las instrucciones que se ejecutarán cuando se encuentre un error en el proceso.
Estas dos secciones (declarativa y errores) son opcionales.
La sintaxis de un programa es la siguiente:
[DECLARE
...]
BEGIN
...
...
[EXCEPTION
...]
END;
En MySQL y PostgreSQL, estos bloques no se pueden usar solos. Deben estar incluidos en una función o trigger, y para MySQL un procedimiento.
Ejemplo de script SQL Server para ejecutar
DECLARE @Hotel int
BEGIN
SET @Hotel = 2
SELECT NumHabitacion, Descripcion
FROM Habitaciones INNER JOIN TipoHabitacion ON TipoHabitacion.idTipoHabitacion =
Habitaciones.TipoHabitacion
WHERE Hotel = @Hotel;
END;
Resultado
NumHabitacion |
Descripción |
1 |
1 cama individual con ducha |
2 |
2 camas individuales con ducha |
3 |
3 camas individuales con ducha y WC separados |
4 |
1 cama doble con ducha |
5 |
1 cama doble con ducha y WC separados |
6 |
1 cama doble con baño y WC separados |
7 |
1 cama doble grande con baño y WC separados |
Por ejemplo, seleccione la etiqueta, el número de estrellas...
Los cursores
Un cursor es un elemento que permite almacenar una consulta que devuelva varias filas. Esto le permite navegar por cada línea para utilizarlas.
Hay que declararlo en la sección declarativa.
Hay que abrirlo con un OPEN, ejecutarlo con un FETCH y cerrarlo con un CLOSE.
En el ejemplo, el tipo de cama buscado se pasa como parámetro al cursor: CURSOR C_habitaciones_por_tipo_cama(TipoCama IN VARCHAR2) IS.
TipoCama se indica en el OPEN CURSOR con la variable que contiene la descripción del tipo de cama: OPEN C_habitaciones_por_tipo_cama(TipoCama _buscada).
Ejemplo con la misma consulta anterior:
DECLARE
-- Declaración del cursor C_habitaciones_por_tipo_cama
CURSOR C_habitaciones_por_tipo_cama (TpCama in varchar2) IS
SELECT Hoteles.Etiqueta, Habitaciones.NumHabitacion,
TiposHabitacion.NumeroCama,
TiposHabitacion.Descripcion
FROM Habitaciones INNER JOIN
Hoteles ON Habitaciones.Hotel = Hoteles.idHotel INNER JOIN
TiposHabitacion ON Habitaciones.TipoHabitacion =
TiposHabitacion.idTipoHabitacion
WHERE TipoCama = tipocama and Estrella = '**';
-- Declaración de las variables receptoras
Etiqueta_hotel varchar2(50);
Num_Habitacion varchar2(6);
NumCama number(38,0);
Descripcion varchar2(255);
-- Declaración de las otras variables
TpCama_buscada varchar2(20) := 'cama...
El control del flujo
1. El bucle WHILE
El WHILE permite repetir un trozo de código mientras la condición que se comprueba al principio sea cierta. Si la condición es falsa, se sale directamente del bucle sin ejecutar el código.
Ejemplo Oracle
DECLARE
-- Declaración del cursor C_habitaciones_por_tipo_cama
CURSOR C_habitaciones_por_tipo_cama (TpCama in varchar2) IS
SELECT Habitaciones.idHabitacion, Hoteles.Etiqueta,
Habitaciones.NumHabitacion, TiposHabitacion.NumeroCama,
TiposHabitacion.Descripcion
FROM Habitaciones INNER JOIN
Hoteles ON Habitaciones.Hotel = Hoteles.idHotel INNER JOIN
TiposHabitacion ON Habitaciones.TipoHabitacion =
TiposHabitacion.idTipoHabitacion
WHERE TipoCama = tipocama and Estrella = '**';
-- Declaración de las variables receptoras
id_habitacion number :=0;
Etiqueta_hotel varchar2(50);
Num_Habitacion varchar2(6);
NumCama number(38,0);
Descripcion varchar2(255);
-- Declaración de las otras variables
TpCama_buscada varchar2(20) := 'cama individual';
BEGIN
-- Apertura
OPEN C_habitaciones_por_tipo_cama(tipocama_buscada);
-- Lectura del primer elemento
FETCH C_habitaciones_por_tipo_cama
INTO id_habitacion, Etiqueta_hotel, Num_Habitacion, NumCama,
descripcion;
-- Bucle de lectura mientras que el identificador de la habitacion
es < 10
WHILE id_habitacion < 10
LOOP
-- Visualización de los elementos recuperados
DBMS_OUTPUT.PUT_LINE('Id Habitación: '||id_habitacion);
DBMS_OUTPUT.PUT_LINE('Nombre del hotel: '||Etiqueta_hotel);
DBMS_OUTPUT.PUT_LINE('Número de habitaciones:
'||num_habitacion);
DBMS_OUTPUT.PUT_LINE('Número de camas: '||numcama);
DBMS_OUTPUT.PUT_LINE('Tipo de cama: '||tipocama_buscada);
DBMS_OUTPUT.PUT_LINE('Descripción: '||descripcion);
-- Lectura del elemento siguiente
FETCH C_habitaciones_por_tipo_cama
INTO id_habitacion, Etiqueta_hotel, Num_Habitacion, NumCama,
descripcion;
EXIT WHEN C_habitaciones_por_tipo_cama%NOTFOUND;
END LOOP;
-- Cierre del cursor (liberación de memoria)
CLOSE C_habitaciones_por_tipo_cama;
END;
Resultado
Id Habitación: 8
Nombre...
Las excepciones Oracle más utilizadas
Salvo la excepción « NOT_DATA_FOUND » que hemos visto en los ejemplos anteriores, existen multitud de excepciones. No vamos a citarlas todas en este libro, pero vamos a ver algunas que puede ser útiles.
CURSOR_ALREADY_OPEN: el cursor ya está abierto. Hay que cerrarlo antes de volver a abrirlo (SQLCODE --> 06511)
INVALID_NUMBER: la variable utilizada no contiene un número válido (SQLCODE --> 01722)
NOT_LOGGED_ON: el usuario no está conectado a la base de datos (SQLCODE --> 01012)
TOO_MANY_ROWS: la selección devuelve varias filas, mientras el select solo prevé una ocurrencia; se debe hacer un cursor (SQLCODE --> 01422)
ZERO_DIVIDE: división por cero (SQLCODE --> 01476)
Para tratar cualquier tipo de error, es preferible añadir siempre una comprobación de este tipo para visualizar el error.
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( 'El número de error es: '||
TO_CHAR( SQLCODE )) ;
DBMS_OUTPUT.PUT_LINE( 'correspondiente a: '||
TO_CHAR( SQLERRM )) ;
La gestión de los errores en Transact SQL
La gestión de los errores permite anticiparse a los problemas que pueden aparecer durante la ejecución de un programa.
El principio consiste en probar el código en un primer bloque con BEGIN TRY … END TRY y después interceptar la excepción con BEGIN CATCH …. END CATCH.
Sintaxis
BEGIN
... ...
[BEGIN TRY
... ...
END TRY]
[BEGIN CATCH
... ...
END CATCH]
END;
Ejemplo
DECLARE @i int
BEGIN
BEGIN TRY
SET @i = 2
SET @i = @i / 0
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber
, ERROR_MESSAGE() AS ErrorMessage
, ERROR_LINE() AS ErrorLine;END CATCH
END;
Resultado
ErrorNumber |
ErrorMessage |
ErrorLine |
8134 |
División por cero |
5 |
También es posible tratar la excepción.
DECLARE @i int
BEGIN
BEGIN TRY
SET @i = 2
SET @i = @i / 0
END TRY
BEGIN CATCH
IF @@ERROR = 8134
SET @i = @i / 1
print @i
END CATCH
END;
Resultado
2
Es posible devolver un error gracias a la función RAISERROR(). Esta función acepta tres argumentos (constante o mensaje o variable, número de gravedad del error y estado del error).
Ejemplo
DECLARE @i int
DECLARE @f float ...
Creación de un procedimiento almacenado
Cuando se quiere compartir un trozo de código realizado en PL/SQL, se puede grabar en la base de datos y así el resto de programadores pueden acceder a él. Un procedimiento almacenado es un bloque de código compilado y almacenado en la base de datos. Basta con llamarlo por su nombre para ejecutarlo.
La principal ventaja del procedimiento almacenado, es que está guardado en un formato « ejecutable », el servidor de la base de datos no va a interpretar los comandos sino que los ejecuta directamente, con la ganancia de tiempo considerable respecto a la ejecución de la misma consulta desde un programa.
Otra ventaja del procedimiento almacenado es que se le pueden pasar parámetros.
Sintaxis Oracle
CREATE OR REPLACE PROCEDURE <nombre procedimiento>
[(<variable entrada 1> IN <formato>,
<variable entrada 2> IN <formato>,
... ...
<variable salida> OUT <formato>)]
IS
BEGIN
... ...
[EXCEPTION
... ...
]
END;
Sintaxis SQL Server
CREATE OR ALTER PROCEDURE <nombre procedimiento>
[(@<variable 1> <formato>,
@<variable 2> <formato>,
... ... )]
AS
BEGIN
... ...
...
Creación de una función almacenada
En el mismo ejemplo, también es posible crear una función en lugar de un procedimiento. ¿Cuál es la diferencia entre una función y un procedimiento? Es que la primera devuelve un valor. Es posible incluirlo en una nueva consulta.
Sintaxis Oracle
CREATE OR ALTER FUNCTION <nombre función>
[(<variable entrada 1> IN <formato>,
<variable entrada 2> IN <formato>,
... ... ]
RETURN <formato>
IS
<variable salida> <formato>)]
BEGIN
... ...
[EXCEPTION
... ...
]
END;
Sintaxis SQL Server
CREATE OR ALTER FUNCTION <nombre función>
[(@<variable 1> <formato>,
@<variable 2> <formato>,
... ... )]
RETURNS <formato>
AS
BEGIN
... ...
END;
Por ejemplo, la siguiente función PL/SQL devuelve el precio de la habitación a partir, del nombre del hotel, del tipo y la cantidad de camas y de una fecha.
CREATE OR REPLACE FUNCTION PRECIO_HABITACION
(vhotel IN VARCHAR2, vtipocama IN VARCHAR2, inumcama IN INT, dfecha IN DATE)
RETURN NUMBER
IS
DPrecio NUMBER;
BEGIN
SELECT Precio INTO dPrecio FROM Tarifas t
INNER...
Los packages
La denominación «package» significa que se agrupan bajo un mismo nombre los procedimientos y funciones sobre el mismo tema, y así podemos crear verdaderas aplicaciones.
En un paquete, podemos tener declaraciones de variables públicas o privadas, así como funciones y procedimientos privados que no se pueden ver desde fuera.
En un paquete, hay que crear una zona de declaraciones y una zona donde están las funciones y los procedimientos.
En la zona de declaraciones, se listarán los procedimientos y funciones que se describen en la otra zona. Todas las funciones que estén declaradas aquí serán «públicas». Las variables funcionan del mismo modo, si están en la zona de declaraciones, son «públicas».
Los packages no existen para SQL Server.
Sintaxis
CREATE OR REPLACE PACKAGE <nombre paquete> IS
PROCEDURE <nombre procedimiento 1>;
FUNCTION <nombre función 1> (<variable 1> IN <formato>) RETURN
<formato>; END;
/
CREATE OR REPLACE PACKAGE BODY <nombre paquete> IS
FUNCTION <función 1>
... ...
END;
PROCEDURE <procedimiento 1> IS
......
Compilación de un procedimiento, de una función o de un paquete
Una vez creado o modificado un procedimiento, función o paquete, es necesario compilar el código antes de poder invocarlo.
Sintaxis
ALTER <'PROCEDURE' o 'FUNCTION' o 'PACKAGE'> <Nombre procedimiento,
función o paquete> COMPILE;
Ejemplo
ALTER FUNCTION PRECIO_HABITACION COMPILE;
ALTER PROCEDURE LISTA_HABITACION_HOTEL COMPILE;
ALTER PACKAGE MOSTRAR_HOTEL COMPILE PACKAGE;
ALTER PACKAGE MOSTRAR_HOTEL COMPILE BODY;
- compila body y package
ALTER PACKAGE MOSTRAR_HOTEL COMPILE;
Eliminación de un procedimiento, de una función o de un paquete
Cuando un código es obsoleto, es aconsejable borrarlo utilizando la siguiente sintaxis.
Sintaxis
DROP <'PROCEDURE', 'FUNCTION' o 'PACKAGE'> <Nombre procedimiento,
función o paquete>;
Ejemplo
DROP FUNCTION PRECIO_HABITACION;
DROP PROCEDURE LISTA_HABITACION;
- eliminación de todo el paquete (cuerpo y declaración)
DROP PACKAGE MOSTRAR_HOTEL;
- eliminación del cuerpo del paquete
DROP PACKAGE BODY MOSTRAR_HOTEL;
Los triggers
Un trigger permite lanzar comandos que se van a ejecutar después de cada evento producido en una tabla.
El contenido del código ejecutado por un trigger normalmente es PL/SQL o C o Java.
Los triggers normalmente se utilizan para gestionar toda la funcionalidad de una aplicación. Permiten realizar controles sobre el contenido de las tablas automáticamente. Los triggers también pueden servir para recuperar información a lo largo de un día completo sobre las actividades de la base de datos para, a continuación, ser tratados esos datos por otra aplicación.
En general, se identifican los controles en los programas aplicativos ejecutados en la parte cliente. Los triggers permiten añadir otros controles que se ejecutarán en la parte del servidor.
La primera ventaja del trigger es que está relacionado con una acción de la base de datos (INSERT, UPDATE, DELETE), por lo que no se corre el riesgo de olvidarse de modificar un programa. De hecho, normalmente es complicado modificar todos los programas de una aplicación para agregar un control asociado a un INSERT, por ejemplo. Sería necesario encontrar todos los programas afectados, modificarlos y probar cada uno de los programas modificados.
El trigger se desencadena de manera sistemática, por lo que no se puede olvidar una actualización y la modificación se hace independientemente de los programas aplicativos.
Un trigger puede desencadenarse antes o después de la sentencia SQL solicitada. Se indica con un AFTER o BEFORE. En SQL Server, se ejecuta antes o en lugar de (INSTEAD OFF) pero no antes.
En un trigger BEFORE, se puede controlar, antes de cualquier modificación, algunos elementos de la base de datos e impedir de esta manera las actualizaciones.
En un trigger AFTER, la actualización ya ha tenido lugar y se desencadenan las acciones que resultan de ella.
Sintaxis general de un trigger PL/SQL
CREATE OR REPLACE TRIGGER <nombre del trigger>
[AFTER] [INSERT o DELETE o UPDATE]
ON <nombre de tabla>
[FOR EACH ROW]
[WHEN]
DECLARE
... ...
BEGIN
... ...
END;
La cláusula FOR EACH ROW significa que el trigger actúa en todas las líneas afectadas por la sentencia SQL.
La cláusula WHEN permite añadir un criterio adicional...
Ejercicios
Primer ejercicio
Crear una función que calcula la edad de un actor.
Segundo ejercicio
Para realizar el siguiente ejercicio, cree un procedimiento almacenado que añada un nuevo país con la etiqueta "POR COMPLETAR".
Tercer ejercicio
Crear un trigger que, durante la creación de un actor, compruebe la existencia de la nacionalidad. Si esta es desconocida, el código se crea con la etiqueta «A COMPLETAR».
Soluciones a los ejercicios
Primer ejercicio
Sintaxis Oracle
CREATE OR REPLACE FUNCTION CALCUL_EDAD_ACTOR
(FECHA_NACIMIENTO IN DATE) RETURN NUMBER
IS
EDAD_ACTOR NUMBER(5);
BEGIN
SELECT (SYSDATE - FECHA_NACIMIENTO)/365 INTO EDAD_ACTOR FROM DUAL;
RETURN (EDAD_ACTOR);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE( ' El número de error es: '||
TO_CHAR( SQLCODE )) ;
DBMS_OUTPUT.PUT_LINE( 'correspondiente a: '||
TO_CHAR( SQLERRM )) ;
END;
SELECT CALCUL_EDAD_ACTOR(FECHA_NACIMIENTO) FROM ACTOR;
Sintaxis SQL Server
CREATE OR ALTER FUNCTION CALCUL_EDAD_ACTOR (@fechaNacimiento fecha)
RETURNS integer
AS
BEGIN
DECLARE @Edad integer
SELECT @Edad= DATEDIFF(YEAR, @fechaNaciemiento, GETDATE());
RETURN @Edad
END;
SELECT dbo.CALCUL_EDAD_ACTOR (FECHA_NACIMIENTO) FROM ACTOR;
Sintaxis MySQL
CREATE FUNCTION CALCUL_EDAD_ACTOR (fechaNacimiento DATE)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE vEdad INT;
SELECT ROUND(DATEDIFF(CURRENT_DATE, fechaNacimiento) / 365)
INTO vEdad FROM DUAL;
RETURN vEdad;
END
SELECT CALCUL_EDAD_ACTOR(FECHA_NACIMIENTO) FROM ACTOR;
Syntaxe PostgreSQL
CREATE OR REPLACE FUNCTION public."CALCUL_EDAD_ACTOR" ...