Através de objetos especiais o Oracle disponibiliza a execução local de instruções programadas, permitindo ao desenvolvedor deixar parte do processamento no servidor de banco de dados. Essa estratégia segue o clássico modelo “Cliente-Servidor”: a aplicação divide com o SGBD as regras de negócio, validações dos dados e processamento das informações.

Fazendo essa separação podemos diminuir a complexidade na construção do sistema e obter ganhos consideráveis de performance, pois a quantidade de comandos SQL trafegando na rede será menor. Observe alguns exemplos práticos de uso desse modelo:

1) Redução da complexidade do sistema: Uma aplicação possui um cálculo de desconto que referencia diversos valores em várias tabelas do banco de dados, obrigando a execução de vários comandos SELECT. Neste caso, podemos codificar o cálculo dentro de uma função no banco de dados; Assim, a aplicação não precisa se preocupar nos detalhes internos para obtenção do resultado, apenas chamando a função quando necessário, diminuindo sua carga de trabalho. Além disso, a execução do cálculo será mais veloz, pois os diversos comandos SELECT necessários não serão solicitados através da rede. Vale lembrar que, se o cálculo mudar, a aplicação não sofrerá alterações.

2) Ganho de performance: Uma aplicação que possui um procedimento de várias chamadas SQL economizaria banda de rede se deixasse a responsabilidade para o servidor. Digamos que a aplicação possui uma tabela que deve, em determinado momento, ser preenchida com 100 registros automáticos, para posterior atualização manual. Uma procedure armazenada no banco de dados seria mais eficaz do que 100 instruções “INSERT” emitidas pelo cliente.

Outro exemplo seria uma aplicação de vendas que, para cada pedido confirmado, deve debitar os produtos solicitados do estoque e emitir uma cobrança. Em vez de a aplicação emitir um SQL para confirmar o pedido, vários SQL para debitar os produtos do estoque e um SQL para emitir a cobrança, poderíamos empacotar tudo isso no servidor, reduzindo o trabalho do cliente à apenas uma chamada ao procedimento.

3) Consistência do banco de dados: Uma empresa necessita que toda atualização realizada em uma tabela deve gerar um log contendo horário e nome do usuário que modificou o registro. Um objeto trigger do tipo after update garantiria que qualquer atualização feita com sucesso sobre a tabela, independente da aplicação que modificou os dados, geraria o histórico.

Os casos descritos são apenas um exemplo de como o processamento de instruções no servidor pode trazer benefícios. As instruções podem ser armazenadas em objetos do Oracle, como Stored Procedures, Functions, Triggers e Packages. Este artigo não focará na construção desses objetos; veremos apenas uma introdução à sintaxe da linguagem PL/SQL, utilizada para construção dos mesmos.

Nota: A partir do Oracle 8 é possível criar procedimentos também utilizando a linguagem Java.

Versao Oracle

Funcionalidade

< 6.0

PRO*C – Códigos SQL dentro de código C. O código era pré-compilado pra converter os comandos SQL em chamadas de bibliotecas.

6.0

Primeira versão da PL/SQL – 1.0.

7.0

Versão 2.0. Inclusão de tipos definidos pelo usuário.

7.1

Versão 2.1. Criação de SQL dinâmico usando o package DBMS_SQL. Possibilidade de utilizar funções definidas pelo usuário.

7.2

Versão 2.2. Introdução de variáveiscursor.

7.3

Versão 2.3. Melhorias no uso decursores. Funcionalidades I/O com a package UTL_FILE.

8.0

Versão 8.0. Introdução de Colletion Types, Procedures externas e melhorias no uso de objetos LOB.

9i

PL/SQL como WebServices.

Tabela 1 – Evolução do PL/SQL

Diferente de outras linguagens de programação, o foco da PL/SQL é a manipulação de grande volume de dados dentro do próprio SGBD, não possuindo, dessa forma, instruções que possibilitem a implementação de interfaces amigáveis com o usuário final, já que não é a sua finalidade.

A PL/SQL (Procedural Language/SQL) é uma linguagem procedural, que estende as funcionalidades previstas na SQL (Structured Query Language), combinando a usabilidade de uma linguagem de 4ª geração com a flexibilidade de uma linguagem de 3ª geração. Como exemplo disso, observe o comando abaixo:

