Módulo 01: Estructura de un Bloque Anónimo y Declaración de Variables
Objetivos del módulo
- Comprender la estructura de un bloque anónimo PL/SQL
- Conocer las secciones DECLARE, BEGIN, EXCEPTION y END
- Declarar y utilizar variables y constantes
- Dominar los tipos de datos escalares más comunes
- Usar los atributos
%TYPEy%ROWTYPE - Mostrar información por pantalla con
DBMS_OUTPUT.PUT_LINE - Entender el ámbito y la visibilidad de las variables
1. Estructura de un bloque PL/SQL
Todo programa PL/SQL se organiza en bloques. Un bloque anónimo (sin nombre) tiene la siguiente estructura:
DECLARE
-- Sección de declaraciones (OPCIONAL)
-- Aquí se declaran variables, constantes, cursores, excepciones...
BEGIN
-- Sección ejecutable (OBLIGATORIA)
-- Aquí va el código que se ejecuta: sentencias SQL, lógica, etc.
EXCEPTION
-- Sección de manejo de excepciones (OPCIONAL)
-- Aquí se capturan y gestionan los errores
END;
/
📘 Concepto: El carácter
/al final del bloque es el terminador que le indica a SQL*Plus, SQLcl o SQL Developer que debe ejecutar el bloque PL/SQL. Sin él, el bloque no se ejecuta.
Reglas fundamentales
- La sección
BEGIN...END;es la única obligatoria DECLAREes opcional (sólo si necesitas declarar variables)EXCEPTIONes opcional (sólo si necesitas gestionar errores)- Todas las sentencias terminan con punto y coma
; - Los bloques se pueden anidar (un bloque dentro de otro)
El bloque más simple posible
BEGIN
NULL; -- No hace nada, pero es un bloque PL/SQL válido
END;
/
2. Habilitar la salida por pantalla
Antes de poder ver mensajes en la consola, necesitas activar el buffer de salida:
-- Activar la salida por pantalla
SET SERVEROUTPUT ON;
⚠️ Importante:
SET SERVEROUTPUT ONes un comando de SQL*Plus/SQLcl, no de PL/SQL. Debe ejecutarse antes del bloque, no dentro de él. En SQL Developer suele estar activado por defecto.
Tu primer programa: ¡Hola Mundo!
SET SERVEROUTPUT ON;
BEGIN
DBMS_OUTPUT.PUT_LINE('¡Hola Mundo desde PL/SQL!');
END;
/
Resultado:
¡Hola Mundo desde PL/SQL!
Opciones de DBMS_OUTPUT
BEGIN
-- Escribe texto CON salto de línea al final
DBMS_OUTPUT.PUT_LINE('Línea completa');
-- Escribe texto SIN salto de línea (se acumula)
DBMS_OUTPUT.PUT('Parte 1 ');
DBMS_OUTPUT.PUT('Parte 2 ');
-- Fuerza el salto de línea del texto acumulado
DBMS_OUTPUT.NEW_LINE;
END;
/
3. Declaración de variables
Las variables se declaran en la sección DECLARE con la siguiente sintaxis:
nombre_variable tipo_de_dato [NOT NULL] [:= valor_inicial];
3.1. Tipos de datos escalares
Los tipos más utilizados en PL/SQL son:
| Tipo | Descripción | Ejemplo |
|---|---|---|
VARCHAR2(n) | Cadena de caracteres de longitud variable (máx. 32.767 en PL/SQL) | 'Hola' |
NUMBER(p,s) | Número con precisión p y escala s | 1234.56 |
INTEGER | Número entero (equivale a NUMBER(38,0)) | 42 |
DATE | Fecha y hora | SYSDATE |
BOOLEAN | Verdadero, falso o nulo (solo en PL/SQL, no en SQL) | TRUE |
CHAR(n) | Cadena de longitud fija | 'A' |
PLS_INTEGER | Entero con mejor rendimiento que NUMBER para operaciones | 100 |
BINARY_FLOAT | Número de punto flotante de precisión simple | 3.14f |
BINARY_DOUBLE | Número de punto flotante de precisión doble | 3.14d |
3.2. Ejemplo básico de variables
DECLARE
v_nombre VARCHAR2(50) := 'Juan Pérez';
v_edad NUMBER(3) := 30;
v_salario NUMBER(8,2) := 2500.50;
v_fecha_alta DATE := SYSDATE;
v_activo BOOLEAN := TRUE;
BEGIN
DBMS_OUTPUT.PUT_LINE('Nombre: ' || v_nombre);
DBMS_OUTPUT.PUT_LINE('Edad: ' || v_edad);
DBMS_OUTPUT.PUT_LINE('Salario: ' || v_salario);
DBMS_OUTPUT.PUT_LINE('Fecha: ' || TO_CHAR(v_fecha_alta, 'DD/MM/YYYY'));
-- Nota: BOOLEAN no se puede mostrar directamente con PUT_LINE
IF v_activo THEN
DBMS_OUTPUT.PUT_LINE('Activo: Sí');
ELSE
DBMS_OUTPUT.PUT_LINE('Activo: No');
END IF;
END;
/
📘 Concepto: Por convención, los nombres de variables locales suelen empezar por
v_, los parámetros porp_, las constantes porc_y las variables globales porg_. Esto facilita distinguirlas en el código.
3.3. NOT NULL y valores por defecto
DECLARE
-- Variable que NO puede ser NULL (obligatorio asignar valor inicial)
v_codigo NUMBER(5) NOT NULL := 1;
-- Usar DEFAULT es equivalente a :=
v_pais VARCHAR2(30) DEFAULT 'España';
BEGIN
DBMS_OUTPUT.PUT_LINE('Código: ' || v_codigo);
DBMS_OUTPUT.PUT_LINE('País: ' || v_pais);
END;
/
⚠️ Importante: Si declaras una variable con
NOT NULL, debes darle un valor inicial. Si no lo haces, Oracle generará un error de compilación.
4. Constantes
Las constantes se declaran con la palabra clave CONSTANT. Una vez asignado su valor, no se puede cambiar:
DECLARE
c_iva CONSTANT NUMBER(4,2) := 21.00;
c_empresa CONSTANT VARCHAR2(50) := 'Mi Empresa S.L.';
c_max_items CONSTANT PLS_INTEGER := 100;
BEGIN
DBMS_OUTPUT.PUT_LINE('IVA: ' || c_iva || '%');
DBMS_OUTPUT.PUT_LINE('Empresa: ' || c_empresa);
DBMS_OUTPUT.PUT_LINE('Máximo items: ' || c_max_items);
-- c_iva := 10; -- ERROR: no se puede modificar una constante
END;
/
5. Atributos %TYPE y %ROWTYPE
5.1. %TYPE — Heredar el tipo de una columna
%TYPE permite que una variable tenga el mismo tipo que una columna de la base de datos. Si el tipo de la columna cambia, la variable se adapta automáticamente:
DECLARE
v_nombre conductor.nombre%TYPE;
v_fecha conductor.fec_nacimiento%TYPE;
v_importe infraccion.valor_economico%TYPE;
BEGIN
-- Asignar un valor de la BD a la variable
SELECT nombre INTO v_nombre
FROM conductor
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE('Conductor: ' || v_nombre);
END;
/
💡 Truco: Usar
%TYPEes una buena práctica muy recomendada. Si en el futuro la columnanombrecambia deVARCHAR2(50)aVARCHAR2(100), tu variable PL/SQL se adaptará automáticamente sin tener que modificar el código.
5.2. %ROWTYPE — Heredar la estructura de una fila completa
%ROWTYPE crea una variable tipo registro con todos los campos de una tabla o vista:
DECLARE
r_conductor conductor%ROWTYPE;
BEGIN
SELECT * INTO r_conductor
FROM conductor
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE('Nombre: ' || r_conductor.nombre);
DBMS_OUTPUT.PUT_LINE('Nacimiento: ' || r_conductor.fec_nacimiento);
DBMS_OUTPUT.PUT_LINE('Carnet: ' || r_conductor.fec_carnet);
END;
/
📘 Concepto: Accedemos a cada campo del registro usando la notación de punto:
r_conductor.nombre,r_conductor.fec_nacimiento, etc. Es como acceder a las columnas de una fila.
6. Asignación de valores
Existen varias formas de asignar valores a las variables:
6.1. Asignación directa con :=
DECLARE
v_contador NUMBER := 0;
BEGIN
v_contador := v_contador + 1;
v_contador := v_contador * 10;
DBMS_OUTPUT.PUT_LINE('Contador: ' || v_contador); -- Resultado: 10
END;
/
6.2. Asignación con SELECT INTO
DECLARE
v_total_multas NUMBER;
v_nombre_conductor conductor.nombre%TYPE;
BEGIN
-- Asignar un valor agregado
SELECT COUNT(*) INTO v_total_multas FROM multa;
-- Asignar un valor de un registro concreto
SELECT nombre INTO v_nombre_conductor
FROM conductor
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE('Total multas: ' || v_total_multas);
DBMS_OUTPUT.PUT_LINE('Conductor: ' || v_nombre_conductor);
END;
/
⚠️ Importante:
SELECT INTOdebe devolver exactamente una fila. Si no devuelve ninguna, se lanza la excepciónNO_DATA_FOUND. Si devuelve más de una, se lanzaTOO_MANY_ROWS. Veremos cómo gestionar esto en el módulo de Excepciones.
6.3. Múltiples asignaciones en un solo SELECT
DECLARE
v_max_importe infraccion.valor_economico%TYPE;
v_min_importe infraccion.valor_economico%TYPE;
v_num_infr NUMBER;
BEGIN
SELECT MAX(valor_economico), MIN(valor_economico), COUNT(*)
INTO v_max_importe, v_min_importe, v_num_infr
FROM infraccion;
DBMS_OUTPUT.PUT_LINE('Importe máximo: ' || v_max_importe);
DBMS_OUTPUT.PUT_LINE('Importe mínimo: ' || v_min_importe);
DBMS_OUTPUT.PUT_LINE('Infracciones: ' || v_num_infr);
END;
/
7. Ámbito y visibilidad de variables
Cuando se anidan bloques, cada bloque tiene su propio ámbito:
DECLARE
v_global VARCHAR2(20) := 'Global';
BEGIN
DBMS_OUTPUT.PUT_LINE('Bloque externo: ' || v_global);
-- Bloque interno (anidado)
DECLARE
v_local VARCHAR2(20) := 'Local';
BEGIN
-- Aquí se ven ambas variables
DBMS_OUTPUT.PUT_LINE('Bloque interno: ' || v_global); -- OK
DBMS_OUTPUT.PUT_LINE('Bloque interno: ' || v_local); -- OK
END;
-- Aquí v_local ya NO existe
-- DBMS_OUTPUT.PUT_LINE(v_local); -- ERROR: variable no definida
DBMS_OUTPUT.PUT_LINE('De vuelta al externo: ' || v_global);
END;
/
📘 Concepto: Las variables del bloque externo son visibles en los bloques internos, pero no al revés. Es como las reglas de ámbito (scope) de cualquier lenguaje de programación: una variable solo existe donde fue declarada y en los bloques que contiene.
8. Tipos de datos compuestos (introducción)
Además de los tipos escalares, PL/SQL permite definir tipos compuestos:
8.1. Registros personalizados (TYPE…RECORD)
DECLARE
-- Definir un tipo registro personalizado
TYPE t_empleado IS RECORD (
nombre VARCHAR2(50),
apellido VARCHAR2(50),
salario NUMBER(8,2),
fecha_alta DATE
);
-- Declarar una variable de ese tipo
v_emp t_empleado;
BEGIN
v_emp.nombre := 'María';
v_emp.apellido := 'García';
v_emp.salario := 3200.00;
v_emp.fecha_alta := SYSDATE;
DBMS_OUTPUT.PUT_LINE(v_emp.nombre || ' ' || v_emp.apellido ||
' - Salario: ' || v_emp.salario);
END;
/
8.2. Tablas PL/SQL (colecciones)
DECLARE
-- Tabla indexada por enteros
TYPE t_nombres IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
v_lista t_nombres;
BEGIN
v_lista(1) := 'Ana';
v_lista(2) := 'Pedro';
v_lista(3) := 'Luis';
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE('Elemento ' || i || ': ' || v_lista(i));
END LOOP;
END;
/
9. Ejercicios prácticos
Ejercicio 1: Variables básicas
Declara variables para almacenar tu nombre, edad y si eres estudiante. Muéstralas por pantalla:
SET SERVEROUTPUT ON;
DECLARE
v_nombre VARCHAR2(100) := 'Tu Nombre';
v_edad NUMBER(3) := 25;
v_estudiante BOOLEAN := TRUE;
BEGIN
DBMS_OUTPUT.PUT_LINE('Nombre: ' || v_nombre);
DBMS_OUTPUT.PUT_LINE('Edad: ' || v_edad);
IF v_estudiante THEN
DBMS_OUTPUT.PUT_LINE('Es estudiante: Sí');
ELSE
DBMS_OUTPUT.PUT_LINE('Es estudiante: No');
END IF;
END;
/
Ejercicio 2: Usando %TYPE y SELECT INTO
Consulta el nombre y la fecha de carnet del primer conductor de la tabla y muéstralos formateados:
DECLARE
v_nombre conductor.nombre%TYPE;
v_carnet conductor.fec_carnet%TYPE;
BEGIN
SELECT nombre, fec_carnet
INTO v_nombre, v_carnet
FROM conductor
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE('Conductor: ' || v_nombre);
DBMS_OUTPUT.PUT_LINE('Carnet desde: ' || TO_CHAR(v_carnet, 'DD "de" Month "de" YYYY'));
END;
/
Ejercicio 3: Registro con %ROWTYPE
Obtén toda la información de una infracción y muestra su descripción, puntos y valor económico:
DECLARE
r_infraccion infraccion%ROWTYPE;
BEGIN
SELECT * INTO r_infraccion
FROM infraccion
WHERE ROWNUM = 1;
DBMS_OUTPUT.PUT_LINE('Infracción: ' || r_infraccion.descripcion);
DBMS_OUTPUT.PUT_LINE('Puntos: ' || r_infraccion.puntos);
DBMS_OUTPUT.PUT_LINE('Importe: ' || r_infraccion.valor_economico || ' €');
END;
/
Ejercicio 4: Cálculos con variables
Calcula el IVA (21%) de un importe base y muestra el desglose:
DECLARE
c_iva CONSTANT NUMBER(4,2) := 21.00;
v_base NUMBER(10,2) := 1500.00;
v_iva NUMBER(10,2);
v_total NUMBER(10,2);
BEGIN
v_iva := v_base * c_iva / 100;
v_total := v_base + v_iva;
DBMS_OUTPUT.PUT_LINE('Base: ' || TO_CHAR(v_base, '99G999D00') || ' €');
DBMS_OUTPUT.PUT_LINE('IVA: ' || TO_CHAR(v_iva, '99G999D00') || ' €');
DBMS_OUTPUT.PUT_LINE('Total: ' || TO_CHAR(v_total, '99G999D00') || ' €');
END;
/
Resumen
| Concepto | Detalle |
|---|---|
| Bloque anónimo | Estructura DECLARE (opcional) / BEGIN (obligatorio) / EXCEPTION (opcional) / END; |
| Terminador | El carácter / ejecuta el bloque en SQL*Plus/SQLcl |
| DBMS_OUTPUT.PUT_LINE | Muestra texto por la consola (requiere SET SERVEROUTPUT ON) |
| Variables | Se declaran en DECLARE con sintaxis nombre tipo [:= valor]; |
| Constantes | Se declaran con CONSTANT y no pueden cambiar de valor |
| NOT NULL | Obliga a que la variable siempre tenga un valor (requiere inicialización) |
| %TYPE | Hereda el tipo de datos de una columna de la BD |
| %ROWTYPE | Hereda la estructura completa de una tabla o vista |
| SELECT INTO | Asigna el resultado de una consulta a variables PL/SQL |
| Ámbito | Las variables son visibles en su bloque y en los bloques internos |
| Registros (RECORD) | Tipos compuestos personalizados con múltiples campos |
| Tablas PL/SQL | Colecciones indexadas para almacenar múltiples valores |