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 %TYPE y %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
  • DECLARE es opcional (sólo si necesitas declarar variables)
  • EXCEPTION es 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 ON es 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 por p_, las constantes por c_ y las variables globales por g_. 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 %TYPE es una buena práctica muy recomendada. Si en el futuro la columna nombre cambia de VARCHAR2(50) a VARCHAR2(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 INTO debe devolver exactamente una fila. Si no devuelve ninguna, se lanza la excepción NO_DATA_FOUND. Si devuelve más de una, se lanza TOO_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