DELETE FROM CLIENTES
 WHERE CODIGO < 100

Dizemos que este código pertence à uma linguagem de quarta geração pois a sintaxe apenas indica o que vai ser feito, sem especificar “como”. Uma linguagem de terceira geração, como “C”, funciona de forma contrária; precisamos dizer “como” as operações serão feitas. O exemplo acima em uma linguagem de terceira geração imaginária ficaria semelhante a listagem:

LOOP over each CLIENTES Record
   IF this Record has codigo < 100 THEN
       DELETE  this Record;
   END IF;
 END LOOP;

Uma linguagem de quarta geração fornece maior praticidade, pois precisa de poucos comandos. Já uma linguagem de terceira geração disponibiliza poder ao desenvolvedor, deixando todo o controle em suas mãos. A PL/SQL combina os dois tipos de linguagem, dando ao SQL a flexibilidade de uma linguagem de terceira geração, sem perder a simplicidade da quarta geração. Por isso a PL/SQL é uma linguagem tão adotada no mundo dos servidores de bancos de dados relacionais.

SINTAXE DA PL/SQL

A programação em PL/SQL é feita em blocos estruturados. O formato básico de um bloco de programação é apresentado na listagem 1.

<h1 align="left">DECLARE
          /* Seção de Declaração */
 <h1 align="left">BEGIN
 /* Seção de Execução*/
 EXCEPTION
 /* Seção de Exceção */
END;

Listagem 1 – Estrutura de um bloco PL/SQL

As três seções consistem de:

DECLARE: Onde devemos declarar as variáveis, constantes e cursores que serão utilizados pelo código PL/SQL. A seção é opcional, visto que podemos ter códigos que não usem variáveis.

BEGIN: Seção de implementação, onde o código PL/SQL propriamente dito será escrito.

EXCEPTION: Seção de tratamento de exceções.

Veja um exemplo de código PL/SQL:

DECLARE
          Lado INTEGER(5);
          Area  INTEGER(10);
 BEGIN
          Lado := 10;
          Area := Lado * 2;
          INSERT INTO quadrado VALUES (Lado,Area);
 END;

Para testar código, utilize o SQL*Plus. O caracter “.” indica o final do bloco PL/SQL e o caracter “/” executa a instrução. Após a inserção deste comando, o Oracle responde com o texto :

PL/SQL procedure successfully completed.

Podemos confirmar a execução do comando realizando uma consulta à tabela QUADRADO:

SELECT * FROM quadrado;
 LADO            AREA
 ------------   ----------------
 10                                        20

Tela de Login no SQL*Plus

[Figura – Tela de Login no SQL*Plus]

Se o banco para conexão for um Personal Local, a string do host pode estar em branco.

 PL/SQL executado no SQL*Plus

[figura – PL/SQL executado no SQL*Plus]

NOTA: Os códigos PL/SQL demonstrados nesse artigo devem ser executados no SQL*Plus. Não é possível executar blocos PL/SQL diretamente por aplicações clientes. Para isso, o bloco deve ser compilado em objetos como Functions ou Stored Procedures. Esse assunto será visto nas próximas edições da SQL Magazine.

NOTA: SQL*Plus é o software que permite a execução manual de instruções SQL no Oracle. Ele é instalado juntamente com o servidor de banco de dados.

Vejamos outro exemplo:

DECLARE
          Pi       Constant NUMBER (9,7) := 3.1415926;
          Raio    INTEGER(5);
          Area   NUMBER(14,2);
 BEGIN
          Raio := 3;
          Area := pi * power(raio,2);
          INSERT INTO circulo VALUES (Raio,Area);
 END;

Neste código vemos o uso do tipo FLOAT e a criação de constantes. A função power, definida no Oracle, retorna a potência indicada pelo segundo parâmetro.

Estruturas de controle e de Laço

Assim como em uma linguagem de terceira geração, na PL/SQL também podemos criar controle condicionais e laços de repetição. A sintaxe para uso do comando IF é:

IF THEN 
 /*código PL/SQL*/
  [ ELSIF 
 /*código PL/SQL*/ ]
 [ ELSE 
 /* código PL/SQL*/ ]
 END IF;
 

Exemplos:

IF var_Velocidade > 80 THEN
    UPDATE multa SET valor = 514 WHERE codmotorista=10;
 END IF;
 IF var_Media >= 5 THEN
    UPDATE aluno SET status = ‘APROVADO’ WHERE codaluno=5;
 ELSE
    UPDATE aluno SET status = ‘REPROVADO’ WHERE codaluno=5;
 END IF;

Podemos construir um laço de repetição de três formas: com LOOP, WHILE ou FOR. Veja a sintaxe:

LOOP
  /*código PL/SQL*/
EXIT WHEN 
   /*código PL/SQL*/
END LOOP;
 
WHILE LOOP
 /*código PL/SQL*/
 END LOOP;
 FOR IN [reverse] ..
 LOOP
 /*código PL/SQL*/
 END LOOP;

Exemplos de construção de laço de repetição:

DECLARE
    X   INTEGER(5);
     MENSAGEM   VARCHAR2(10);
  
 BEGIN
   X := 1;
  
   LOOP
     X := X + 1; 
    EXIT WHEN X = 30
    
     IF MOD(X,2) = 0 THEN
        MENSAGEM := ‘ é par’;
     ELSE
        MENSAGEM := ‘ é impar’;
     END IF;
  
      INSERT INTO tabelamensagem (NUMERO, DESCRICAO) VALUES (X, MENSAGEM);   
   END LOOP;
 END;

O LOOP é executado até que a condição descrita no comando EXIT WHEN seja satisfeita. A estrutura garante que os comandos antes de EXIT WHEN sejam executados pelo menos uma vez. No exemplo, o número 30 não será cadastrado em “tabelamensagem”, pois o LOOP será abandonado no comando EXIT, antes de alcançar a instrução INSERT.

Uso do While:

DECLARE
    X   INTEGER(5);
     MENSAGEM   VARCHAR2(10);
  
 BEGIN
    X:=1

  
    WHILE X < 30 LOOP
       IF MOD(X,2) = 0 THEN
           MENSAGEM := ‘ é par’;
       ELSE
           MENSAGEM := ‘ é impar’;
       END IF;
   
        INSERT INTO tabelamensagem (NUMERO, DESCRICAO) VALUES (X, MENSAGEM );
    
        X := X + 1;
     END LOOP;
 END;

A instrução WHILE é repetida enquanto a condição for verdadeira. A diferença do WHILE é que, se a condição iniciar falsa, nenhuma instrução do laço será executada.

O comando FOR é um pouco diferente dos anteriores. Vejamos:

DECLARE
    mensagem  VARCHAR(30);
    Max_value   INTEGER(5);
 BEGIN
   Max_Value := 10;
   FOR contador IN 1..Max_value LOOP
  
     mensagem := ‘INSERINDO A LINHA Nº ‘ || contador;
     INSERT INTO tabelamensagem (NUMERO, DESCRICAO) VALUES (contador, mensagem);
  
   END LOOP;
 END;

Observe que a variável contador não foi declarada. Essa é uma particularidade do laço FOR; a variável contadora é implicitamente instanciada, com escopo interno ao laço, não permanecendo disponível fora dele. Outro detalhe é o uso do símbolo “||”, sintaxe para concatenação na SQL.

SELECT INTO

Através da palavra-chave INTO podemos atribuir o valor retornado por um SELECT à variáveis PL/SQL. A sintaxe de SELECT INTO é descrita como:

SELECT INTO FROM   [WHERE ]
 

Veja o exemplo:

DECLARE
          vCodigo        INTEGER(10);
          vSalario        NUMBER (14,2);
 BEGIN
          SELECT codigo, salario INTO vCodigo, vSalario 
 FROM funcionarios
 WHERE codmatricula = 1000;
  
 INSERT INTO pagamento_funcionario (cod_funcionario, data, valor)
      VALUES (vCodigo, SYSDATE, vSalario);
          END;

