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,WHILEyFOR - Controlar el flujo de los bucles con
EXIT,EXIT WHENyCONTINUE - 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’), noELSEIFniELSE 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,FALSEoNULL. Cualquier comparación conNULLda como resultadoNULL(ni verdadero ni falso). Por eso se usaIS NULLy 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
CASEcomo expresión (asignando a una variable), termina conEND;. Cuando lo uses como sentencia (sin asignar), termina conEND 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
LOOPsinEXITgenerará 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 ejemploi) 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 WHENestá disponible desde Oracle 11g. Es más limpio que unIF...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 |