Bem-vindo ao nosso artigo sobre Propagação de Exceções em PL/SQL Oracle. Hoje, vamos explorar em detalhes como as exceções se propagam em diferentes partes de um bloco PL/SQL e como podemos controlar esse comportamento.
Introdução à Propagação de Exceções
A propagação de exceções refere-se às regras que governam como as exceções são levantadas e tratadas em diferentes partes de um bloco PL/SQL. Essas regras são cruciais para entender como lidar com erros em situações complexas.
Vamos explorar três cenários principais:
- Erros na seção executável
- Erros na seção declarativa
- Erros na seção de tratamento de exceções
1. Erros na Seção Executável
Quando um erro ocorre na seção executável de um bloco PL/SQL, o comportamento é relativamente direto. Vamos revisar este caso.
1 2 3 4 5 6 7 8 9 10 11 |
-- Exemplo 1: Erro na seção executável DECLARE v_result NUMBER; BEGIN v_result := 1 / 0; -- Divisão por zero DBMS_OUTPUT.PUT_LINE('Este código não será executado'); EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('Erro: Divisão por zero'); END; / |
Neste caso:
- Se uma exceção específica está associada ao erro, o controle passa para a seção de tratamento de exceções do bloco.
- Após a execução das instruções associadas à exceção, o controle passa para o ambiente host ou para o bloco envolvente.
- Se não houver um manipulador de exceção para este erro, a exceção é propagada para o bloco envolvente (bloco externo).
- Se nenhum manipulador de exceção for encontrado, a execução do programa é interrompida e o controle é transferido para o ambiente host.
2. Erros na Seção Declarativa
Erros na seção declarativa são mais complicados. Vamos examinar este cenário em detalhes.
1 2 3 4 5 6 7 8 9 10 |
-- Exemplo 2: Erro na seção declarativa DECLARE v_test_var CHAR(3) := 'ABCDE'; -- Erro: string muito longa BEGIN DBMS_OUTPUT.PUT_LINE('Este código não será executado'); EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Este tratamento não funcionará'); END; / |
Quando executado, este exemplo produz o seguinte resultado:
1 2 3 |
ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 2 |
Observe que, mesmo tendo um manipulador de exceção para VALUE_ERROR, o bloco não consegue executar com sucesso. Isso nos leva à conclusão de que quando um erro de tempo de execução ocorre na seção declarativa do bloco PL/SQL, a seção de tratamento de exceções deste bloco não é capaz de capturar o erro.
Agora, vamos considerar uma versão modificada com blocos PL/SQL aninhados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-- Exemplo 3: Erro na seção declarativa com bloco aninhado <<outer_block>> BEGIN <<inner_block>> DECLARE v_test_var CHAR(3) := 'ABCDE'; -- Erro: string muito longa BEGIN DBMS_OUTPUT.PUT_LINE('Este código não será executado'); EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Este tratamento não funcionará'); END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro capturado no bloco externo: ' || SQLERRM); END; / |
Quando executado, este exemplo produz:
1 2 |
Erro capturado no bloco externo |
Nesta versão, o bloco PL/SQL está envolto por outro bloco, e o programa consegue completar. A exceção definida no bloco externo é levantada quando o erro ocorre na seção declarativa do bloco interno. Portanto, podemos concluir que quando um erro de tempo de execução ocorre na seção declarativa do bloco interno, a exceção é imediatamente propagada para o bloco envolvente (externo).
3. Erros na Seção de Tratamento de Exceções
Agora, vamos considerar o terceiro caso, onde um erro de tempo de execução ocorre na seção de tratamento de exceções do bloco. Assim como no caso anterior, se não houver um bloco externo, a execução do programa é interrompida e o controle passa para o ambiente host.
Considere o seguinte exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 |
-- Exemplo 4: Erro na seção de tratamento de exceções DECLARE v_test_var CHAR(3) := 'ABC'; BEGIN v_test_var := '1234'; -- Causa VALUE_ERROR DBMS_OUTPUT.PUT_LINE('v_test_var: ' || v_test_var); EXCEPTION WHEN VALUE_ERROR THEN v_test_var := 'ABCD'; -- Causa outro VALUE_ERROR DBMS_OUTPUT.PUT_LINE('Erro tratado'); END; / |
Quando executado, este exemplo produz:
1 2 3 4 5 |
ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 4 ORA-06502: PL/SQL: numeric or value error: character string buffer too small ORA-06512: at line 7 |
Como você pode ver, a instrução de atribuição na seção executável do bloco causa um erro. Por sua vez, o controle é transferido para a seção de tratamento de exceções do bloco. No entanto, a instrução de atribuição na seção de tratamento de exceções do bloco levanta o mesmo erro. Como resultado, a saída deste exemplo exibe a mesma mensagem de erro duas vezes.
Agora, vamos considerar uma versão modificada do mesmo exemplo com blocos PL/SQL aninhados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<<outer_block>> BEGIN <<inner_block>> DECLARE v_test_var CHAR(3) := 'ABC'; BEGIN v_test_var := '1234'; -- Causa VALUE_ERROR DBMS_OUTPUT.PUT_LINE('v_test_var: ' || v_test_var); EXCEPTION WHEN VALUE_ERROR THEN v_test_var := 'ABCD'; -- Causa outro VALUE_ERROR DBMS_OUTPUT.PUT_LINE('Erro tratado no bloco interno'); END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro capturado no bloco externo: ' || SQLERRM); END; / |
Quando executado, esta versão produz:
1 2 |
Erro capturado no bloco externo |
Nesta versão do exemplo, o bloco PL/SQL está envolto por outro bloco, e o programa consegue completar. Neste caso, a exceção definida no bloco externo é levantada quando o erro ocorre na seção de tratamento de exceções do bloco interno. Portanto, podemos concluir que quando um erro de tempo de execução ocorre na seção de tratamento de exceções do bloco interno, a exceção é imediatamente propagada para o bloco envolvente.
Re-levantamento de Exceções
Em algumas ocasiões, você pode querer ser capaz de parar seu programa se um certo tipo de erro ocorrer. Em outras palavras, você pode querer tratar uma exceção no bloco interno e depois passá-la para o bloco externo. Este processo é chamado de re-levantamento de exceções.
Considere o seguinte exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
-- Exemplo 6: Re-levantamento de exceções <<outer_block>> DECLARE e_custom_exception EXCEPTION; BEGIN <<inner_block>> BEGIN RAISE e_custom_exception; EXCEPTION WHEN e_custom_exception THEN DBMS_OUTPUT.PUT_LINE('Erro tratado no bloco interno'); RAISE; -- Re-levanta a exceção END; EXCEPTION WHEN e_custom_exception THEN DBMS_OUTPUT.PUT_LINE('Erro re-levantado e capturado no bloco externo'); END; / |
Neste exemplo, a exceção e_exception é primeiro declarada no bloco externo, depois levantada no bloco interno. Como resultado, o controle é transferido para a seção de tratamento de exceções do bloco interno. A instrução RAISE na seção de tratamento de exceções do bloco causa a propagação da exceção para a seção de tratamento de exceções do bloco externo. Observe que quando a instrução RAISE é usada na seção de tratamento de exceções do bloco interno, ela não é seguida pelo nome da exceção.
Quando executado, este exemplo produz a seguinte saída:
1 2 |
Um erro ocorreu |
Considerações Importantes
- Quando uma exceção é levantada em um bloco PL/SQL que não tem um mecanismo de tratamento de exceções apropriado e não está envolto por outro bloco, o controle é transferido para o ambiente host, e o programa não consegue completar com sucesso. Por exemplo:
1 2 3 4 5 6 |
DECLARE e_exception EXCEPTION; BEGIN RAISE e_exception; END; / |
Isso resultará em:
1 2 3 |
ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at line 4 |
- Quando uma exceção é re-levantada em um bloco que não está envolto por nenhum outro bloco, o programa não consegue completar com sucesso. Por exemplo:
1 2 3 4 5 6 7 8 9 |
DECLARE e_exception EXCEPTION; BEGIN RAISE e_exception; EXCEPTION WHEN e_exception THEN RAISE; END; / |
Isso resultará em:
1 2 3 |
ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at line 7 |
O Problema
- O código levanta uma exceção e a captura corretamente
- No entanto, ao invés de tratar a exceção, ele simplesmente a re-levanta
- Como não há nenhum bloco externo, a exceção re-levantada não é capturada
- Isso resulta em um erro não tratado: “ORA-06510: PL/SQL: unhandled user-defined exception”
Consequências
- O programa termina abruptamente
- Nenhum tratamento real da exceção é realizado
- Qualquer lógica que deveria ser executada após este bloco não será executada
Solução: Usar Bloco Externo
1 2 3 4 5 6 7 8 9 10 11 12 13 |
BEGIN DECLARE e_exception EXCEPTION; BEGIN RAISE e_exception; EXCEPTION WHEN e_exception THEN RAISE; END; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Exceção capturada no bloco externo'); END; |
- Apenas uma exceção pode ser tratada por vez em um bloco. Se uma nova exceção é levantada durante o tratamento, ela se propaga para o bloco externo.
Exercício Prático Detalhado
Preparação do Ambiente
Primeiro, caso ainda não tenha criado, vamos criar as tabelas necessárias e inserir alguns dados de amostra. Execute os seguintes comandos SQL:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
-- CUIDADO O SCRIPT PRIMEIRO REMOVE -- Remover dados das tabelas DELETE FROM ENROLLMENT; DELETE FROM STUDENT; DELETE FROM COURSE; -- Remover as tabelas DROP TABLE ENROLLMENT; DROP TABLE STUDENT; DROP TABLE COURSE; -- Remover as sequências DROP SEQUENCE student_seq; DROP SEQUENCE course_seq; DROP SEQUENCE enrollment_seq; -- Commit para salvar as alterações COMMIT; -- Criação das tabelas CREATE TABLE student ( student_id NUMBER PRIMARY KEY, student_name VARCHAR2(100) NOT NULL, email VARCHAR2(100) ); CREATE TABLE course ( course_id NUMBER PRIMARY KEY, course_name VARCHAR2(100) NOT NULL, credits NUMBER(2) NOT NULL ); CREATE TABLE enrollment ( enrollment_id NUMBER PRIMARY KEY, student_id NUMBER, course_id NUMBER, enrollment_date DATE DEFAULT SYSDATE, FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (course_id) REFERENCES course(course_id) ); -- Sequências para gerar IDs automaticamente CREATE SEQUENCE student_seq START WITH 1 INCREMENT BY 1; CREATE SEQUENCE course_seq START WITH 1 INCREMENT BY 1; CREATE SEQUENCE enrollment_seq START WITH 1 INCREMENT BY 1; -- Inserção de dados de amostra INSERT INTO student (student_id, student_name, email) VALUES (student_seq.NEXTVAL, 'João Silva', 'joao.silva@email.com'); INSERT INTO student (student_id, student_name, email) VALUES (student_seq.NEXTVAL, 'Maria Santos', 'maria.santos@email.com'); INSERT INTO student (student_id, student_name, email) VALUES (student_seq.NEXTVAL, 'Pedro Oliveira', 'pedro.oliveira@email.com'); INSERT INTO student (student_id, student_name, email) VALUES (student_seq.NEXTVAL, 'Ana Rodrigues', 'ana.rodrigues@email.com'); INSERT INTO student (student_id, student_name, email) VALUES (student_seq.NEXTVAL, 'Carlos Ferreira', 'carlos.ferreira@email.com'); INSERT INTO course (course_id, course_name, credits) VALUES (course_seq.NEXTVAL, 'Introdução à Programação', 4); INSERT INTO course (course_id, course_name, credits) VALUES (course_seq.NEXTVAL, 'Banco de Dados', 4); INSERT INTO course (course_id, course_name, credits) VALUES (course_seq.NEXTVAL, 'Estruturas de Dados', 3); INSERT INTO course (course_id, course_name, credits) VALUES (course_seq.NEXTVAL, 'Redes de Computadores', 3); INSERT INTO course (course_id, course_name, credits) VALUES (course_seq.NEXTVAL, 'Inteligência Artificial', 4); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 1, 1); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 1, 2); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 1, 3); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 2, 1); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 2, 2); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 2, 3); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 2, 4); INSERT INTO enrollment (enrollment_id, student_id, course_id) VALUES (enrollment_seq.NEXTVAL, 2, 5); COMMIT; |
Agora Vamos criar um exemplo mais complexo para demonstrar todos os aspectos da propagação de exceções que discutimos:
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 31 32 33 34 35 36 37 38 |
-- Exemplo 7: Cenário mais complexo usando os dados inseridos DECLARE v_student_id NUMBER := 2; -- ID da Maria Santos v_student_name VARCHAR2(100); v_course_count NUMBER; e_invalid_student EXCEPTION; e_too_many_courses EXCEPTION; BEGIN -- Verifica se o estudante existe BEGIN SELECT student_name INTO v_student_name FROM student WHERE student_id = v_student_id; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE e_invalid_student; END; -- Conta o número de cursos SELECT COUNT(*) INTO v_course_count FROM enrollment WHERE student_id = v_student_id; -- Verifica se o estudante está matriculado em muitos cursos IF v_course_count >= 5 THEN RAISE e_too_many_courses; END IF; DBMS_OUTPUT.PUT_LINE('O estudante ' || v_student_name || ' está matriculado em ' || v_course_count || ' cursos.'); EXCEPTION WHEN e_invalid_student THEN DBMS_OUTPUT.PUT_LINE('Erro: Estudante não encontrado.'); WHEN e_too_many_courses THEN DBMS_OUTPUT.PUT_LINE('Aviso: Estudante já está matriculado no número máximo de cursos.'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro inesperado: ' || SQLERRM); END; / |
Este procedimento demonstra:
- Uso de sub-procedimentos para organizar a lógica
- Propagação de exceções entre sub-procedimentos e o bloco principal
- Re-levantamento de exceções personalizadas
- Tratamento de erros na seção declarativa (através do bloco aninhado em validate_student)
- Tratamento de erros na seção de tratamento de exceções (em check_course_limit)
- Re-levantamento de exceções para o chamador do procedimento
Para testar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
BEGIN process_student_enrollment(1); -- Estudante válido EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro capturado no bloco chamador: ' || SQLERRM); END; / BEGIN process_student_enrollment(100); -- Estudante inválido EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro capturado no bloco chamador: ' || SQLERRM); END; / -- Assumindo que o estudante com ID 2 está matriculado em 5 ou mais cursos BEGIN process_student_enrollment(2); -- Muitos cursos EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Erro capturado no bloco chamador: ' || SQLERRM); END; / |
Conclusão
A compreensão da propagação de exceções é crucial para escrever código PL/SQL robusto e confiável. Lembre-se:
- Erros na seção declarativa não podem ser tratados no mesmo bloco, mas podem ser capturados por um bloco externo.
- Erros na seção de tratamento de exceções propagam-se imediatamente para o bloco envolvente.
- Use blocos aninhados para maior controle sobre a propagação de exceções.
- O re-levantamento de exceções permite tratar uma exceção e depois passá-la para um nível superior.
- Sempre considere como as exceções se propagarão em sua estrutura de blocos PL/SQL.
- Apenas uma exceção pode ser tratada por vez em um bloco.
- Exceções não tratadas em blocos não aninhados resultam na terminação do programa.
Praticar com diferentes cenários ajudará você a dominar este conceito importante. Continue explorando e experimentando com propagação de exceções em suas estruturas de blocos PL/SQL para criar código mais robusto e confiável.
Este artigo abrangente sobre propagação de exceções em PL/SQL Oracle fornece uma base sólida para entender e implementar estratégias eficazes de tratamento de erros em seus programas. Lembre-se de que o tratamento adequado de exceções é fundamental para criar aplicações PL/SQL robustas e de fácil manutenção.