Neste bloco, as variáveis vCodigo e vSalario recebem os valores dos campos Codigo e Salario do funcionário cuja matrícula é igual à 1000. Em seguida é cadastrada uma ocorrência de pagamento para o funcionário selecionado, registrando o codigo, a data e o valor pago. A data atual é recuperada através da função SYSDATE do Oracle.

Observe que o SELECT deve retornar apenas uma linha para que o código funcione corretamente. Caso o comando retorne mais de um registro, a execução será abortada com a mensagem de erro:

ORA-1427: Single-row query returns more than one row

Para trabalhar com uma query que retorne múltiplos registros devemos utilizar os Cursores, mostrados mais adiante, neste artigo.

O comando acima também poderia ser escrito no formato:

DECLARE
             VCodigo         funcionarios.codigo%TYPE;
             VSalario         funcionarios.salario%TYPE;
 BEGIN
          SELECT CODIGO,SALARIO INTO vCodigo, vSalario 
 FROM funcionarios
 WHERE codigo = 1000;
  
 INSERT INTO pagamento_funcionario(COD_FUNCIONARIO, DATA, VALOR)
 VALUES (vCodigo, SYSDATE, vSalario);
          END;

Observe que a diferença está na declaração das variáveis:

DECLARE
          vCodigo        funcionarios.codigo%TYPE;
          vSalario        funcionarios.salario%TYPE;
 

Nesta sintaxe atribuímos à variável o tipo do campo indicado. A sintaxe completa é escrita como:

Nome_da_variavel    nome_da_tabela.nome_do_campo%TYPE;

Por exemplo, se o campo codigo da tabela de funcionários é do tipo Integer, a variável vCodigo também será Integer. A vantagem desta sintaxe é que a definição dos tipos de variáveis fica independente dos tipos de campos utilizados. Se futuramente o administrador mudar o tipo do campo codigo para NUMBER, não será necessário reescrever todos os blocos PL/SQL que o referenciam.

Outra cláusula para definição de tipos é %ROWTYPE. Com ela, podemos dizer que uma variável PL/SQL é do tipo registro. Observe o exemplo reescrito:

DECLARE
             vInfo_Funcionario  funcionarios%ROWTYPE;
 BEGIN
          SELECT * INTO vInfo_Funcionario
     FROM funcionarios
     WHERE codigo = 1000;
  
 INSERT INTO pagamento_funcionario(COD_FUNCIONARIO, DATA, VALOR)
    VALUES (vInfo_Funcionario.codigo, SYSDATE, vInfo_funcionario.Salario);
          END;

Dessa forma temos uma versão resumida do exemplo. Para acessar os campos do registro recuperado, basta utilizar a sintaxe variavel_registro.nome_do_campo.

Trabalhando com Cursores

Cursor é um dos recursos mais poderosos na PL/SQL. Através dele, podemos recuperar um recordset utilizando um comando SELECT e fazer a navegação entre os múltiplos registros como se estivéssemos em uma linguagem de terceira geração. Internamente, o cursor é um ponteiro para uma área de memória especial, conhecida como context area, utilizada pelo Oracle para controlar e processar um comando SQL. Entre as informações armazenadas na context area estão o número de linhas processadas, um ponteiro para o comando SQL interpretado e um ponteiro para os dados retornados pela query. Observe um exemplo do uso de Cursor:

DECLARE
 CURSOR c_funcionarios IS
     SELECT * FROM funcionarios;
  
 VInfo_Funcionario c_funcionarios%ROWTYPE;
 BEGIN
          --Abre o cursor
          OPEN c_funcionarios;
  
          LOOP
                    --Lê um registro do cursor
                    FETCH  c_funcionarios INTO vInfo_Funcionario;
  
                    --Sai do Loop caso seja o final do cursor
                    EXIT WHEN c_funcionarios%NOTFOUND;
  
                    -- insere o pagamento do funcionario
                    INSERT INTO pagamento_funcionario (cod_funcionario,data,valor) 
                     VALUES (vInfo_Funcionario.codigo, SYSDATE, vInfo_Funcionario.salario);
          END LOOP;
  
          --Fecha o cursor;
          CLOSE c_funcionarios;                 
 END;
 

