manejo-errores-try-catch-sql

Manejo de errores en SQL con TRY...CATCH

  • 4 min

TRY…CATCH es una estructura de control en T-SQL que permite manejar errores que pueden ocurrir en la ejecución de código.

Hasta ahora, si uno de nuestros scripts fallaba (por ejemplo, una división por cero o una violación de llave foránea), SQL Server mostraba un mensaje rojo y detenía la ejecución.

Pero eso… no queda muy profesional, no. No esta bien que dejemos que el código “explote” sin control. Necesitamos capturar el error, limpiarlo y decidir qué hacer.

Para ello, T-SQL implementa la estructura TRY...CATCH (muy similar a la que existe en lenguajes como C#, JavaScript o Python).

Estructura básica

Su funcionamiento es sencillo:

  1. BEGIN TRY: “Intenta hacer esto”.
  2. BEGIN CATCH: “Si algo falla arriba, salta aquí inmediatamente y ejecuta este plan de contingencia”.

Es decir, la sintaxis básica es la siguiente:

BEGIN TRY
    -- Código que podría generar un error
END TRY
BEGIN CATCH
    -- Manejo del error
END CATCH;
Copied!

Ejemplo básico

Imaginemos que tenemos una tabla Productos y queremos insertar un nuevo producto. Si el producto ya existe (incumple una restricción UNIQUE), queremos manejar el error de manera controlada:

BEGIN TRY
    INSERT INTO Productos (ID, Nombre, Precio)
    VALUES (1, 'Laptop', 1200);
END TRY
BEGIN CATCH
    PRINT 'Error: No se pudo insertar el producto.';
    PRINT 'Mensaje de error: ' + ERROR_MESSAGE();
END CATCH;
Copied!

En este ejemplo, si el producto con ID = 1 ya existe, se captura el error en el bloque CATCH y se muestra un mensaje informativo.

Obtener información del error

Saber que hubo un error está bien, pero también necesitamos saber qué pasó. Dentro del bloque CATCH, tenemos acceso a funciones especiales del sistema que nos dan los detalles del accidente:

FunciónDescripción
ERROR_NUMBER()El código interno del error.
ERROR_MESSAGE()El texto descriptivo (“Divide by zero error encountered”).
ERROR_SEVERITY()La gravedad (1-25).
ERROR_LINE()En qué línea de código explotó.
ERROR_PROCEDURE()En qué procedimiento almacenado ocurrió.

Por ejemplo,

BEGIN TRY
    -- Intentamos insertar un duplicado en una PK
    INSERT INTO Clientes (ClienteID, Nombre) VALUES (1, 'Juan');
    INSERT INTO Clientes (ClienteID, Nombre) VALUES (1, 'Pedro'); -- Falla aquí
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS NumeroError,
        ERROR_MESSAGE() AS Mensaje,
        ERROR_LINE() AS Linea;
END CATCH
Copied!

Manejo de errores en transacciones

Uno de los usos más comunes de TRY...CATCH es proteger la integridad de los datos cuando usamos transacciones.

Si abres una transacción, haces dos cambios, y el tercero falla… ¿qué pasa con la transacción abierta? Se queda “colgada”, bloqueando tablas y consumiendo recursos.

Debemos usar el bloque CATCH para hacer el ROLLBACK (deshacer) automáticamente si algo sale mal.

BEGIN TRY
    BEGIN TRANSACTION; -- Abrimos transacción

        -- Paso 1: Restar Stock
        UPDATE Productos SET Stock = Stock - 1 WHERE ID = 100;

        -- Paso 2: Generar error provocado (ej: restricción CHECK)
        UPDATE Ventas SET Cantidad = -5 WHERE ID = 999; 

    COMMIT TRANSACTION; -- Si todo va bien, guardamos
    PRINT 'Venta finalizada con éxito';
END TRY
BEGIN CATCH
    -- ¡Algo falló!
    
    -- Verificamos si hay una transacción abierta para deshacerla
    IF @@TRANCOUNT > 0
    BEGIN
        ROLLBACK TRANSACTION;
        PRINT 'Error detectado. Se han deshecho todos los cambios.';
    END

    -- Mostramos el error original
    SELECT ERROR_MESSAGE() AS MotivoFallo;
END CATCH
Copied!

Este es el snippet (plantilla) que deberías usar siempre que escribas procedimientos almacenados transaccionales.

Relanzar el error: THROW

Cuando capturas un error con CATCH, para SQL Server el error ya ha sido manejado. El script termina “con éxito” (código de salida 0) aunque tú hayas impreso un mensaje de error.

Esto es peligroso porque la aplicación que llamó al SQL (tu backend en .NET, Java, etc.) pensará que todo fue bien.

Para avisar al mundo exterior de que hubo un problema, debemos relanzar el error usando THROW.

BEGIN CATCH
    IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;

    -- Registramos el error en una tabla de log (opcional)
    INSERT INTO LogErrores (Mensaje) VALUES (ERROR_MESSAGE());

    -- ¡Lanzamos el error de nuevo a la aplicación!
    -- Sintaxis: THROW [Numero], [Mensaje], [Estado];
    -- O simplemente ;THROW para relanzar el original.
    
    ;THROW; 
END CATCH
Copied!

Es una peculiaridad sintáctica famosa. La instrucción anterior al THROW debe terminar obligatoriamente con punto y coma ;.

Por costumbre, muchos desarrolladores escriben ;THROW para asegurarse.