Módulo 02: Estructuras de Control de Flujo

Objetivos del módulo

  • Utilizar la estructura condicional IF-THEN-ELSIF-ELSE
  • Dominar la sentencia CASE (simple y buscada)
  • Escribir bucles con LOOP, WHILE y FOR
  • Controlar el flujo de los bucles con EXIT, EXIT WHEN y CONTINUE
  • Anidar estructuras de control de forma correcta
  • Aplicar buenas prácticas en la escritura de flujos de control

1. Estructura condicional IF

La sentencia IF permite ejecutar código de forma condicional. Tiene varias formas.

1.1. IF-THEN (forma básica)

DECLARE
    v_nota NUMBER := 7;
BEGIN
    IF v_nota >= 5 THEN
        DBMS_OUTPUT.PUT_LINE('Aprobado');
    END IF;
END;
/

1.2. IF-THEN-ELSE

DECLARE
    v_nota NUMBER := 3;
BEGIN
    IF v_nota >= 5 THEN
        DBMS_OUTPUT.PUT_LINE('Aprobado');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Suspenso');
    END IF;
END;
/

1.3. IF-THEN-ELSIF-ELSE

Para evaluar múltiples condiciones en cascada:

DECLARE
    v_nota NUMBER := 8;
BEGIN
    IF v_nota >= 9 THEN
        DBMS_OUTPUT.PUT_LINE('Sobresaliente');
    ELSIF v_nota >= 7 THEN
        DBMS_OUTPUT.PUT_LINE('Notable');
    ELSIF v_nota >= 5 THEN
        DBMS_OUTPUT.PUT_LINE('Aprobado');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Suspenso');
    END IF;
END;
/

⚠️ Importante: Se escribe ELSIF (sin la segunda ‘E’), no ELSEIF ni ELSE IF. Es uno de los errores más comunes al empezar con PL/SQL.

1.4. IF anidados

Se pueden anidar varios IF, pero es preferible usar ELSIF cuando sea posible para mejorar la legibilidad:

DECLARE
    v_edad    NUMBER := 25;
    v_carnet  BOOLEAN := TRUE;
BEGIN
    IF v_edad >= 18 THEN
        IF v_carnet THEN
            DBMS_OUTPUT.PUT_LINE('Puede conducir');
        ELSE
            DBMS_OUTPUT.PUT_LINE('Mayor de edad pero sin carnet');
        END IF;
    ELSE
        DBMS_OUTPUT.PUT_LINE('Menor de edad, no puede conducir');
    END IF;
END;
/

1.5. Operadores de comparación y lógicos

Operador Descripción Ejemplo
= Igual a v_edad = 18
<> o != Distinto de v_estado <> 'A'
>, < Mayor, menor que v_nota > 5
>=, <= Mayor o igual, menor o igual v_salario >= 1000
AND Y lógico v_edad >= 18 AND v_carnet = TRUE
OR O lógico v_dia = 'SAB' OR v_dia = 'DOM'
NOT Negación NOT v_activo
IS NULL Es nulo v_color IS NULL
IS NOT NULL No es nulo v_nombre IS NOT NULL
BETWEEN Rango v_nota BETWEEN 5 AND 10
IN Lista de valores v_dia IN ('LUN', 'MAR', 'MIE')
LIKE Patrón v_nombre LIKE 'A%'

📘 Concepto: En PL/SQL, la lógica es ternaria: los valores pueden ser TRUE, FALSE o NULL. Cualquier comparación con NULL da como resultado NULL (ni verdadero ni falso). Por eso se usa IS NULL y no = NULL.


2. Sentencia CASE

CASE es una alternativa más elegante a las cadenas de IF-ELSIF cuando se comparan valores de una misma expresión.

2.1. CASE simple (comparación de igualdad)

DECLARE
    v_dia VARCHAR2(10) := 'LUN';
    v_tipo VARCHAR2(20);
