Essa é a parte 2 de uma série de 3 posts (ou 4, quem sabe), onde eu quero falar sobre as formas de realizar o tratamento de exceções no SQL Server.

Se você caiu aqui por acaso, sugiro antes dar uma olhada na parte 1 da série, onde eu trato de alguns detalhes no que diz respeito à forma que o SQL Server realiza o encerramento de processos, nos casos de erro.

Hoje irei falar sobre como realizar o tratamento de exceções, via TRY / CATCH.

Estrutura:

BEGIN TRY
  <Bloco de comandos>
END TRY
BEGIN CATCH
  <Bloco de comandos>
END CATCH

Simples, não? 🙂
O objetivo: Fazer com que (quase) todo e qualquer erro de execução que esteja dentro do TRY, seja direcionado para o CATCH (o “quase” é porque isso acontece desde que a exceção tenha uma severidade maior que 10 ou que não aborte a conexão do usuário).
Caso o bloco em execução dentro do TRY não gere nenhuma exceção, o CATCH simplesmente não é executado e o que existir após o CATCH é executado normalmente.
Se o CATCH é a última linha sendo executada, o retorno vai para quem o chamou, seja uma procedure aninhada ou o próprio SSMS, por exemplo. Veja abaixo, como o CATCH não é executado caso não ocorra erro na execução:

PRINT 'Antes do TRY'
BEGIN TRY
  PRINT 'Primeiro item no TRY'
END TRY
BEGIN CATCH
  PRINT 'Primeiro item no CATCH'
END CATCH
PRINT 'Após o CATCH'
[![](/images/2015/11/TryCatch1.png)][2]
Sem exceção…

Agora, deixe-me forçar um erro de divisão por zero e veja o que acontece:

set nocount on
select 1/0
PRINT 'Antes do TRY'
BEGIN TRY
  PRINT 'Primeiro item no TRY'
  select 1/0
  PRINT 'Segundo item no TRY'
END TRY
BEGIN CATCH
  PRINT 'Primeiro item no CATCH'
END CATCH
PRINT 'Após o CATCH'
[![](/images/2015/11/TryCatch2.png)][3]
Agora sim, entrou no CATCH.

Repare que eu forcei o mesmo erro duas vezes. A primeira, fora do TRY, para mostrar o erro que é gerado, e a segunda, mostrando que o erro não é exibido. Ao invés disso, é gerado como resultado o que está dentro do CATCH. Note também que dentro do _TRY _existia a execução de 3 statements, porém apenas o primeiro PRINT foi realizado.
No post anterior, eu comentei a respeito dos erros que podem ser tratados pelo usuário, onde o bloco continua a execução do batch. Porém, quando o bloco estiver dentro de um TRY, assim que um erro surgir, o fluxo será direcionado diretamente para o CATCH e os statements restantes serão ignorados. Veja:

[![](/images/2015/11/TryCatch3-300x298.png)][4]
Abortando todo o bloco do TRY

Caso você queira que, no caso de uma exceção, nenhum registro inserido ou alterado seja commitado no banco, você deve trabalhar com transações:

[![](/images/2015/11/TryCatch4-257x300.png)][5]
Está faltando algo ainda…

Veja que, mesmo colocando o bloco dentro de uma transação, o registro com código 2 foi inserido. Por que?

Se eu abrir uma nova conexão e tentar fazer um select simples, vou ficar com a janela travada, em lock, pois eu abri uma transação mas o commit não foi realizado, uma vez que a exceção foi gerada antes de chegar no commit (não vou entrar no mérito dos níveis de isolamento e hints aqui). Precisamos, portanto, colocar dentro do CATCH um ROLLBACK, para que a transação seja encerrada, os dados não sejam efetivados no banco e um próximo select na tabela rode normalmente.
O CATCH deve ficar da seguinte forma:

BEGIN CATCH
  PRINT 'Gerou um erro'
  ROLLBACK
END CATCH

Assim não preciso me preocupar com locks, certo? É, não é bem assim.
Nem todo o bloco dentro do TRY estará sempre dentro de uma transação. Veja o que acontece se depois do _COMMIT _eu forçar um erro.

[![](/images/2015/11/TryCatch5-300x239.png)][6]
Opa… ROLLBACK do que?

Ou seja, eu preciso, antes de fazer o ROLLBACK, validar se existe alguma transação não efetivada no banco para encerrá-lo. Para isso, devo mudar o CATCH para:

BEGIN CATCH
  PRINT 'Gerou um erro'
  IF @@TRANCOUNT > 0
    ROLLBACK
END CATCH
[![](/images/2015/11/TryCatch6-300x225.png)][7]
Agora sim, como precisamos!

Estamos quase chegando em um template para tratamento de exceções, mas ainda está faltando uma coisa para encerrar esse post: Capturar o erro que está sendo executado.

No SQL Server existem algumas funções de erro que retornam todas as situações já expostas (clique no link para acessar a referência no BOL): número, severidade, estado, procedure, linha e mensagem:

SELECT
    ERROR_NUMBER() AS Numero,
    ERROR_SEVERITY() AS Severidade,
    ERROR_STATE() AS Estado,
    ERROR_PROCEDURE() AS [Procedure],
    ERROR_LINE() AS Linha,
    ERROR_MESSAGE() AS Mensagem;

Porém, é imporante lembrar que estas funções retornam um valor apenas quando estão dentro do CATCH. Se executar diretamente, mesmo após um erro, será retornado nulo:

[![](/images/2015/11/TryCatch7-300x179.png)][8]
Não terá retorno
[![](/images/2015/11/TryCatch8-300x154.png)][9]
Agora sim, com o erro.

Bom o que estará no select, é a regra de negócio que ditará as regras.

No próximo post, pretendo falar sobre o RAISERROR e o THROW. Suas diferenças e semelhanças.

Abraço!