Construyendo Con Bloques en PL
Construyendo Con Bloques en PL
Construyendo Con Bloques en PL
Marzo/Abril 2011
Tres años más tarde, escribí mi primer libro sobre PL/SQL y desde entonces he estudiado este lenguaje, he
desarrollado montones y montones de código PL/SQL, y he escrito sobre este excelente lenguaje de
programación de base de datos. Por supuesto, yo no era el único. Miles de desarrolladores en todo el mundo
han construido una multitud de aplicaciones basadas en Oracle PL/SQL en las décadas desde que se liberó.
Lo mejor de todo es que sigue habiendo un flujo constante de nuevos desarrolladores PL/SQL. De hecho, con
la aparición relativamente reciente de la India, China y otras naciones como potencias de tecnología, he visto
a toda una nueva generación de desarrolladores descubrir PL/SQL y trabajar para dominarlo.
Para ayudar a que los recién llegados a PL/SQL saquen el máximo provecho de este lenguaje, Oracle
Magazine me ha pedido que escriba una serie de artículos para principiantes, de los cuales éste es el primero.
Si usted es un desarrollador experimentado en PL/SQL, también puede encontrar en estos artículos un repaso
práctico sobre sus fundamentos.
Para esta serie de artículos, voy a suponer que a pesar de ser nuevos en PL/SQL, mis lectores han tenido
alguna experiencia en programación y están familiarizados con SQL. Mi enfoque general, además, estará en
conseguir desarrolladores productivos en PL/SQL lo más rápido posible.
¿Qué es PL/SQL?
Para contestar esta pregunta, es importante recordar que en cada sitio Web que visitamos, cada aplicación
que se ejecuta es construida sobre un stack (una pila) de tecnologías de software. En la parte superior de la
pila está la capa de presentación, las pantallas o dispositivos interactivos con los que el usuario interactúa
directamente (hoy en día los lenguajes más populares para las capas de presentación son Java y .NET). En la
parte inferior de la pila está el lenguaje de máquina que se comunica con el hardware.
En algún lugar en medio de la pila de tecnología se encuentra la base de datos, el software que nos permite
almacenar y manipular grandes volúmenes de datos complejos. La tecnología de bases de datos relacionales,
construida en torno a SQL, es la tecnología de base de datos dominante en el mundo de hoy.
SQL es un lenguaje de conjuntos muy poderoso, cuyo único objetivo es manipular el contenido de bases de
datos relacionales. Si usted desarrolla aplicaciones basadas en bases de datos Oracle, usted (o el código de
alguien que escribe en un nivel inferior de la pila de la tecnología) debe ejecutar sentencias SQL para
recuperar datos desde o cambiar datos en la base de datos. Sin embargo, SQL no se puede utilizar para
implementar toda la lógica de negocios y la funcionalidad que el usuario final necesita en nuestras
aplicaciones. Esto nos lleva a PL/SQL.
1. Declarativa: sentencias que declaran variables, constantes y otros elementos de código, que después
pueden ser usados dentro del bloque
2. Ejecutable: sentencias que se ejecutan cuando se ejecuta el bloque
3. Manejo de excepciones: una sección especialmente estructurada para atrapar y manejar cualquier
excepción que se produzca durante la ejecución de la sección ejecutable
Sólo la sección ejecutable es obligatoria. No es necesario que usted declare nada en un bloque, ni que
maneje las excepciones que se puedan lanzar.
Un bloque es en sí mismo una sentencia ejecutable, por lo que se pueden anidar los bloques unos dentro de
otros.
El clásico “¡Hola Mundo!” es un bloque con una sección ejecutable que llama al procedimiento
DBMS_OUTPUT.PUT_LINE para mostrar texto en pantalla:
BEGIN
DBMS_OUTPUT.put_line('¡Hola Mundo!');
END;
Las funciones y procedimientos —tipos de bloques con un nombre— son discutidos con mayor detalle más
adelante en este artículo, así como los paquetes. En pocas palabras, sin embargo, un paquete es un
contenedor de múltiples funciones y procedimientos. Oracle extiende PL/SQL con muchos paquetes
incorporados en el lenguaje.
El siguiente bloque declara una variable de tipo VARCHAR2 (un string) con un largo máximo de 100 bytes
para contener el string ‘¡Hola Mundo!’. Después, el procedimiento DBMS_OUTPUT.PUT_LINE acepta la
variable, en lugar del literal, para desplegarlo:
DECLARE
l_mensaje VARCHAR2(100) := '¡Hola Mundo!';
BEGIN
DBMS_OUTPUT.put_line(l_mensaje);
END;
Note que he llamado a la variable l_mensaje. Normalmente uso el prefijo l_ para variables locales —variables
definidas dentro del código de un bloque— y el prefijo g_ para variables globales definidas en un paquete.
El siguiente ejemplo de bloque agrega una sección de manejo de excepciones que atrapa cualquier excepción
(WHEN OTHERS) que pueda ser lanzada y muestra el mensaje de error, que es retornado por la función
SQLERRM (provista por Oracle).
DECLARE
l_mensaje VARCHAR2(100) := '¡Hola Mundo!';
BEGIN
DBMS_OUTPUT.put_line(l_mensaje);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(SQLERRM);
END;
El siguiente ejemplo de bloque demuestra la habilidad de PL/SQL de anidar bloques dentro de bloques así
como el uso del operador de concatenación (||) para unir múltiples strings.
DECLARE
l_mensaje VARCHAR2(100) := '¡Hola';
BEGIN
DECLARE
l_mensaje2 VARCHAR2(100) := l_mensaje || ' Mundo!';
BEGIN
DBMS_OUTPUT.put_line(l_mensaje2);
END;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(DBMS_UTILITY.format_error_stack);
END;
SQL*Plus entonces ejecuta el bloque y muestra el “¡Hola Mundo!” en la pantalla. SQL*Plus es provisto por
Oracle como una especie de línea base de entorno donde se pueden ejecutar sentencias SQL y bloques
PL/SQL. Mientras que algunos desarrolladores siguen utilizando únicamente SQL*Plus, la mayoría utiliza un
entorno de desarrollo integrado (IDE). Entre los más populares de estos entornos de desarrollo (basado en
encuestas informales que he tomado en mis sesiones de entrenamiento) son
• Oracle SQL Developer, de Oracle • Toad y SQL Navigator, de Quest Software • PL/SQL Developer, de
Allround Automations
Cada herramienta ofrece, con algunas diferencias, ventanas y pasos para crear, guardar, y ejecutar bloques
PL/SQL, así como habilitar y deshabilitar la salida del servidor. En esta serie de artículos, voy a suponer que
sólo tienen acceso a SQL*Plus y que van a ejecutar todas las sentencias en una ventana de comandos
SQL*Plus.
Todos los bloques que hemos visto hasta el momento son “anónimos”, no tienen nombres. Si los bloques
anónimos fueran la única manera de organizar el código, sería muy difícil usar PL/SQL para crear una
aplicación grande y compleja. Por esto, PL/SQL soporta la definición de bloques nombrados (named blocks),
también conocidos como subprogramas. Los subprogramas pueden ser procedimientos o funciones.
Generalmente, un procedimiento se utiliza para realizar una acción y una función se utiliza para calcular y
devolver un valor. Voy a tratar sobre subprogramas con mucho más detalle en un próximo artículo de esta
serie. Por ahora, vamos a asegurarnos de que se comprendan los conceptos básicos detrás de la creación del
subprograma.
Supongamos que necesitamos mostrar "¡Hola Mundo!" desde múltiples lugares en nuestra aplicación.
Queremos evitar la repetición de la misma lógica en todos esos lugares. Por ejemplo, ¿qué pasa cuando
tenemos que cambiar el mensaje, tal vez para "¡Hola Universo!"? Vamos a tener que encontrar todos los
lugares en nuestro código donde esta lógica aparece.
Con esto hemos extendido PL/SQL. Además de llamar a los programas creados por Oracle e instalados en la
base de datos (como DBMS_OUTPUT.PUT_LINE), podemos llamar a nuestro propio subprograma dentro de
un bloque PL/SQL:
BEGIN
hola_mundo;
END;
Hemos escondido todos los detalles de cómo decir hola al mundo dentro del cuerpo (body), o implementación,
de nuestro procedimiento. Ahora podemos llamar a este procedimiento hola_mundo y mostrar el mensaje
deseado sin tener que escribir la llamada a DBMS_OUTPUT.PUT_LINE o averiguar la forma correcta de darle
formato al texto. Podemos llamar a este procedimiento desde cualquier lugar en nuestra aplicación. Así que si
alguna vez necesitamos cambiar ese texto, lo vamos a hacer en un solo lugar, el único punto de definición de
ese texto.
El procedimiento hola_mundo es muy simple. Tus procedimientos tendrán mucho más código, y casi siempre
también tendrán parámetros. Los parámetros pasan información a los subprogramas, cuando éstos son
llamados, y es lo que permite crear subprogramas más flexibles y genéricos. Se pueden usar en muchos
contextos diferentes.
He mencionado antes que algún día puede ser que desee mostrar "¡Hola Universo!" en lugar de "¡Hola
Mundo!". Podría hacer una copia de nuestro procedimiento hola_mundo y cambiar el texto que se muestra:
Justo después del nombre del procedimiento, añadimos entre paréntesis de apertura y cierre, un único
parámetro. Podemos tener varios parámetros, pero cada parámetro de la misma forma básica:
En otras palabras, debemos proveer un nombre para el parámetro, el modo o forma en que éste será usado
(IN = sólo lectura), y el tipo de dato que será pasado al subprograma a través de este parámetro.
BEGIN
hola_lugar('Mundo!');
hola_lugar('Universo!');
END;
Más adelante en esta serie vamos a explorar el concepto de reutilización y la manera de evitar la repetición,
pero debes ser capaz de ver en este ejemplo, el poder de ocultar la lógica detrás de un bloque con nombre.
Ahora supongamos que no sólo queremos mostrar nuestros mensajes "Hola". A veces tenemos que mantener
los mensajes en una tabla en la base de datos; en otras ocasiones, tenemos que pasar el texto de nuevo al
entorno del cliente para su visualización en un navegador Web. En otras palabras, necesitamos separar la
forma en que se construyó el mensaje "Hola" de la forma en que se utiliza (se visualiza, se guarda, se envía
otro programa, etc). Podemos alcanzar este nivel deseado de flexibilidad moviendo el código que construye el
mensaje a su propia función:
DECLARE
l_mensaje VARCHAR2(100);
BEGIN
l_mensaje := hola_mensaje('Universo');
END;
Nota que llamamos a la función hola_mensaje como parte de una sentencia PL/SQL (en este caso, la
asignación de un texto a una variable). La función hola_mensaje devuelve un string, por lo que se puede
utilizar en lugar de un string en cualquier sentencia ejecutable.
También podemos volver a nuestro procedimiento hola_lugar y reemplazar el código utilizado para crear el
string con una llamada a la función:
También podemos llamar la función desde una sentencia SQL. En el siguiente bloque, insertamos el mensaje
en una tabla de la base:
BEGIN
INSERT INTO tabla_mensaje(fecha_mensaje, texto_mensaje)
VALUES (SYSDATE, hola_mensaje('Montevideo'));
END;
Aunque la lógica del “mensaje hola” es muy simple, demuestra el poder de asignar nombres a una o más
sentencias ejecutables (un algoritmo) y luego referenciar el algoritmo simplemente especificando el nombre y
los parámetros requeridos.
Los bloques PL/SQL con nombre, permiten construir aplicaciones complejas que pueden ser comprendidas y
mantenidas con relativa facilidad.
hola_mundo
hola$mundo
hola#mundo
• PL/SQL es case-insensitive (no es sensitivo a mayúsculas y minúsculas) con respecto a los identificadores.
PL/SQL trata todos los siguientes como el mismo identificador:
hola_mundo
Hola_Mundo
HOLA_MUNDO
Para ofrecer más flexibilidad, Oracle permite evitar las restricciones de la segunda y tercera regla, encerrando
al identificador entre comillas dobles. Un quoted identifier (identificador encerrado entre comillas) puede
contener cualquier secuencia de caracteres imprimibles excluyendo las comillas dobles; las diferencias entre
mayúsculas y minúsculas serán además preservadas. Así, todos los siguientes identificadores son válidos y
distintos:
"Abc"
"ABC"
"a b c"
Es muy raro encontrar identificadores entre comillas en código PL/SQL; algunos grupos de desarrollo los usan
para conformar con sus convenciones de nombres o porque encuentran que una mezcla de mayúsculas y
minúsculas resulta más fácil de leer.
Estas mismas reglas aplican a los nombres de los objetos de base de datos como tablas, vistas y
procedimientos, con una regla adicional: a menos que se encierren entre comillas los nombres de estos
objetos, Oracle los mantendrá en mayúsculas.
En el bloque siguiente, llamaremos este procedimiento tres veces, y aunque el nombre luzca diferente cada
vez, siempre se ejecutará el mismo procedimiento:
BEGIN
hola_mundo;
HOLA_MUNDO;
"HOLA_MUNDO";
END;
Por otro lado, la base Oracle no será capaz de ejecutar el procedimiento si lo llamamos como sigue:
BEGIN
"hola_mundo";
END;
Si no se quiere que los nombres de los subprogramas se mantengan en mayúsculas, los nombres se deben
encerrar entre comillas cuando se crea el subprograma:
Y aquí hay algunas buenas noticias: Oracle hace que sea muy fácil escribir y ejecutar sentencias SQL en
PL/SQL. La mayor parte de las veces, simplemente escribiremos las sentencias SQL directamente en nuestro
bloque PL/SQL y después agregaremos el código necesario para la interfaz entre las sentencias SQL y el
código PL/SQL.
Supongamos, por ejemplo, que tenemos una tabla llamada empleados, con una columna clave primaria
id_empleado, y una columna apellido. Podemos ver el apellido del empleado con ID 138, como sigue:
SELECT apellido
FROM empleados
WHERE id_empleado = 138
Ahora querríamos ejecutar esta misma consulta dentro de nuestro bloque PL/SQL y desplegar el nombre.
Para hacer esto, necesitaremos “copiar” el apellido desde la tabla a una variable local, lo cual podemos hacer
con la cláusula INTO:
DECLARE
v_apellido empleados.apellido%TYPE;
BEGIN
SELECT apellido
INTO v_apellido
FROM empleados
WHERE id_empleado = 138;
DBMS_OUTPUT.put_line(v_apellido);
END;
Primero declaramos una variable local, y haciendo esto introducimos otra característica elegante de PL/SQL:
la capacidad de fijar el tipo de datos de nuestra variable en función del tipo de datos de una columna en una
tabla (esto será profundizado más adelante en esta serie)
Después ejecutamos una consulta contra la base, obteniendo el apellido del empleado y asignándolo
directamente en la variable v_apellido.
Por supuesto, querremos hacer más que ejecutar sentencias SELECT en PL/SQL, también querremos
insertar, modificar y eliminar datos desde PL/SQL. Aquí hay ejemplos de cada uno de esos tipos de
sentencias DML:
• Eliminamos todos los empleados en el departamento 10 y mostramos cuántas tuplas fueron eliminadas:
DECLARE
v_id_departamento empleados.id_departamento%TYPE := 10;
BEGIN
DELETE FROM empleados
WHERE id_departamento = v_id_departamento;
DBMS_OUTPUT.put_line(SQL%ROWCOUNT);
END;
DECLARE
v_id_departamento empleados.id_departamento%TYPE := 10;
BEGIN
UPDATE empleados
SET salario = salario * 1.2
WHERE id_departamento = v_id_departamento;
DBMS_OUTPUT.put_line(SQL%ROWCOUNT);
END;
BEGIN
INSERT INTO empleados (id_empleado
, apellido
, id_departamento
, salario)
VALUES (100
, 'Feuerstein'
, 10
, 200000);
DBMS_OUTPUT.put_line(SQL%ROWCOUNT);
END;
En este bloque, proveímos los valores de las columnas como literales, en lugar de variables, directamente
dentro de la sentencia SQL.
• PL/SQL Challenge, en plsqlchallenge.com: una pregunta diaria sobre PL/SQL que te ayudará a evaluar y
mejorar tu conocimiento sobre PL/SQL
DECLARE
l_mensaje1 VARCHAR2(100) := 'Hola';
l_mensaje2 VARCHAR2(100) := ' Mundo!';
BEGIN
IF SYSDATE >= TO_DATE('01-JAN-2012')
THEN
l_mensaje2 := l_mensaje1 || l_mensaje2;
DBMS_OUTPUT.put_line(l_mensaje2);
ELSE
DBMS_OUTPUT.put_line(l_mensaje1);
END IF;
END;
El bloque despliega “¡Hola Mundo!” cuando la fecha de hoy (retornada por SYSDATE) es por lo menos el
primer día de 2012; en otro caso, únicamente despliega el mensaje “Hola”. Aunque este bloque se ejecute en
2011, asigna memoria para la variable l_mensaje2.
Si reestructuramos este bloque, la memoria para l_mensaje2 será asignada únicamente después del 2011:
DECLARE
l_mensaje1 VARCHAR2(100) := 'Hola';
BEGIN
IF SYSDATE > TO_DATE('01-JAN-2011')
THEN
DECLARE
l_mensaje2 VARCHAR2(100) := ' Mundo!';
BEGIN
l_mensaje2 := l_mensaje1 || l_mensaje2;
DBMS_OUTPUT.put_line(l_mensaje2);
END;
ELSE
DBMS_OUTPUT.put_line(l_mensaje1);
END IF;
END;
De forma similar, podemos agregar una sección de excepciones a este bloque anidado, atrapando errores y
permitiendo que el bloque exterior continúe con su ejecución:
DECLARE
l_mensaje1 VARCHAR2(100) := 'Hola';
BEGIN
DECLARE
l_mensaje2 VARCHAR2(5);
BEGIN
l_mensaje2 := ' Mundo!';
DBMS_OUTPUT.put_line(l_mensaje1 || l_mensaje2);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(DBMS_UTILITY.format_error_stack);
END;
DBMS_OUTPUT.put_line(l_mensaje1);
END;
En este caso, el bloque anidado lanzará una excepción VALUE_ERROR, porque la variable l_mensaje2 es
muy pequeña (máximo de 5 bytes) para el texto “ Mundo!”.La sección de excepciones del bloque anidado
atrapará y desplegará el error. El bloque exterior continuará su ejecución.
Un posterior artículo en estas series se enfocará en cómo funciona el manejo de excepciones en PL/SQL.
Más adelante en esta serie de artículos, mostraré cómo controlar el flujo de ejecución en nuestros bloques:
lógica condicional con IF y CASE; lógica iterativa con FOR, WHILE y LOOP; así como lanzar y manejar
excepciones.