Os operadores são fundamentais em qualquer linguagem de Programação e até mesmo no dia a dia. Como viver sem os operadores de Adição (+), igualdade (=), etc..
Porém, a Oracle disponibiliza desde a versão 8i o conceito de Operadores customizados.
Basicamente, um operador customizado é uma forma de esconder a complexidade de uma regra de negócio através de um Operador (Similar ao que é feito com a utilização das views). Para quem já estava acostumado a utilizar funções como operadores justamente com o objetivo de garantir a utilização das regras de negócio definidas como padrão, não vai ter muitas dificuldades em se adaptar a este conceito.
A utilização do Operador (Ou da função simulando o operador) exige um certo nível de maturidade do desenvolvimento, pois a correta documentação e utilização dos operadores definidos é fundamental para que o recurso gere ganhos no projeto, especialmente, os ganhos relacionados a manutenibilidade do aplicativo.
Bom, vamos à demonstração do recurso. Imagine o seguinte cenário:
Um processo onde um pedido, depois de inserido, precisa ser validado para se considerado um pedido valido e precisa pertencer a uma pessoa Jurídica e estar registrado no dia de Ontem (O processo valida automaticamente todos os pedidos depois da meia noite);
Então vamos criar esta estrutura para atender o requisito:
create table clientes (id number primary key ,nome varchar2(30) ,endereco varchar2(100) ,tipo varchar2(1)); create table pedido (id number ,data date ,valor number ,cliente_id number ,constraint fk_ref foreign key (cliente_id) references clientes (id)); create sequence pedido_seq;
Agora, vamos alimentar esta estrutura com uma massa de dados para testes:
DECLARE
v_tipo varchar2(1);
v_data date;
BEGIN
<<loop_pedido>>
FOR x IN 1..17 LOOP
----------------------------------
--Definindo o tipo
----------------------------------
IF MOD(x,2) = 0 THEN
v_tipo := 'F'; --Pessoa Fisica
ELSE
v_tipo := 'J'; --Pessoa Juridica
END IF;
----------------------------------
--Inserindo o registro
----------------------------------
insert into clientes (id,nome,endereco,tipo)
values (x,'Cliente '||x,'Endereço do cliente '||x, v_tipo);
----------------------------------
--Inserindo o pedido
----------------------------------
<<loop_data>>
FOR y IN 1..5 LOOP
-------------------------------
--Definindo a data do pedido
-------------------------------
v_data := TRUNC(sysdate-(y-1));
-------------------------------
--Definindo a data do pedido
-------------------------------
<<loop_cliente>>
FOR z IN REVERSE 1..5 LOOP
insert into PEDIDO (id,data,valor,cliente_id)
values (PEDIDO_SEQ.nextval,v_data,X*Y*Z*10,x);
END LOOP loop_pedido;
END LOOP loop_data;
END LOOP loop_cliente;
-------------------------------------
--Efetivando a transação
-------------------------------------
commit;
END;
/
Para Identificar os Pedidos válidos de acordo com a regra acima, poderia ser utilizada a seguinte query:
select * from CLIENTES a, PEDIDO b where b.cliente_id = a.id and a.tipo = 'J' and b.data = TRUNC(sysdate-1) Veja abaixo como fica o custo desta query:
------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 45 | 5940 | 7 (15)| 00:00:01 | |* 1 | HASH JOIN | | 45 | 5940 | 7 (15)| 00:00:01 | |* 2 | TABLE ACCESS FULL| CLIENTES | 9 | 756 | 3 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| PEDIDO | 85 | 4080 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------------
Observe o volume de bytes que ela retornou, além do custo.
Agora, vamos criar a função que será a base do nosso trabalho:
create or replace function pedido_valido (p_ref in varchar2) return number is
BEGIN
DECLARE
v_tipo varchar2(1);
v_data date;
v_cliente number;
v_ret number;
BEGIN
------------------------------------------------------------------------
--Identificando o Pedido
------------------------------------------------------------------------
select data, cliente_id
into v_data, v_cliente
from PEDIDO
where rowid = p_ref;
------------------------------------------------------------------------
--checando o cliente
------------------------------------------------------------------------
IF v_data = TRUNC(sysdate-1) THEN
BEGIN
------------------------------------------------------------------
--Identificando o cliente
------------------------------------------------------------------
select tipo into v_tipo
from CLIENTES
where id = v_cliente;
------------------------------------------------------------------
--Checando o tipo
------------------------------------------------------------------
IF v_tipo = 'F' THEN
v_ret := 0;
ELSE
v_ret := 1;
END IF;
EXCEPTION
WHEN others THEN
v_ret := 0;
END;
ELSE
v_ret := 0;
END IF;
------------------------------------------------------------------------
--Retornando o resultado
------------------------------------------------------------------------
RETURN (v_ret);
END;
END;
/
Você poderia utilizar diretamente a função em sua query. Ela já serviria para ocultar a regra de negócio que envolve a definição de um pedido válido, conforme abaixo:
select * from PEDIDO where PEDIDO_VALIDO (rowid) = 1
O custo desta operação ficaria assim:
---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 4 | 240 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL | PEDIDO | 4 | 240 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------
Observe que, além da redução de custo obtido, o volume de bytes trabalhado foi menor.
Ainda existe a possibilidade de se criar um operador específico para isto, conforme abaixo:
CREATE OPERATOR ped_e_valido BINDING (VARCHAR2) RETURN NUMBER USING PEDIDO_VALIDO;
A query para consultar os pedidos validos utilizando o operador ficaria assim:
select * from PEDIDO where PED_E_VALIDO (rowid) = 1
Observe que o custo se mantém o mesmo que quando utilizado diretamente a função:
---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 SELECT STATEMENT | | 4 | 240 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL | PEDIDO | 4 | 240 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------
Uma outra forma de utilizá-lo seria na query conforme abaixo:
select id pedido_id
, case
when PED_E_VALIDO (rowid) = 1 then 'Sim'
else 'Não'
end status
from PEDIDO
O custo desta query ficaria exatamente assim:
---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 425 | 10625 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| PEDIDO | 425 | 10625 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------- Na prática, o que ele faz é justamente chamar a função, porém, você consegue ter um melhor nível de controle sobre o que é utilizado como operador, através da tabela USER_OBJECTS, conforme query abaixo:
select * from user_objects where object_type = 'OPERATOR'
Desta forma você saberia facilmente quais são os operadores que aplicam alguma regra de negócio ao sistema.
[Crédito da Imagem: Operadores Customizados – ShutterStock]