1. Anatomia do TRY/CATCH
Assim como em C#, T-SQL executa o bloco TRY e, se qualquer comando
dentro dele falhar, desvia imediatamente para o CATCH — pulando
qualquer código restante do TRY.
BEGIN TRY
-- código que pode falhar
SELECT 1/0;
END TRY
BEGIN CATCH
-- roda só se algo no TRY falhou
PRINT ERROR_MESSAGE();
END CATCH
try
{
var x = 1 / 0;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
2. Funções de diagnóstico do erro
Dentro do bloco CATCH, seis funções dão acesso aos detalhes do erro
que acabou de acontecer:
| Função | Devolve |
|---|---|
ERROR_NUMBER() | Código numérico do erro (ex: 547 para violação de FK) |
ERROR_MESSAGE() | Texto da mensagem de erro |
ERROR_SEVERITY() | Gravidade (11-16 = erro do usuário; 17+ = problema de sistema) |
ERROR_STATE() | Estado — útil para diferenciar onde, no código, o mesmo erro foi lançado |
ERROR_LINE() | Linha do código onde o erro ocorreu |
ERROR_PROCEDURE() | Nome da procedure onde o erro ocorreu (NULL se foi fora de uma procedure) |
BEGIN CATCH
PRINT 'Erro ' + CAST(ERROR_NUMBER() AS VARCHAR(10))
+ ' na procedure ' + ISNULL(ERROR_PROCEDURE(), '(fora de procedure)')
+ ', linha ' + CAST(ERROR_LINE() AS VARCHAR(10))
+ ': ' + ERROR_MESSAGE();
END CATCH
3. THROW vs. RAISERROR
Existem duas formas de lançar um erro manualmente. THROW é mais novo
(SQL Server 2012+) e mais simples; RAISERROR é mais antigo e mais
flexível na formatação. Hoje, prefira THROW como padrão.
THROW | RAISERROR | |
|---|---|---|
| Sintaxe para erro customizado | THROW 50000, 'mensagem', 1; | RAISERROR('mensagem', 16, 1); |
| Relançar o erro capturado | THROW; (sem parâmetros, dentro do CATCH) | Não existe forma direta — precisa reconstruir a mensagem |
| Formatação com placeholders | Não suporta (%d, %s) | Suporta: RAISERROR('Cliente %d inválido', 16, 1, @Id) |
| Sempre desfaz a transação com XACT_ABORT? | Sim | Sim |
CREATE PROCEDURE dbo.usp_ContaTransferir
@ContaOrigemId INT,
@ContaDestinoId INT,
@Valor DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
IF @Valor <= 0
THROW 50001, 'O valor da transferência deve ser positivo.', 1;
BEGIN TRANSACTION;
UPDATE dbo.Conta SET Saldo = Saldo - @Valor WHERE Id = @ContaOrigemId;
IF @@ROWCOUNT = 0
THROW 50002, 'Conta de origem não encontrada.', 1;
UPDATE dbo.Conta SET Saldo = Saldo + @Valor WHERE Id = @ContaDestinoId;
IF @@ROWCOUNT = 0
THROW 50003, 'Conta de destino não encontrada.', 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
INSERT INTO dbo.ErrorLog (Numero, Mensagem, Procedure_, DataOcorrencia)
VALUES (ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_PROCEDURE(), SYSDATETIME());
THROW; -- relança o erro original para o C#
END CATCH
END;
GO
THROW; sem argumentos, usado dentro de um CATCH,
relança o erro exatamente como capturado — número, mensagem e linha originais.
É diferente de THROW 50000, ERROR_MESSAGE(), 1;, que cria um erro novo
e perde a linha/procedure originais no rastreamento.
4. Uma tabela simples de log de erros
CREATE TABLE dbo.ErrorLog
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Numero INT,
Mensagem NVARCHAR(2000),
Procedure_ NVARCHAR(200),
DataOcorrencia DATETIME2 NOT NULL DEFAULT SYSDATETIME()
);
5. Como isso chega ao C#
Quando uma procedure lança um erro (via THROW ou erro nativo do SQL
Server) e a transação já foi desfeita, o driver ADO.NET traduz isso em uma
SqlException do lado do C#:
try
{
using var comando = new SqlCommand("dbo.usp_ContaTransferir", conexao);
comando.CommandType = CommandType.StoredProcedure;
comando.Parameters.AddWithValue("@ContaOrigemId", 1);
comando.Parameters.AddWithValue("@ContaDestinoId", 999);
comando.Parameters.AddWithValue("@Valor", 100m);
comando.ExecuteNonQuery();
}
catch (SqlException ex)
{
// ex.Number carrega o código do THROW (50003 no exemplo)
// ex.Message carrega o texto exato do THROW
Console.WriteLine($"Erro {ex.Number}: {ex.Message}");
}
6. Onde isso te leva
Com transações e tratamento de erro cobertos, as próximas procedures deste curso já assumem esse
padrão (NOCOUNT + XACT_ABORT +
TRY/CATCH) como base. Os próximos capítulos entram em ferramentas mais
específicas: tabelas temporárias, table-valued parameters e cursores.