Vejamos a sintaxe detalhada deste exemplo. Primeiramente, declaramos uma variável do tipo Cursor:

CURSOR c_funcionarios IS
     SELECT * FROM funcionarios;
 

A sintaxe para declaração de uma variável cursor é:

CURSOR nome_do_cursor IS comando_select;

Em seguida, criamos uma variável para referenciar o registro retornado pelo cursor:

VInfo_Funcionario c_funcionarios%ROWTYPE;

Poderíamos criar variáveis de tipos comuns e atribuir a essas variáveis os campos do cursor. No entanto, para evitar a manutenção no código caso o tipo do campo seja alterado no futuro, podemos utilizar o recurso %ROWTYPE.

Na implementação do cursor, a sintaxe que deve ser utilizada é

OPEN nome_do_cursor;
 Abre o cursor
 FETCH nome_do_cursor INTO variavel1, variavel2,...
 Lê um registro do cursor e movimenta o ponteiro para o próximo registro
  
 CLOSE nome_do_cursor;
 Fecha o cursor
 

No exemplo, fazemos um loop pela tabela de funcionários, incluindo uma ocorrência de pagamento para cada registro lido:

         LOOP
                    FETCH  c_funcionarios INTO vInfo_Funcionario;
  
                    EXIT WHEN c_funcionários%NOTFOUND;
  
                    INSERT INTO pagamento_funcionario (cod_funcionario,data,valor) 
                                    VALUES (vInfo_Funcionario.codigo, SYSDATE, vInfo_Funcionario.salario);
          END LOOP;
 

A palavra %NOTFOUND retorna TRUE quando o ponteiro de navegação atinge o final do cursor. Dessa forma, o Loop será executado enquanto o ponteiro não chegar ao final do Dataset. Existem ainda outros atributos que podem ser utilizados no uso de cursores:

  • %FOUND – Retorna TRUE se o último FETCH realizado retornou registros com sucesso. Por exemplo, se um FETCH for solicitado depois que o ponteiro atingir o último registro, o valor de %FOUND será FALSE (e o valor de %NOTFOUND será TRUE).
  • %ISOPEN – Retorna TRUE se o cursor estiver aberto.
  • %ROWCOUNT – Retorna a quantidade de FETCHes realizados. Para cada FETCH, o valor de ROWCOUNT é incrementado em 1.

Observe mais um exemplo de uso de Cursor:

DECLARE
          vLado quadrado.lado%TYPE;
          vArea  quadrado.area%TYPE;
  
          CURSOR c_quadrado IS
              SELECT lado, area FROM quadrado 
 WHERE  area > 50;
 BEGIN
    Open c_quadrado;
  
    LOOP 
          FETCH c_quadrado INTO  vLado, vArea;
          EXIT WHEN c_quadrado%NOTFOUND;
   
          INSERT INTO quadrado_temp VALUES(vLado,vArea);      
    END LOOP;      
  
    Close c_quadrado;
 END;
 

Neste exemplo inserimos em uma tabela todos os quadrados com área maior que 50. A diferença aqui está na declaração das variáveis, pois desta vez não utilizamos uma variável do tipo registro:

         vLado quadrado.lado%TYPE;
          vArea  quadrado.area%TYPE;

O comando FETCH deve solicitar a mesma quantidade de colunas disponibilizadas pelo cursor. O FETCH abaixo retornaria um erro do Oracle:

FETCH  c_quadrado INTO vLado

O cursor c_quadrado disponibiliza dois campos e o comando está realizando um INTO para apenas uma variável. A execução dessa linha dispara um erro do tipo:

PLS-394: wrong number of values in the INTO list of a FETCH statement

Vejamos mais um exemplo:

DECLARE 
     CURSOR c_quadrado  IS
          SELECT * from quadrado
              WHERE   area > 50;
  
    vReg_Quadrado c_quadrado%ROWTIPE;
 BEGIN
     OPEN c_quadrado;
  
    FETCH c_quadrado INTO vReg_Quadrado;
    WHILE  c_quadrado%FOUND  LOOP
  
           INSERT INTO quadrado_temp VALUES(vReg_Quadrado.lado,vReg_Quadrado.area);   
           FETCH c_quadrado INTO vReg_quadrado;
    END LOOP;
  
     CLOSE c_quadrado;
 END;