BEGIN
    v_tipo := CASE v_dia
        WHEN 'LUN' THEN 'Laborable'
        WHEN 'MAR' THEN 'Laborable'
        WHEN 'MIE' THEN 'Laborable'
        WHEN 'JUE' THEN 'Laborable'
        WHEN 'VIE' THEN 'Laborable'
        WHEN 'SAB' THEN 'Fin de semana'
        WHEN 'DOM' THEN 'Fin de semana'
        ELSE 'Día no válido'
    END;
    
    DBMS_OUTPUT.PUT_LINE(v_dia || ' es: ' || v_tipo);
END;
/

2.2. CASE buscado (searched CASE)

Permite condiciones más complejas con operadores de comparación:

DECLARE
    v_salario NUMBER := 2800;
    v_categoria VARCHAR2(20);
BEGIN
    v_categoria := CASE
        WHEN v_salario < 1000  THEN 'Junior'
        WHEN v_salario < 2000  THEN 'Intermedio'
        WHEN v_salario < 3000  THEN 'Senior'
        WHEN v_salario >= 3000 THEN 'Lead'
        ELSE 'Sin clasificar'
    END;
    
    DBMS_OUTPUT.PUT_LINE('Salario: ' || v_salario || ' → Categoría: ' || v_categoria);
END;
/

2.3. CASE como sentencia (no como expresión)

También se puede usar CASE como sentencia independiente (sin asignación), terminando con END CASE:

DECLARE
    v_opcion NUMBER := 2;
BEGIN
    CASE v_opcion
        WHEN 1 THEN
            DBMS_OUTPUT.PUT_LINE('Has elegido la opción 1');
        WHEN 2 THEN
            DBMS_OUTPUT.PUT_LINE('Has elegido la opción 2');
        WHEN 3 THEN
            DBMS_OUTPUT.PUT_LINE('Has elegido la opción 3');
        ELSE
            DBMS_OUTPUT.PUT_LINE('Opción no válida');
    END CASE;
END;
/

💡 Truco: Cuando uses CASE como expresión (asignando a una variable), termina con END;. Cuando lo uses como sentencia (sin asignar), termina con END CASE;. Si los confundes, Oracle lanzará un error de compilación.


3. Bucle LOOP básico (bucle infinito)

El bucle LOOP más simple se repite indefinidamente hasta que se le indique que pare con EXIT:

DECLARE
    v_contador NUMBER := 1;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE('Iteración: ' || v_contador);
        v_contador := v_contador + 1;
        
        -- Condición de salida
        EXIT WHEN v_contador > 5;
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('Fin del bucle');
END;
/

Resultado:

Iteración: 1
Iteración: 2
Iteración: 3
Iteración: 4
Iteración: 5
Fin del bucle

EXIT vs EXIT WHEN

DECLARE
    v_i NUMBER := 0;
BEGIN
    LOOP
        v_i := v_i + 1;
        
        -- EXIT simple: con IF explícito
        IF v_i > 10 THEN
            EXIT;
        END IF;
        
        DBMS_OUTPUT.PUT_LINE('Valor: ' || v_i);
    END LOOP;
END;
/

⚠️ Importante: Asegúrate siempre de que el bucle tenga una condición de salida reachable. Un bucle LOOP sin EXIT generará un bucle infinito que puede bloquear tu sesión.


4. Bucle WHILE

El bucle WHILE evalúa la condición antes de cada iteración. Si la condición es falsa desde el principio, el cuerpo del bucle no se ejecuta nunca:

DECLARE
    v_contador NUMBER := 1;
BEGIN
    WHILE v_contador <= 5 LOOP
        DBMS_OUTPUT.PUT_LINE('Iteración: ' || v_contador);
        v_contador := v_contador + 1;
    END LOOP;
END;
/

Ejemplo práctico: tabla de multiplicar

DECLARE
    v_numero   NUMBER := 7;
    v_i        NUMBER := 1;
BEGIN
    DBMS_OUTPUT.PUT_LINE('--- Tabla del ' || v_numero || ' ---');
    
    WHILE v_i <= 10 LOOP
        DBMS_OUTPUT.PUT_LINE(v_numero || ' x ' || v_i || ' = ' || (v_numero * v_i));
        v_i := v_i + 1;
    END LOOP;
END;
/

5. Bucle FOR

El bucle FOR itera automáticamente sobre un rango numérico. La variable de control se declara implícitamente y no necesita estar en DECLARE:

