Neste artigo vou abordar as melhores práticas de programação em Pl/SQL. Não existe forma correta ou errada de se programar, desde que os requisitos sejam atendidos. O que existe são práticas que podemos utilizar para minimizar erros, aumentar o entendimento do código, melhorar o desempenho e a aplicação, aumentar a produtividade, diminuir possíveis vulnerabilidades e permitir a continuidade do trabalho.
1. Utilizando a tabela DUAL
Émuito comum vermos em blocos Pl/SQL a utilização de query na tabela dual para execução de funções:
Declare
V_data date;
V_usuario varchar2(30);
V_result varchar2(2);
V_param varchar2(2);
begin
Select sysdate
Into v_data
From dual;
Select user
Into v_usuario
From dual;
Select decode(v_param,'SP','BR','EX')
Into v_result
From dual;
End;
Evite este tipo de coisa, fazer query na tabela dual para execução de uma função gera um parse e um execute desnecessário no banco de dados. A melhor forma de executar uma função é chamá-la diretamente:
Declare
V_data date;
V_usuario varchar2(30);
V_result varchar2(2);
V_param varchar2(2);
Begin
V_data := sysdate;
V_ususario := user;
If(v_param = 'SP')then
V_result := 'BR';
Else
V_result := 'EX';
End if;
End;
2. Contando registros
Outro erro comum é a utilização ordenação em query de contagem de registros.
Select count(*)
From tb_movimento
Order by id_movimento;
Não há sentido de utilizar um order by em contagem de registros, o resultado da contagem é o mesmo independente da ordenação. Quando encontrado um comando de ordenação, o banco de dados utiliza a tablespace temporária para ordenar os dados, tornando a query mais lenta:
Select count(*)
From tb_movimento
Nunca, mas nunca, faça estruturas condicionais utilizando a tabela dual:
declare
v_a boolean;
v_b number := 1;
v_c number := 2;
begin
select true
into v_a
from dual
where v_b > v_c
exception when no_data_found then
v_a := false;
end;
Forma correta:
declare
v_a boolean;
v_b number := 1;
v_c number := 2;
begin
if(v_b > v_c)then
v_a := true;
else
v_a := false;
end if;
end;
3. Tratamentos de exceções necessários e desnecessários
Émuito comum esquecer de tratar as exceções ocorridas no processo. Mas tratar exceções desnecessárias também torna o código confuso e muitas vezes ineficiente. Certamente você já viu isso:
Declare
V_result varchar2(2);
V_param varchar2(2);
Begin
Select decode(v_param, 'SP', 'BR', 'EX') Into v_result From dual;
Exception
when no_data_found then
Raise_applcation_error(-20001,
'Nenhum registro encontrado na tabela dual');
When too_many_rows then
Raise_applcation_error(-20002,
'Mais de um registro encontrado na tabela dual');
End;
Utilizar tratamento de exceções na tabela dual é muito preciosismo, comandos DML na tabela dual só podem ser executados pelo usuário SYS. Logo, concluímos que é quase impossível de fazer um delete ou insert nesta tabela. Caso isso ocorra é mais fácil resolver o problema na raiz.
Tratar exceções que estruturalmente não ocorrerão:
create table tb_tecnico(id_tecnico number(8) primary key,
nome varchar2(40));
create or replace fnc_cad_nome_tecnico(p_id_tecnico in tb_tecnico.id_tecnico%type)
return tb_tecnico.nome%type
is
v_nome tb_tecnico.nome%type;
begin
select t.nome
into v_nome
from tb_tecnico t
where t.id_tecnico = p_id_tecnico;
return v_nome;
exception when no_data_found then
raise_application_error(-20001,'Tecnico não encontrado');
when too_many_rows then
raise_application_error(-20002,'Mais de um técnico encontrado');
end fnc_cad_nome_tecnico;
Neste caso acima, o tratamento de too_many_rows é desnecessário, pois o campo base para retorno da consulta é a chave primária da tabela, que nunca irá se repetir. Logo, é impossível de retornar mais que um registro.
No caso abaixo simulo um erro de valor muito grande para precisão da variável:
declare
v_a number(6) := 999999;
v_b number(8) := 99999999;
v_total number(6);
e_long_value exception;
pragma exception_init(e_long_value,-06502);
begin
v_total := v_a + v_b;
exception when e_long_value then
v_total := 0;
end;
Na verdade, a forma mais simples de resolver este problema seria aumentar a precisão da variável total para number(9). Tomando como regra que uma variável totalizadora deve ter sua precisão igual ou maior que a precisão maior variável somada + 1.
declare
v_a number(6) := 999999;
v_b number(8) := 99999999;
v_total number(9);
begin
v_total := v_a + v_b;
end;
4. Declaração de parâmetros e variáveis
Para parâmetros ou variáveis que representam colunas de tabelas, prefira declarar seu type utilizando a herança do mesmo tipo da tabela que representa:
Declare
v_qtd_movimento tb_movimento.qtd_movimento%type;
v_local_estoque tb_local_estoque.local_estoque%type;
begin
select m.qtd_movimento,l.local_estoque
into v_qtd_movimento, v_local_estoque
from tb_movimento m
where m.id_movimento = 1
and m.id_local_estoque = l.id_local_estoque;
End;
Create or replace Function fnc_fin_vlr_titulo(p_id_titulo in tb_titulo.id_titulo%type)
Return tb_titulo.vlr_titulo%type
Is
Vlr_titulo tb_titulo.vlr_titulo%type;
Begin
Select vlr_titulo
Into vlr_titulo
From tb_titulo
Where id_titulo = p_id_titulo;
Return vlr_titulo;
End fnc_fin_vlr_titulo;
Para representar todas as colunas de uma tabela é interessante utilizar a herança de linha:
Declare
V_movimento tb_movimento%rowtype;
V_pessoa tb_pessoa%rowtype;
Begin
Select m.*
Into v_movimento
From tb_movimento m
Where m.id_movimento = 1;
Select p.*
Into v_pessoa
From tb_pessoa p
Where p.id_pessoa = 32;
End;
create or replace function fnc_titulo(p_id_titulo in tb_titulo.id_titulo%type)
return tb_titulo%rowtype
is
v_titulo tb_titulo%rowtype;
is
begin
select t.*
into v_titulo
from tb_titulo t
where t.id_titulo = p_id_titulo;
return v_titulo;
end fnc_titulo;
Deste modo, caso ocorra uma alteração na estrutura da tabela, as variáveis também herdaram as alterações.
5. Crie prefixos para os objetos
É de muita ajuda a utilização de prefixos para identificar a qual módulo o objeto pertence, e qual o seu tipo:
Objeto | Modulo | Tipo | Objetivo | |
---|---|---|---|---|
Pck_est_movimento | Package | Efetuar | Movimento de estoque | |
Vw_cad_pessoa | Cadastros | View | Listar as pessoas cadastradas | |
Fnc_fin_vlr_titulo | Financeiro | Função | Retornar o valor de um título | |
Typ_cad_veiculo | Cadastros | Type | Estrutura referente a um veículo | |
E_long_value | Genérico | Exception | Exception referente ao erro -06502, valor muito grande | |
Prc_cad_atu_veiculo | Cadastros | Procedure | Atualizar o cadastro de um veículo | |
Tb_estado | Cadastros | Tabela | Informações do estado | |
Tb_veiculo.Ds_veiculo | Cadastros | Coluna | Descrição do veículo |
Para nomenclatura de um sinônimo, pode-se utilizar o mesmo nome do objeto que ele representa ou informar um novo nome a ele.
create or replace synonym tb_veiculo for teste.tb_veiculo;
Ou
create or replace synonym tb_veiculo for teste.tb_veiculo;
Para nomenclatura de triggers, pode-se utilizar o prefixo do evento que aciona a mesma, e pode-se suprimir o módulo a que a mesma pertence (irá herdar o modulo da tabela), por exemplo:
De modo geral:
Sigla | Momento |
---|---|
A | After |
B | Before |
Sigla | Evento |
---|---|
I | Inset |
U | Update |
D | Delete |
Por exemplo:
Objeto | Prefixo | Evento | Descrição |
---|---|---|---|
BI_CALC_NOTA | BI | Before Insert | Antes do Insert |
BU_LOG_VEICULO | BU | Before Update | Antes do Update |
BD_VALIDA_REFERENCIA | BD | Before Delete | Antes do Delete |
AIUD_LOG_VEICULO | AIUD | After Insert, Update e Delete | Depois do insert, update ou delete |
AD_CRIA_GENERICO | AD | After Delete | Depois do delete |
6. Passagem de parametros
Existem duas formas para passar parâmetros entre objetos:
Referenciada, quando o parâmetro que receberá a informação está identificado. Posicional, quando o parâmetro que receberá a informação é identificado por sua posição na declaração. Por exemplo, temos a seguinte declaração abaixo:
Procedure prc_fin_calcula_iss(p_id_pessoa in number,
P_dt_base in date,
P_vlr_total in number,
P_id_aliquota in number);
Veja que utilizando a passagem identificada, é possível visualizar que valor cada parâmetro recebe. Ferramentas case podem ajudar a montar a chamada dos objetos com parâmetros identificados.
Declare
Begin
Prc_fin_calcula_iss(p_id_pessoa => 3356,
P_dt_base => null,
P_vlr_total => 1000.00,
P_id_aliquota => 98);
End;
Utilizando a passagem por posição, não é possível visualizar qual valor refere-se a qual parâmetro. Caso precise de um novo parâmetro no procedimento, o desenvolvedor deverá, sempre, colocá-lo por último na declaração do objeto.
Declare
Begin
Prc_fin_calcula_iss(3356, null, 1000.00, 98);
End;
Ambas formas realizam a chamada com a mesma eficiência, mas por questão de entendimento do código, prefira utilizar a passagem por referência.
Falaremos de outras situações de boas práticas no próximo artigo.
Abraços.