A diferença está no uso do While. Repare que utilizamos %FOUND ao invés de %NOTFOUND, pois o While trabalha enquanto a condição for verdadeira. O comando FETCH teve que ser chamado duas vezes, para que o primeiro loop de While não acusasse %FOUND como False.

Existe ainda outra forma, mais simples, de usar loops com cursores. O comando FOR reduz o código necessário para essa tarefa. Observe a listagem a seguir:

DECLARE
    CURSOR c_quadrado IS
       SELECT * FROM quadrado
       WHERE area > 50 ; 
 BEGIN
    
    FOR  vReg_quadrado IN c_quadrado LOOP
        INSERT INTO quadrado_temp VALUES (vReg_Quadrado.lado,vReg_Quadrado.area);     
    END LOOP;
  
 END;   
 

O comando FOR abre, lê e fecha o cursor automaticamente. Quando o primeiro laço do FOR é executado, o cursor é aberto e o primeiro FETCH é realizado, de forma implícita, para a variável vReg_quadrado. A seguir, cada laço do FOR realiza um novo FETCH e automaticamente testa a variável %FOUND, para verificar se ainda restam registros não lidos. Quando o valor dessa variável se torna falso o cursor é fechado e o loop é abandonado. Observe também que a variável vReg_quadrado não foi declarada; Da mesma forma que no comando FOR com valores numéricos visto anteriormente, a variável contadora é internamente criada. No uso com cursores, a variável será sempre do tipo %ROWTIPE.

Não há regras sobre o melhor tipo de instrução para construção de laço, ficando totalmente a critério do desenvolvedor.

Cursores Implícitos

Os cursores demonstrados na seção anterior são conhecidos como cursores explícitos, pois são referenciados por uma variável e por um nome. No entanto, todos os comandos SQL processados pelo servidor criam um cursor interno, conhecido como cursor SQL. Esse tipo de cursor é implicitamente aberto e fechado pelo Oracle, permitindo ao código PL/SQL utilizá-lo sem criar uma variável para isso. Veja o exemplo abaixo:

BEGIN
          UPDATE  quadrado
                      SET area=20
                WHERE lado=10;
  
          --O cursor SQL é criado durante a execução do comando UPDATE
          IF SQL%NOTFOUND THEN
                       INSERT INTO quadrado (area,lado) VALUES (20,10);
                END IF;
 END;

Neste bloco, se não houver nenhum quadrado com lado igual a 10, um registro será inserido com este valor. O mesmo cursor poderia ser escrito da forma abaixo:

BEGIN
          UPDATE  quadrado
                      SET area=20
                WHERE lado=10;
  
          IF SQL%ROWCOUNT=0 THEN
                       INSERT INTO quadrado (area,lado) VALUES (20,10);
                END IF;
 END;

Observe que não é necessário abrir o cursor SQL, pois o Oracle já cuida disso internamente.

Tratamento de exceções

Dentro de um bloco PL/SQL algumas instruções podem disparar exceções, como violação de chave primária, deleção de registros sem permissão, entre outras. O bloco PL/SQL passível de erro pode ser tratado através da seção EXCEPTION. Observe o exemplo abaixo:

DECLARE
     DIV INTEGER(5) := 6;
     X INTEGER(5);
     Y NUMBER(14,2);
 BEGIN
     X := 0;
     Y := DIV /  X;
 END;

A tentativa de execução deste bloco gera o erro:

ORA-01476: divisor is equal do zero

Para proteger o bloco, a seção EXCEPTION é implementada conforme a listagem:

DECLARE
     DIV INTEGER(5) := 6;
     X INTEGER(5);
     Y NUMBER(14,2);
 BEGIN
     X := 0;
     Y := DIV /  X;
  
 EXCEPTION
     WHEN OTHERS THEN
          INSERT INTO log_erro (mensagem) VALUES (‘Operação inválida’);
 END;

WHEN OTHERS THEN indica que, para qualquer erro gerado a seção será executada. Podemos, no entanto, definir validações mais específicas. Se quiséssemos chamar a exceção somente quando o erro fosse de divisão por zero, poderíamos escrever da seguinte forma:

EXCEPTION
     WHEN ZERO_DIVIDE THEN
          INSERT INTO log_erro (mensagem) VALUES (‘Divisão por zero’);

Observe mais um exemplo:

DECLARE
     DIV INTEGER(5) := 6;
     X INTEGER(5);
     Y NUMBER(14,2);
 BEGIN
  
     SELECT lado INTO X FROM quadrado WHERE area = 0;      
     Y := DIV /  X;
  
 EXCEPTION
    WHEN NO_DATA_FOUND THEN
          INSERT INTO log_erro (mensagem) VALUES (‘Nenhum registro encontrado’);    
     WHEN ZERO_DIVIDE THEN
          INSERT INTO log_erro (mensagem) VALUES (‘Divisão por zero’);
     WHEN OTHERS THEN
          INSERT INTO log_erro (mensagem) VALUES (‘Operação inválida’);
  
 END;

Podemos ter vários tratamentos dentro do mesmo bloco de exceção. A cláusula NO_DATA_FOUND é utilizada para possíveis erros no comando SELECT INTO. Quando o SELECT não retornar nenhum registro para a variável descrita em INTO, a exceção NO_DATA_FOUND será disparada. Observe que esta exceção não é gerada quando utilizamos cursores. Neste caso, devemos verificar as variáveis %FOUND e %NOTFOUND. A cláusula WHEN OTHERS THEN será executada se o erro não for compatível com NO_DATA_FOUND e ZERO_DIVIDE

A execução do procedimento é automaticamente abortada quando uma exceção é gerada. Se a exceção estiver tratada em um bloco EXCEPTION, todo o código escrito após a linha que gerou o erro não será executado, passando o controle para a seção de tratamento.

Se a exceção não for tratada no bloco EXCEPTION, o erro será propagado para a aplicação cliente. Neste caso, cabe a aplicação capturar o erro e fazer o tratamento para o usuário.

Geração de erros

Uma exceção também pode ser gerada pelo desenvolvedor, através do comando RAISE. Veja o exemplo:

DECLARE
     E_quadrado EXCEPTION;
     Qtd_quadrado INTEGER(5); 
 BEGIN
     SELECT COUNT(*) INTO qtd_quadrado 
         FROM quadrado WHERE area > 50;
  
     IF qtd_quadrado > 10 THEN
         RAISE  e_quadrado;
     END IF;
  
 EXCEPTION
     WHEN  NO_DATA_FOUND OR e_quadrado THEN
          INSERT INTO log_erro (mensagem) VALUES (‘A quantidade de quadrados precisa estar entre 1 e 10’);
  

 END;

Se o comando RAISE não for tratado, ele também será ecoado para a aplicação, permitindo a criação de exceções personalizadas. Para enviar uma string de erro, podemos utilizar a sintaxe abaixo:

DECLARE
     E_quadrado EXCEPTION;
     Qtd_quadrado INTEGER(5); 
 BEGIN
     SELECT COUNT(*) INTO qtd_quadrado 
         FROM quadrado WHERE area > 50;
  
     IF qtd_quadrado > 10 THEN
         RAISE_APPLICATION_ERROR(-20000,’Muitos quadrados foram selecionados’);
     END IF;
  
 END;

Os parâmetros de RAISE_APPLICATION_ERROR são o código do erro e a string que será passada para a aplicação. O código do erro deve ser um número de erro não utilizado pelo Oracle. Consulte a referência do banco para verificar os números disponíveis.

Conclusão

Não vimos um exemplo real do uso de PL/SQL neste artigo, já que não construímos nenhum objeto para acesso através de uma aplicação cliente. Os códigos apresentados só podem ser executados no SQL*Plus, tornando a matéria puramente didática. Vimos aqui apenas uma introdução à sintaxe dos comandos básicos da linguagem, visto que o assunto é bastante extenso e existem livros inteiros dedicados a esmiuçar a PL/SQL. Esperamos que agora o leitor tenha o “caminho das pedras” para se aventurar nesta poderosa linguagem. Na próxima edição veremos mais sobre PL/SQL, não perca!