A utilização de dados contidos em uma base de dados é algo trivial em qualquer empresa. É fundamental que determinados conjuntos de dados estejam corretos, para que sua manipulação tenha real utilidade. Talvez isso seja óbvio. Se os dados estão contidos na base, temos que partir do princípio que estão corretos. Certo? Nem sempre.

É comum várias aplicações compartilharem da mesma base de dados. É primordial que os programadores realizem as validações necessárias para impedir a inserção ou atualização de dados incorretos. Nem sempre os programadores tem esse cuidado. Nesse contexto, é importante encontrarmos mecanismos para melhorar a qualidade dos dados.

Dificilmente essa preocupação vira antes da criação de uma tabela. Certamente, sua analise identificou que existem dados incorretos. Fruto da falta de validação em um dos campos de alguma aplicação. É preciso solucionar esse problema para garantir que futuramente sejam armazenados a maior quantidade de dados corretos.

“Seja criterioso com a qualidade. Algumas pessoas não estão acostumadas com um ambiente onde a excelência é esperada.” Steve Jobs

Realizaremos as manutenções e novas implementações para melhorar a qualidade dos dados na base. Irei propor uma solução para sanar o problema explanado.

Vou elucidar esse problema da seguinte forma. Existe uma tabela no banco de dados muito utilizada. A tabela "cliente". Conforme o código abaixo:

Criação da tabela


CREATE TABLE cliente(
  codcliente NUMBER(10,0) NOT NULL,
  nome       VARCHAR2(30) NOT NULL,
  cpf        CHAR(11)     NULL,
  cnpj       CHAR(14)     NULL,
  CONSTRAINT pk_cliente
    PRIMARY KEY(codcliente)
);

Várias aplicações diferentes utilizam essa mesma estrutura. Em alguma aplicação, um programador não validou os campos "cpf" e "cnpj". Ou validou de forma parcial. Permitindo o usuário modificar a validação do frontend e submeter o formulário com "cpf" ou "cnpj" inválidos.

See the Pen Cadastro de Cliente by Márcio (@marciotognere) on CodePen.

O resultado da não validação (ou validação parcial) desses campos foi a inserção de cpf's e cnpj's falsos, com caracteres diferentes de numéricos, caracteres especiais, entre outros. A fim de melhorar a qualidade dos dados contidos nessa tabela, poderíamos criar uma trigger, que chama uma function para verificar se o "cpf" ou o "cnpj" passado é correto. Essa trigger seria chamada sempre que fosse inserido ou atualizado essa coluna.

Criaremos a function para verificar o cpf.


CREATE OR REPLACE FUNCTION Verifica_CPF(cpf IN CHAR)
  RETURN CHAR
  IS digito1  INTEGER := 0;
     digito2  INTEGER := 0;
     primeiro INTEGER := 0;
     conta    INTEGER := 0;
  BEGIN
    IF (cpf IS NOT NULL AND REGEXP_REPLACE(cpf,'[^0-9]') IS NULL) OR (LENGTH(cpf) > 11 OR LENGTH(REGEXP_REPLACE(cpf,'[^0-9]')) <> 11) THEN
      RETURN 'F';
    ELSE
      FOR z IN 1 .. 11 LOOP
        IF z = 1 THEN
          primeiro := SUBSTR(cpf, z, 1);
        ELSE
          IF SUBSTR(cpf, z, 1) = primeiro THEN
            conta := conta + 1;
          END IF;
        END IF;
      END LOOP;
        
      IF conta = 10 THEN
        RETURN 'F';
      END IF;
      
      FOR x IN 1 .. 9 LOOP
        digito1 := digito1 + (SUBSTR(cpf, x, 1) * (11 - x));
      END LOOP;
      
      digito1 := MOD(digito1,11);
        
      IF digito1 < 2 THEN
        digito1 := 0;
      ELSE
        digito1 := 11 - digito1;
      END IF;
        
      FOR y IN 1 .. 10 LOOP
        digito2 := digito2 + (SUBSTR(cpf, y, 1) *(12 - y));
      END LOOP;
        
      digito2 := MOD(digito2,11);
        
      IF digito2 < 2 THEN
        digito2 := 0;
      ELSE
        digito2 := 11 - digito2;
      END IF;
      
      IF (digito1 = SUBSTR(cpf,10,1)) AND (digito2 = SUBSTR(cpf,11,1)) THEN
        RETURN 'V';
      END IF;
        
      RETURN 'F';
    END IF;
    
    RETURN 'F';
  END;

Criaremos a trigger que chama a function para verificar a autenticidade do cpf.


CREATE OR REPLACE TRIGGER valida_cpf
  BEFORE INSERT OR UPDATE OF cpf ON cliente FOR EACH ROW
BEGIN
  IF (:NEW.cpf IS NOT NULL) AND (VERIFICA_CPF(:NEW.cpf) = 'F')  THEN
    RAISE_APPLICATION_ERROR(-20055,('O cpf '||:NEW.cpf||' inserido é inválido'));
  END IF;
END;

Criaremos a function para verificar o cnpj.