BEGIN
    FOR i IN 1..5 LOOP
        DBMS_OUTPUT.PUT_LINE('Iteración: ' || i);
    END LOOP;
END;
/

5.1. FOR en orden inverso (REVERSE)

BEGIN
    FOR i IN REVERSE 1..5 LOOP
        DBMS_OUTPUT.PUT_LINE('Cuenta atrás: ' || i);
    END LOOP;
END;
/

Resultado:

Cuenta atrás: 5
Cuenta atrás: 4
Cuenta atrás: 3
Cuenta atrás: 2
Cuenta atrás: 1

5.2. Rango con variables

DECLARE
    v_inicio NUMBER := 3;
    v_fin    NUMBER := 8;
BEGIN
    FOR i IN v_inicio..v_fin LOOP
        DBMS_OUTPUT.PUT_LINE('i = ' || i);
    END LOOP;
END;
/

📘 Concepto: La variable del FOR (por ejemplo i) es de solo lectura dentro del bucle. No puedes asignarle un valor con :=. Además, solo existe dentro del bucle; fuera de él no es accesible.

5.3. Ejemplo: números pares

BEGIN
    DBMS_OUTPUT.PUT_LINE('Números pares del 1 al 20:');
    FOR i IN 1..20 LOOP
        IF MOD(i, 2) = 0 THEN
            DBMS_OUTPUT.PUT_LINE(i);
        END IF;
    END LOOP;
END;
/

6. CONTINUE y CONTINUE WHEN

CONTINUE salta a la siguiente iteración del bucle sin ejecutar el código restante del cuerpo:

BEGIN
    FOR i IN 1..10 LOOP
        -- Saltar los números divisibles por 3
        CONTINUE WHEN MOD(i, 3) = 0;
        
        DBMS_OUTPUT.PUT_LINE('Valor: ' || i);
    END LOOP;
END;
/

Resultado:

Valor: 1
Valor: 2
Valor: 4
Valor: 5
Valor: 7
Valor: 8
Valor: 10

💡 Truco: CONTINUE WHEN está disponible desde Oracle 11g. Es más limpio que un IF...THEN CONTINUE; END IF;, aunque ambas formas son correctas.


7. Etiquetas de bucles

Cuando tienes bucles anidados, las etiquetas permiten identificar a qué bucle se refiere un EXIT o CONTINUE:

BEGIN
    <<bucle_externo>>
    FOR i IN 1..3 LOOP
        <<bucle_interno>>
        FOR j IN 1..3 LOOP
            -- Salir del bucle externo cuando i=2 y j=2
            EXIT bucle_externo WHEN i = 2 AND j = 2;
            
            DBMS_OUTPUT.PUT_LINE('i=' || i || ', j=' || j);
        END LOOP bucle_interno;
    END LOOP bucle_externo;
    
    DBMS_OUTPUT.PUT_LINE('Fin');
END;
/

8. Bucles anidados: ejemplo completo

Un ejemplo práctico que combina bucles y condicionales para generar un triángulo de asteriscos:

DECLARE
    v_linea VARCHAR2(100);
BEGIN
    FOR i IN 1..5 LOOP
        v_linea := '';
        FOR j IN 1..i LOOP
            v_linea := v_linea || '* ';
        END LOOP;
        DBMS_OUTPUT.PUT_LINE(v_linea);
    END LOOP;
END;
/

Resultado:

* 
* * 
* * * 
* * * * 
* * * * * 

9. Comparativa de bucles

Característica LOOP WHILE FOR
Condición Al final (EXIT WHEN) Al principio Implícita (rango)
Mínimo de ejecuciones 1 (al menos una vez) 0 (puede no ejecutarse) Depende del rango
Variable de control Manual Manual Automática (solo lectura)
Ideal para Bucles con salida condicional compleja Bucles donde la condición puede ser falsa al inicio Iterar un rango numérico conocido

10. Ejercicios prácticos

Ejercicio 1: Clasificación de infracciones

Dada una variable con el valor económico de una infracción, clasifícala como ‘Leve’, ‘Grave’ o ‘Muy grave’ usando IF-ELSIF:

DECLARE
    v_importe NUMBER := 250;
BEGIN
    IF v_importe < 100 THEN
        DBMS_OUTPUT.PUT_LINE('Infracción LEVE (' || v_importe || ' €)');
    ELSIF v_importe < 300 THEN
        DBMS_OUTPUT.PUT_LINE('Infracción GRAVE (' || v_importe || ' €)');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Infracción MUY GRAVE (' || v_importe || ' €)');
    END IF;
END;
/

Ejercicio 2: Menú con CASE

Simula un menú de opciones usando CASE como sentencia:

DECLARE
    v_opcion NUMBER := 2;
BEGIN
    CASE v_opcion
        WHEN 1 THEN
            DBMS_OUTPUT.PUT_LINE('Consultar conductores');
        WHEN 2 THEN
            DBMS_OUTPUT.PUT_LINE('Consultar multas');
        WHEN 3 THEN
            DBMS_OUTPUT.PUT_LINE('Consultar vehículos');
        ELSE
            DBMS_OUTPUT.PUT_LINE('Opción no válida');
    END CASE;
END;
/

Ejercicio 3: Factorial con WHILE

Calcula el factorial de un número usando un bucle WHILE:

DECLARE
    v_numero    NUMBER := 6;
    v_factorial NUMBER := 1;
    v_i         NUMBER := 1;
BEGIN
    WHILE v_i <= v_numero LOOP
        v_factorial := v_factorial * v_i;
        v_i := v_i + 1;
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE(v_numero || '! = ' || v_factorial);
    -- Resultado: 6! = 720
END;
/

Ejercicio 4: Números primos con FOR

Muestra los números primos entre 2 y 50:

DECLARE
    v_es_primo BOOLEAN;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Números primos entre 2 y 50:');
    
    FOR i IN 2..50 LOOP
        v_es_primo := TRUE;
        
        FOR j IN 2..TRUNC(SQRT(i)) LOOP
            IF MOD(i, j) = 0 THEN
                v_es_primo := FALSE;
                EXIT;  -- No hace falta seguir comprobando
            END IF;
        END LOOP;
        
        IF v_es_primo THEN
            DBMS_OUTPUT.PUT(i || ' ');
        END IF;
    END LOOP;
    
    DBMS_OUTPUT.NEW_LINE;
END;
/

Ejercicio 5: FizzBuzz clásico

Imprime los números del 1 al 30. Si es divisible por 3, imprime ‘Fizz’. Si es divisible por 5, imprime ‘Buzz’. Si es divisible por ambos, imprime ‘FizzBuzz’:

BEGIN
    FOR i IN 1..30 LOOP
        CASE
            WHEN MOD(i, 15) = 0 THEN DBMS_OUTPUT.PUT_LINE(i || ': FizzBuzz');
            WHEN MOD(i, 3) = 0  THEN DBMS_OUTPUT.PUT_LINE(i || ': Fizz');
            WHEN MOD(i, 5) = 0  THEN DBMS_OUTPUT.PUT_LINE(i || ': Buzz');
            ELSE                      DBMS_OUTPUT.PUT_LINE(i);
        END CASE;
    END LOOP;
END;
/

Resumen

Concepto Detalle
IF-THEN Ejecuta código si se cumple una condición
IF-THEN-ELSE Dos caminos alternativos según la condición
IF-THEN-ELSIF Múltiples condiciones en cascada (ojo: ELSIF, no ELSEIF)
CASE simple Compara una expresión con varios valores de igualdad
CASE buscado Evalúa múltiples condiciones con operadores de comparación
LOOP Bucle infinito que requiere EXIT o EXIT WHEN para terminar
WHILE LOOP Bucle que evalúa la condición antes de cada iteración
FOR LOOP Bucle con variable de control automática sobre un rango numérico
REVERSE Invierte el orden del bucle FOR (de mayor a menor)
EXIT / EXIT WHEN Sale del bucle actual (o del etiquetado)
CONTINUE / CONTINUE WHEN Salta a la siguiente iteración del bucle
Etiquetas <<nombre>> para identificar bucles y controlar EXIT/CONTINUE en anidamientos