O que é Escopo de Exceção?
Você já ouviu falar de escopo de variáveis, certo? Pois bem, o escopo de exceções funciona de maneira parecida. É como se cada exceção tivesse seu próprio “território” dentro do código.
Imagine que seu código é uma casa. Cada cômodo dessa casa é um bloco de código. As exceções são como as regras de cada cômodo. Algumas regras valem só para um cômodo específico, outras valem para a casa toda. Entendeu a ideia?
Exemplo Básico de Escopo de Exceção
Vamos começar com um exemplo simples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DECLARE v_id_aluno NUMBER := &sv_id_aluno; v_nome VARCHAR2(30); BEGIN SELECT RTRIM(primeiro_nome) || ' ' || RTRIM(sobrenome) INTO v_nome FROM aluno WHERE id_aluno = v_id_aluno; DBMS_OUTPUT.PUT_LINE('Nome do aluno: ' || v_nome); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Não existe aluno com esse ID'); END; |
Neste exemplo, a exceção NO_DATA_FOUND cobre todo o bloco. É como se ela fosse uma regra geral da nossa “casa de código”.
Blocos Aninhados e Escopo de Exceção
Agora, vamos complicar um pouquinho. E se tivéssemos um bloco dentro de outro bloco?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<<bloco_externo>> DECLARE v_id_aluno NUMBER := &sv_id_aluno; v_nome VARCHAR2(30); v_total NUMBER(1); BEGIN SELECT RTRIM(primeiro_nome) || ' ' || RTRIM(sobrenome) INTO v_nome FROM aluno WHERE id_aluno = v_id_aluno; DBMS_OUTPUT.PUT_LINE('Nome do aluno: ' || v_nome); <<bloco_interno>> BEGIN SELECT COUNT(*) INTO v_total FROM matricula WHERE id_aluno = v_id_aluno; DBMS_OUTPUT.PUT_LINE('Aluno está matriculado em ' || v_total || ' curso(s)'); EXCEPTION WHEN VALUE_ERROR OR INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Ops! Ocorreu um erro no bloco interno'); END; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Não existe aluno com esse ID'); END; |
Neste exemplo, temos duas “salas” na nossa casa de código: o bloco externo e o bloco interno. Cada um tem suas próprias regras (exceções).
- VALUE_ERROR e INVALID_NUMBER são exceções locais do bloco interno. Elas só funcionam lá dentro.
- NO_DATA_FOUND é uma exceção do bloco externo, mas ela também vale para o bloco interno. É como uma regra da casa toda.
Propagação de Exceções
E se uma exceção acontecer no bloco interno, mas não tiver uma regra lá para lidar com ela? É aí que entra a propagação de exceções.
A Propagação de Exceções é um mecanismo pelo qual uma exceção não tratada em um bloco interno de código é automaticamente passada (ou “propagada”) para o bloco externo que o contém. Isso permite que exceções sejam tratadas em níveis superiores do código, caso não sejam tratadas no nível onde ocorreram.
Pre-requisitos para Executar o Exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-- Inserção de dados de exemplo -- Alunos INSERT INTO aluno (id_aluno, primeiro_nome, sobrenome) VALUES (101, 'João', 'Silva'); INSERT INTO aluno (id_aluno, primeiro_nome, sobrenome) VALUES (202, 'Maria', 'Santos'); INSERT INTO aluno (id_aluno, primeiro_nome, sobrenome) VALUES (284, 'Pedro', 'Ferreira'); INSERT INTO aluno (id_aluno, primeiro_nome, sobrenome) VALUES (303, 'Ana', 'Oliveira'); -- Matrículas INSERT INTO matricula (id_matricula, id_aluno, id_curso) VALUES (1, 101, 1); INSERT INTO matricula (id_matricula, id_aluno, id_curso) VALUES (2, 202, 1); INSERT INTO matricula (id_matricula, id_aluno, id_curso) VALUES (3, 202, 2); INSERT INTO matricula (id_matricula, id_aluno, id_curso) VALUES (4, 303, 1); -- Commit para salvar as alterações COMMIT; |
Veja este exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<<bloco_externo>> DECLARE v_id_aluno NUMBER := &sv_id_aluno; v_nome VARCHAR2(30); v_matriculado CHAR; BEGIN SELECT RTRIM(primeiro_nome) || ' ' || RTRIM(sobrenome) INTO v_nome FROM aluno WHERE id_aluno = v_id_aluno; DBMS_OUTPUT.PUT_LINE('Nome do aluno: ' || v_nome); <<bloco_interno>> BEGIN SELECT 'S' INTO v_matriculado FROM matricula WHERE id_aluno = v_id_aluno; DBMS_OUTPUT.PUT_LINE('Aluno está matriculado'); EXCEPTION WHEN VALUE_ERROR OR INVALID_NUMBER THEN DBMS_OUTPUT.PUT_LINE('Ops! Ocorreu um erro no bloco interno'); END; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Não existe aluno com esse ID'); END; |
Agora, vamos considerar o cenário onde executamos este código com um ID de aluno que existe no banco de dados, mas não está matriculado (como o ID 284 mencionado):
- O bloco externo começa a executar. Ele encontra o aluno com ID 284 na tabela
aluno
e exibe o nome. - O bloco interno começa a executar. Ele tenta encontrar uma matrícula para o aluno 284 na tabela
matricula
. - Como o aluno não está matriculado, a consulta no bloco interno não retorna nenhuma linha, gerando uma exceção
NO_DATA_FOUND
. - O bloco interno tem uma seção EXCEPTION, mas ela só trata
VALUE_ERROR
eINVALID_NUMBER
. Não há tratamento paraNO_DATA_FOUND
. - Como a exceção
NO_DATA_FOUND
não é tratada no bloco interno, ela é propagada para o bloco externo. - O bloco externo tem uma seção EXCEPTION que trata
NO_DATA_FOUND
. Esta seção é acionada. - A mensagem “Não existe aluno com esse ID” é exibida.
O resultado final é confuso porque:
- O nome do aluno é exibido (indicando que o aluno existe).
- A mensagem “Não existe aluno com esse ID” também é exibida (devido à exceção propagada do bloco interno).
Esta situação demonstra um problema comum em programação: a reutilização inadequada de tratamentos de exceção. A mensagem no bloco externo foi originalmente pensada para o caso em que o aluno não existe, mas está sendo incorretamente usada quando o aluno existe, mas não está matriculado.
Para corrigir isso, você poderia:
- Adicionar um tratamento específico para
NO_DATA_FOUND
no bloco interno. - Usar exceções personalizadas para diferenciar entre “aluno não existe” e “aluno não está matriculado”.
- Reestruturar o código para evitar a necessidade de propagação de exceções neste caso.
Este exemplo ilustra a importância de entender como as exceções se propagam e de projetar cuidadosamente a estrutura de tratamento de exceções em seu código PL/SQL.
Conclusão e Dicas
Entender o escopo das exceções é crucial para criar códigos PL/SQL robustos. Algumas dicas importantes:
- Seja específico com suas exceções. Trate cada erro no nível mais apropriado.
- Cuidado com mensagens de erro genéricas. Elas podem confundir mais do que ajudar.
- Lembre-se: exceções em blocos externos são “globais” para blocos internos.
- Sempre considere exceções que podem ser propagadas de blocos internos.
Alguma dúvida? Não hesite em perguntar. Pratique esses conceitos e você se tornará um mestre em lidar com exceções em PL/SQL. Até a próxima!