SQL Dentro de PL-SQL
SQL Dentro de PL-SQL
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.
TEMA 12
GENERALIDADES DE PL/SQL
PL/SQL
Generalidades de PL/SQL (I)
Comentarios en PL/SQL:
o Anteponga a los comentarios de una sola línea dos guiones (--) a modo
de prefijo.
o Coloque comentarios de varias líneas entre /* y */.
Operadores de PL/SQL:
**, NOT
+, -
*, /
+, -, ||
AND
OR
PL/SQL
Generalidades de PL/SQL (II)
PL/SQL
Generalidades de PL/SQL (III)
PL/SQL
Generalidades de PL/SQL (IV)
Cuando trabaje con valores nulos puede evitar algunos de los errores más
habituales si recuerda las siguientes reglas:
o Las comparaciones en las que se incluyen valores nulos siempre
resultan NULL.
o Si se aplica el operador lógico NOT a un valor nulo resulta NULL.
o En las sentencias de control condicionales, si la condición resulta NULL,
no se ejecutarán las sentencias asociadas.
PL/SQL
Sentencias SQL en PL/SQL
PL/SQL
Sentencia SELECT
Sintaxis:
Recuerde, sólo se debe de recuperar una fila. Más de una fila provocará errores.
PL/SQL
Ejemplo de SELECT
PL/SQL
Inserción de Datos
PL/SQL
Actualización de Datos
Aumente el salario de todos los empleados de la tabla emp que son Analistas:
PL/SQL
Supresión de Datos
PL/SQL
Control de Transacciones
PL/SQL
Control de Transacciones
PL/SQL
Control Flujo de Ejecución
o IF-THEN
o IF-THEN-ELSE
o IF-THEN-ELSIF
Control de bucles:
PL/SQL
Sentencia IF
Sintaxis:
PL/SQL
Flujo de IF-THEN-ELSE
PL/SQL
Flujo de IF-THEN-ELSIF
PL/SQL
Condiciones Booleanas
PL/SQL
Bucle Básico: LOOP
Sintaxis:
Sintaxis:
PL/SQL
Bucle WHILE
Sintaxis:
PL/SQL
Etiquetas y Loops Anidados
Salga al bucle externo con la sentencia EXIT que hace referencia a la etiqueta.
Ejemplo:
TEMA 11
PL/SQL
PL/SQL
¿Qué es PL/SQL?
PL/SQL
Estructura Bloques Anónimos
[DECLARE]
Define objetos PL/SQL que serán utilizados
dentro del mismo bloque
BEGIN
Sentencias Ejecutables
[EXCEPTION]
Qué hacer si la acción ejecutada causa error
END;
PL/SQL
Estructura de Procedimiento
PROCEDURE nombre IS
Sección Declarativa
BEGIN
Sección Ejecutable
[EXCEPTION]
Sección de Excepciones
END;
PL/SQL
Estructura de Función
PL/SQL
Declaración de Variables
PL/SQL
Asignación e Inicialización de Variables
Asignación:
Inicialización:
PL/SQL
Tipos de Variables
ESCALARES
BOOLEANAS
COMPUESTAS
LOB
DE ENLACE (BIND)
PL/SQL
Variables Escalares
VARCHAR2 (longitud_máxima)
NUMBER [(precisión, escala)]
DATE
CHAR [(longitud_máxima)]
LONG
LONG RAW
BOOLEAN
BINARY_INTEGER
PL/SQL
Atributo %TYPE
PL/SQL
Variables BOOLEANAS
PL/SQL
Tipos de Datos Compuestos
Tipos:
o REGISTROS PL/SQL
o TABLAS PL/SQL
Contienen componentes internos
PL/SQL
Creación de un Registro
Sintaxis:
PL/SQL
Ejemplo de Registro PL/SQL
PL/SQL
El Atributo %ROWTYPE
Define un registro con la estructura de la tabla o vista de la B.D.
Los campos del registro toman sus nombres y tipos de datos de las
columnas de la vista o tabla.
Ejemplo:
DECLARE registro_empleado emp%ROWTYPE;
PL/SQL
Ventajas de %ROWTYPE
PL/SQL
Tablas PL/SQL
PL/SQL
Creación de Tablas PL/SQL
Sintaxis:
Ejemplo:
PL/SQL
Creación de Tablas PL/SQL
PL/SQL
Variables LOB
CLOB Character Large Object. Se utiliza para almacenar bloques grandes de datos de caracteres.
BLOB Binary Large Object. Se utiliza para almacenar objetos binarios grandes en la B.D.
BFILE Binary File. Se utiliza para almacenar objetos binarios grandes en archivos del sistema operativo, f
NCLOB National Language Character Large Object. Se utiliza para almacenar en la B.D. bloques grandes d
PL/SQL
Variables de Enlace (Bind)
Descripción
SQL es un lenguaje de consulta, para los sistemas de bases de datos relacionales, que no posee la
potencia de los lenguajes de programación. No permite el uso de variables, estructuras de control de
flujo, bucles y demás elementos característicos de la programación. No es de extrañar, SQL es un
lenguaje de consulta, no un lenguaje de programación.
Sin embargo, SQL es la herramienta ideal para trabajar con bases de datos. Cuando se desea realizar
una aplicación completa, para el manejo de una base de datos relacional, resulta necesario utilizar
alguna herramienta que soporte la capacidad de consulta del SQL y la versatilidad de los lenguajes de
programación tradicionales. PL/SQL es el lenguaje de programación que proporciona Oracle para
extender el SQL estándar con otro tipo de instrucciones y elementos propios de los lenguajes de
programación.
Con PL/SQL vamos a poder programar las unidades de programa de la base de datos Oracle:
Procedimientos almacenados
Funciones
Triggers
Scripts
Pero además, PL/SQL nos permite realizar programas sobre las siguientes herramientas de Oracle:
Oracle Forms
Oracle Reports
Oracle Graphics
Oracle Aplication Server
Conceptos básicos
Como introducción vamos a ver algunos elementos y conceptos básicos del lenguaje. PL/SQL no es
CASE-SENSITIVE, es decir, no diferencia mayúsculas de minúsculas como otros lenguajes de
programación como C o Java. Sin embargo, debemos recordar que Oracle es CASE-SENSITIVE en las
búsquedas de texto.
Una línea en PL/SQL contiene grupos de caracteres, conocidos como UNIDADES LÉXICAS, que pueden
ser clasificadas como: DELIMITADORES, IDENTIFICADORES, LITERALES, COMENTARIOS o
EXPRESIONES.
DELIMITADOR: Es un símbolo, simple o compuesto, que tiene una función especial en PL/SQL:
o Operadores Aritméticos
o Operadores Lógicos
o Operadores Relacionales
IDENTIFICADOR: Son empleados para nombrar objetos de programas en PL/SQL, así como a
unidades dentro del mismo:
o Constantes
o Cursores
o Variables
o Subprogramas
o Excepciones
o Paquetes
LITERAL: Es un valor de tipo numérico, carácter, cadena o lógico no representado por un
identificador (es un valor explícito).
COMENTARIO: Es una aclaración que el programador incluye en el código. Son soportados dos estilos
de comentarios, el de línea simple y de multilínea, para lo cual son empleados ciertos caracteres
especiales.
Tipos de datos
Cada constante y variable tiene un tipo de dato en el que se especifica el formato de almacenamiento,
restricciones y rango de valores válidos. PL/SQL proporciona una variedad predefinida de tipos de
datos. Casi todos los tipos de datos manejados por PL/SQL son similares a los soportados por SQL. A
continuación se muestran los tipos de datos más comunes:
Tablas de PL/SQL
Las tablas de PL/SQL son tipos de datos que nos permiten almacenar varios valores del mismo tipo de
dato. Una tabla PL/SQL, que es similar a un array, tiene dos componentes:
Un índice de tipo BINARY_INTEGER que permite acceder a los elementos en la tabla PL/SQL.
Una columna de escalares o registros que contiene los valores de la tabla PL/SQL.
Puede incrementar su tamaño dinámicamente. La sintaxis general para declarar una tabla de PL es la
siguiente:
TYPE <nombre_tipo_tabla> IS TABLE OF
<tipo_datos>[NOT NULL]
INDEX BY BINARY_INTEGER ;
Una vez que hemos definido el tipo, podemos declarar variables y asignarle valores.
DECLARE /* Definimos el tipo PAISES como tabla PL/SQL */
TYPE PAISES IS TABLE OF NUMBER INDEX BY BINARY_INTEGER ;
/* Declaramos una variable del tipo PAISES */
tPAISES PAISES;
BEGIN
tPAISES(1) := 1;
tPAISES(2) := 2;
tPAISES(3) := 3;
END;
No es posible inicializar las tablas en la declaración. El rango de binary integer es –2147483647..
2147483647, por lo tanto el índice puede ser negativo, lo cual indica que el índice del primer valor no
tiene que ser necesariamente el cero.
Estructuras
Estas son las diversas estructuras que se manejan en PL/SQL:
Sentencia GOTO
PL/SQL dispone de la sentencia GOTO. La sentencia GOTO desvía el flujo de ejecución a una
determinada etiqueta. En PL/SQL las etiquetas se indican del siguiente modo: << etiqueta >>. El
siguiente ejemplo ilustra el uso de GOTO:
DECLARE
flag NUMBER;
BEGIN
flag :=1 ;
IF (flag = 1) THEN
GOTO paso2;
END IF;
<<paso1>>
dbms_output.put_line('Ejecucion de paso 1');
<<paso2>>
dbms_output.put_line('Ejecucion de paso 2');
END;
Bucles
En PL/SQL tenemos a nuestra disposición los siguientes iteradores o bucles: LOOP, WHILE, FOR. El
bucle LOOP se repite tantas veces como sea necesario hasta que se fuerza su salida con la instrucción
EXIT. Su sintaxis es la siguiente:
LOOP
-- Instrucciones
IF (expresión) THEN
-- Instrucciones
EXIT;
END IF;
END LOOP;
El bucle WHILE, se repite mientras que se cumpla la expresión:
WHILE (expresión) LOOP
-- Instrucciones
END LOOP;
El bucle FOR se repite tanta veces como le indiquemos en los identificadores inicio y final:
FOR contador IN [REVERSE] inicio..final LOOP
-- Instrucciones
END LOOP;
En el caso de especificar REVERSE el bucle se recorre en sentido inverso.
Bloques PL/SQL
Un programa de PL/SQL está compuesto por bloques, como mínimo de uno. Los bloques de PL/SQL
pueden ser de los siguientes tipos:
Bloques anónimos.
Subprogramas.
Estructura de un Bloque
Los bloques PL/SQL presentan una estructura específica compuesta de tres partes bien diferenciadas:
La sección declarativa, donde se declaran todas las constantes y variables que se van a utilizar en la
ejecución del bloque.
La sección de ejecución, que incluye las instrucciones a ejecutar en el bloque PL/SQL.
La sección de excepciones, en donde se definen los manejadores de errores que soportará el bloque
PL/SQL.
Cada una de las partes anteriores se delimita por una palabra reservada, de modo que un bloque
PL/SQL se puede representar como sigue:
[declare | is | as ] /*Parte declarativa*/
begin /*Parte de ejecucion*/
[ exception ] /*Parte de excepciones*/
end;
De las anteriores partes, únicamente la sección de ejecución es obligatoria, que quedaría delimitada
entre las cláusulas BEGIN y END. Veamos un ejemplo de bloque PL/SQL muy genérico. Se trata de un
bloque anónimo, es decir, no lo identifica ningún nombre. Los bloques anónimos identifican su parte
declarativa con la palabra reservada DECLARE.
DECLARE
/*Parte declarativa*/
nombre_variable DATE;
BEGIN
/*Parte de ejecución
* Este código asigna el valor de la columna "nombre_columna"
* a la variable identificada por "nombre_variable"
*/
SELECT SYSDATE INTO nombre_variable FROM DUAL;
EXCEPTION
/*Parte de excepciones*/
WHEN OTHERS THEN
dbms_output.put_line('Se ha producido un error');
END;
Cursores en PL/SQL
PL/SQL utiliza cursores para gestionar las instrucciones SELECT. Un cursor es un conjunto de registros
devuelto por una instrucción SQL. Técnicamente, los cursores son fragmentos de memoria reservados
para procesar los resultados de una consulta SELECT. Podemos distinguir dos tipos de cursores:
Cursores implícitos. Este tipo de cursores se utiliza para operaciones SELECT INTO. Se usan cuando la
consulta devuelve un único registro.
Cursores explícitos. Son los cursores que son declarados y controlados por el programador. Se utilizan
cuando la consulta devuelve un conjunto de registros. Ocasionalmente también se utilizan en
consultas que devuelven un único registro por razones de eficiencia. Son más rápidos.
Un cursor se define como cualquier otra variable de PL/SQL y debe nombrarse de acuerdo a los
mismos convenios que cualquier otra variable. Los cursores implícitos no necesitan declaración. El
siguiente ejemplo declara un cursor explícito:
declare
cursor c_paises is
SELECT CO_PAIS, DESCRIPCION
FROM PAISES;
begin
/* Sentencias del bloque ...*/
end;
Para procesar instrucciones SELECT que devuelvan más de una fila, son necesarios cursores explícitos
combinados con un estructura de bloque. Un cursor admite el uso de parámetros. Los parámetros
deben declararse junto con el cursor. El siguiente ejemplo muestra la declaración de un cursor con un
parámetro, identificado por p_continente.
declare
cursor c_paises (p_continente IN VARCHAR2) is
SELECT CO_PAIS, DESCRIPCION
FROM PAISES
WHERE CONTINENTE = p_continente;
begin
/* Sentencias del bloque ...*/
end;
Cursores implícitos
Los cursores implícitos se utilizan para realizar consultas SELECT que devuelven un único registro.
Deben tenerse en cuenta los siguientes puntos cuando se utilizan cursores implícitos:
Excepción Explicación
Dado que cada cursor implícito sólo es capaz de recuperar una fila , esta
TOO_MANY_ROWS
excepción detecta la existencia de más de una fila.
Cursores explícitos
Los cursores explícitos se emplean para realizar consultas SELECT que pueden devolver cero filas o
más de una fila. Para trabajar con un cursor explícito necesitamos realizar las siguientes tareas:
Declarar el cursor.
Abrir el cursor con la instrucción OPEN.
Leer los datos del cursor con la instrucción FETCH.
Cerrar el cursor y liberar los recursos con la instrucción CLOSE
A continuación se muestra un ejemplo del empleo de cursores:
CURSOR nombre_cursor IS
instrucción_SELECT // Se declara el cursor
Manejo de excepciones
En PL/SQL una advertencia o condición de error se llama excepción. Las excepciones se controlan
dentro de su propio bloque, cuya estructura se muestra a continuación:
DECLARE
-- Declaraciones
BEGIN
-- Ejecución
EXCEPTION
-- Excepción
END;
Cuando ocurre un error, se ejecuta la porción del programa marcada por el bloque EXCEPTION,
transfiriéndose el control a ese bloque de sentencias. El siguiente ejemplo muestra un bloque de
excepciones que captura las excepciones NO_DATA_FOUND y ZERO_DIVIDE. Cualquier otra excepción
será capturada en el bloque WHEN OTHERS THEN.
DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Se ejecuta cuando ocurre una excepción de tipo NO_DATA_FOUND
WHEN ZERO_DIVIDE THEN
-- Se ejecuta cuando ocurre una excepción de tipo ZERO_DIVIDE
WHEN OTHERS THEN
-- Se ejecuta cuando ocurre una excepción de un tipo no tratado
-- en los bloques anteriores
END;
Como se ha indicado, cuando ocurre un error se ejecuta el bloque EXCEPTION, transfiriéndose el
control a las sentencias del bloque. Una vez finalizada la ejecución del bloque de EXCEPTION no se
continúa ejecutando el bloque anterior. Si existe un bloque de excepción apropiado para el tipo de
excepción se ejecuta dicho bloque. Si no existe un bloque de control de excepciones adecuado al tipo
de excepción se ejecutará el bloque de excepción WHEN OTHERS THEN (¡si existe!). WHEN OTHERS
debe ser el último manejador de excepciones.
Las excepciones pueden ser definidas de forma interna o explícitamente por el usuario. Ejemplos de
excepciones definidas de forma interna son la división por cero y la falta de memoria en tiempo de
ejecución. Estas mismas condiciones excepcionales tienen sus propio tipos y pueden ser referenciadas
por ellos: ZERO_DIVIDE y STORAGE_ERROR. Las excepciones definidas por el usuario deben ser
alcanzadas explícitamente utilizando la sentencia RAISE.
Con las excepciones se pueden manejar los errores cómodamente sin necesidad de mantener
múltiples chequeos por cada sentencia escrita. También provee claridad en el código ya que permite
mantener las rutinas correspondientes al tratamiento de los errores de forma separada de la lógica del
negocio.
Excepciones predefinidas
PL/SQL proporciona un gran número de excepciones predefinidas que permiten controlar las
condiciones de error más habituales. Las excepciones predefinidas no necesitan ser declaradas,
simplemente se utilizan cuando éstas son lanzadas por algún error determinado. La siguiente lista
muestra las excepciones predeterminadas por PL/SQL y una breve descripción de cuándo son
accionadas:
Excepción Descripción
TOO_MANY_ROWS Una sentencia SELECT INTO devuelve más de una fila -1422.
Reglas de alcance
Una excepción es válida dentro de su ambito de alcance, es decir, el bloque o programa donde ha sido
declarada. Las excepciones predefinidas son siempre válidas. Como las variables, una excepción
declarada en un bloque es local a ese bloque y global a todos los subbloques que comprende.
La sentencia RAISE
La sentencia RAISE permite lanzar una excepción en forma explícita. Es posible utilizar esta sentencia
en cualquier lugar que se encuentre dentro del alcance de la excepción.
DECLARE
-- Declaramos una excepción identificada por VALOR_NEGATIVO
VALOR_NEGATIVO EXCEPTION;
valor NUMBER;
BEGIN
-- Ejecución valor := -1;
IF valor < 0 THEN
RAISE VALOR_NEGATIVO;
END IF;
EXCEPTION -- Excepción
WHEN VALOR_NEGATIVO THEN
dbms_output.put_line('El valor no puede ser negativo');
END;
Con la sentencia RAISE podemos lanzar una excepción definida por el usuario o predefinida, siendo el
comportamiento habitual lanzar excepciones definidas por el usuario.
Propagación de excepciones
Una de las características más interesantes de la excepciones es su propagación. Cuando se lanza una
excepción, el control se transfiere hasta la sección EXCEPTION del bloque donde se ha producido la
excepción. Entonces se busca un manejador válido de la excepción (WHEN THEN, WHEN OTHERS
THEN) dentro del bloque actual. En el caso de que no se encuentre ningún manejador válido, el
control del programa se desplaza hasta el bloque EXCEPTION del bloque que ha realizado la llamada
PL/SQL.
Subprogramas en PL/SQL
Como hemos visto anteriormente, los bloques de PL/SQL pueden ser bloques anónimos (scripts) y
subprogramas. Los subprogramas son bloques de PL/SQL a los que asignamos un nombre
identificativo y que normalmente almacenamos en la propia base de datos para su posterior ejecución.
Los subprogramas pueden recibir parámetros. Los subprogramas pueden ser de varios tipos:
Procedimientos almacenados.
Funciones.
Triggers.
Subprogramas en bloques anónimos.
Triggers
Un trigger es un bloque PL/SQL asociado a una tabla, que se ejecuta como consecuencia de una
determinada instrucción SQL (una operación DML: INSERT, UPDATE o DELETE) sobre dicha tabla. La
sintaxis para crear un trigger es la siguiente:
CREATE [OR REPLACE] TRIGGER <nombre_trigger>
{BEFORE|AFTER}
{DELETE|INSERT|UPDATE [OF col1, col2, ..., colN]
[OR {DELETE|INSERT|UPDATE [OF col1, col2, ..., colN]...]}
ON <nombre_tabla>
[FOR EACH ROW [WHEN (<condicion>)]]
DECLARE
-- variables locales
BEGIN
-- Sentencias
[EXCEPTION]
-- Sentencias control de excepcion
END <nombre_trigger>;
El uso de OR REPLACE permite sobreescribir un trigger existente. Si se omite y el trigger existe se
producirá un error. Los triggers pueden definirse para las operaciones INSERT, UPDATE o DELETE, y
pueden ejecutarse antes o después de la operación. El modificador BEFORE AFTER indica que el
trigger se ejecutará antes o después de ejecutarse la sentencia SQL definida por DELETE, INSERT,
UPDATE. Si incluimos el modificador OF el trigger, solo se ejecutará cuando la sentencia SQL afecte a
los campos incluidos en la lista.
El alcance de los disparadores puede ser la fila o de orden. El modificador FOR EACH ROW indica que
el trigger se disparará cada vez que se realicen operaciones sobre una fila de la tabla. Si se acompaña
del modificador WHEN, se establece una restricción; el trigger solo actuará, sobre las filas que
satisfagan la restricción. La siguiente tabla resume los contenidos anteriores.
Valor Descripción
INSERT,
DELETE, Define qué tipo de orden DML provoca la activación del disparador.
UPDATE
BEFORE,
Define si el disparador se activa antes o después de que se ejecute la orden.
AFTER
Los disparadores con nivel de fila se activan una vez por cada fila afectada por
FOR EACH orden que provocó el disparo. Los disparadores con nivel de orden se activan s
ROW una vez, antes o después de la orden. Los disparadores con nivel de fila se
identifican por la cláusula FOR EACH ROW en la definición del disparador
La cláusula WHEN sólo es válida para los disparadores con nivel de fila.
Procedimiento
Un procedimiento es un subprograma que ejecuta una acción especifica y que no devuelve ningún
valor. Un procedimiento tiene un nombre, un conjunto de parámetros (opcional) y un bloque de
código. La sintaxis de un procedimiento almacenado es la siguiente:
CREATE [OR REPLACE]
PROCEDURE <procedure_name> [(<param1> [IN|OUT|IN OUT] <type>,
<param2> [IN|OUT|IN OUT] <type>, ...)]
IS
-- Declaración de variables locales
BEGIN
-- Sentencias
[EXCEPTION]
-- Sentencias control de excepción
END [<procedure_name>];
El uso de OR REPLACE permite sobreescribir un procedimiento existente. Si se omite, y el
procedimiento existe, se producirá un error. La sintaxis es muy parecida a la de un bloque anónimo,
salvo porque se reemplaza la sección DECLARE por la secuencia PROCEDURE … IS y en la
especificación del procedimiento debemos especificar el tipo de datos de cada parámetro. Al
especificar el tipo de dato del parámetro no debemos especificar la longitud del tipo. Los parámetros
pueden ser de entrada (IN), de salida (OUT) o de entrada salida (IN OUT). El valor por defecto es IN,
y se toma ese valor en caso de que no especifiquemos nada.
CREATE OR REPLACE
PROCEDURE Actualiza_Saldo(cuenta NUMBER,
new_saldo NUMBER)
IS
-- Declaración de variables locales
BEGIN
-- Sentencias
UPDATE SALDOS_CUENTAS
SET SALDO = new_saldo,
FX_ACTUALIZACION = SYSDATE
WHERE CO_CUENTA = cuenta;
END Actualiza_Saldo;
Función
Una función es un subprograma que devuelve un valor. La sintaxis para construir funciones es la
siguiente:
CREATE [OR REPLACE]
FUNCTION <fn_name>[(<param1> IN <type>, <param2> IN <type>, ...)]
RETURN
<return_type>
IS
result <return_type>;
BEGIN return(result);
[EXCEPTION]
-- Sentencias control de excepción
END [<fn_name>];
El uso de OR REPLACE permite sobreescribir una función existente. Si se omite, y la función existe, se
producirá un error. La sintaxis de los parámetros es la misma que en los procedimientos almacenados,
exceptuando que sólo pueden ser de entrada.