CREATE OR REPLACE FUNCTION Verifica_CNPJ(cnpj IN CHAR)
  RETURN CHAR
  IS digito1  INTEGER := 0;
     digito2  INTEGER := 0;
     primeiro INTEGER := 0;
     conta    INTEGER := 0;
  BEGIN
    IF (cnpj IS NOT NULL AND REGEXP_REPLACE(cnpj,'[^0-9]') IS NULL) OR (LENGTH(cnpj) > 14 OR LENGTH(REGEXP_REPLACE(cnpj,'[^0-9]')) <> 14) THEN
      RETURN 'F';
    ELSE
      FOR y IN 1 .. 14 LOOP
        IF y = 1 THEN
          primeiro := SUBSTR(cnpj, y, 1);
        ELSE
          IF SUBSTR(cnpj, y, 1) = primeiro THEN
            conta := conta + 1;
          END IF;
        END IF;
      END LOOP;
      
      IF conta = 13 THEN
        RETURN 'F';
      END IF;
    
      FOR z IN 1 .. 12 LOOP
        IF z = 1 OR z = 9 THEN
          digito1 := digito1 + (5 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 2 OR z = 10 THEN 
          digito1 := digito1 + (4 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 3 OR z = 11 THEN 
          digito1 := digito1 + (3 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 4 OR z = 12 THEN 
          digito1 := digito1 + (2 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 5 THEN 
          digito1 := digito1 + (9 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 6 THEN 
          digito1 := digito1 + (8 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 7 THEN 
          digito1 := digito1 + (7 * SUBSTR(cnpj, z, 1));
        END IF;
        IF z = 8 THEN 
          digito1 := digito1 + (6 * SUBSTR(cnpj, z, 1));
        END IF;
      END LOOP;
      
      digito1 := MOD(digito1,11);
      
      IF digito1 < 2 THEN
        digito1 := 0;
      ELSE
        digito1 := 11 - digito1;
      END IF;
      
      FOR x IN 1 .. 13 LOOP
        IF x = 1 OR x = 9 THEN
          digito2 := digito2 + (6 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 2 OR x = 10 THEN 
          digito2 := digito2 + (5 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 3 OR x = 11 THEN 
          digito2 := digito2 + (4 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 4 OR x = 12 THEN 
          digito2 := digito2 + (3 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 5 OR x = 13 THEN 
          digito2 := digito2 + (2 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 6 THEN 
          digito2 := digito2 + (9 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 7 THEN 
          digito2 := digito2 + (8 * SUBSTR(cnpj, x, 1));
        END IF;
        IF x = 8 THEN 
          digito2 := digito2 + (7 * SUBSTR(cnpj, x, 1));
        END IF;
      END LOOP;
      
      digito2 := MOD(digito2,11);
      
      IF digito2 < 2 THEN
        digito2 := 0;
      ELSE
        digito2 := 11 - digito2;
      END IF;
      
      IF (digito1 = SUBSTR(cnpj,13,1)) AND (digito2 = SUBSTR(cnpj,14,1)) THEN
        RETURN 'V';
      END IF;
      
      RETURN 'F';
    END IF;
    
    RETURN 'F';
  END;

Criaremos a trigger que chama a function para verificar a autenticidade do cnpj.


CREATE OR REPLACE TRIGGER valida_cnpj
  BEFORE INSERT OR UPDATE OF cnpj ON cliente FOR EACH ROW
BEGIN
  IF (:NEW.cnpj IS NOT NULL) AND (VERIFICA_CNPJ(:NEW.cnpj) = 'F') THEN
    RAISE_APPLICATION_ERROR(-20056,('O cnpj '||:NEW.cnpj||' inserido é inválido'));
  END IF;
END;

Dessa forma, estamos tratando esse problema na base. Ainda que o programador não tratasse a validação, nós tratamos na base. Com isso, temos a garantia que um dado correto seja armazenado na base.

Vamos testar?

Criaremos uma sequence para facilitar nosso teste.

Criação da sequence


CREATE SEQUENCE seq_cliente_codcliente
  START WITH 1
  INCREMENT BY 1
  NOMAXVALUE
  MINVALUE 1
  NOCACHE
  NOCYCLE;

Testaremos as inserções.

Quando inserimos um cpf CORRETO, como esperado, o registro é inserido:


INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere','46464771221',null);
1 row inserted.

Quando inserimos um cpf INCORRETO, como esperado, é exibido um erro:


INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere','12345678944',null);
Error starting at line : 21 in command -
INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere','12345678944',null)
Error report -
ORA-20055: O cpf 12345678944 inserido é inválido
ORA-06512: at "TOGNERE.VALIDA_CPF", line 3
ORA-04088: error during execution of trigger 'TOGNERE.VALIDA_CPF'

Quando inserimos um cnpj CORRETO, como esperado, o registro é inserido:


INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere',null,'26333191000105');
1 row inserted.

Quando inserimos um cnpj INCORRETO, como esperado, é exibido um erro:


INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere',null,'12345678912344');
Error starting at line : 21 in command -
INSERT INTO cliente(codcliente,nome,cpf,cnpj)
  VALUES(seq_cliente_codcliente.nextval,'Márcio Tognere',null,'12345678912344')
Error report -
ORA-20056: O cnpj 12345678912344 inserido é inválido
ORA-06512: at "TOGNERE.VALIDA_CNPJ", line 3
ORA-04088: error during execution of trigger 'TOGNERE.VALIDA_CNPJ'

O mesmo comportamento ocorre quando realizamos atualização de algum registro.


Esse procedimento pode ser realizado em diversos casos. A escolha das colunas cpf e cnpj foi apenas um exemplo. É importante identificar em sua base onde precisemos tratar o dado que está sendo inserido.

Códigos fonte: https://github.com/marciotognere/valida_cpf_cnpj