Imagem

Normalização de Dados

Bráulio Ferreira de Carvalho

Na minha experiência profissional tenho observado, ao longo dos anos, como a normalização é tratada com pouca atenção. Já encontrei muitas tabelas sem chave primária e com redundância de informações, além de projetos cheios de anomalias de atualização.

O pior é que muitas vezes, ao averiguar, me deparei com “justificativas técnicas” – na maior parte dos casos infundadas. Há pouco me surpreendi com o comentário de um aluno - um analista havia lhe “ensinado” que a normalização está desaparecendo! Só se sumirem juntos os bancos de dados relacionais e as boas práticas de desenvolvimento.

A normalização tem dois objetivos principais: i) garantir a integridade dos dados, evitando que informações sem sentido sejam inseridas; ii) organizar e dividir as tabelas da forma mais eficiente possível, diminuindo a redundância e permitindo a evolução do banco de dados com o mínimo de efeito colateral. Esses objetivos são atingidos através da implementação de regras conhecidas como formas normais.

São seis as formas normais mais conhecidas:

1FN (1ª Forma Normal)

2FN (2ª Forma Normal)

3FN (3ª Forma Normal)

FNBC (Forma Normal de Boyce e Codd)

4FN (4ª Forma Normal)

5FN (5ª Forma Normal)

Uma forma normal engloba todas as anteriores, ou seja, para que uma tabela esteja na 2FN, ela obrigatoriamente deve estar na 1FN e assim por diante (figura 1).

Imagem

Vale ressaltar que as três primeiras formas normais atendem à maioria dos casos de normalização. Neste artigo seguiremos passo a passo até a terceira forma normal - a quarta, quinta e outras formais normais pouco conhecidas serão apresentadas em outra edição.

CONCEITOS ÚTEIS

Entender o significado dos conceitos a seguir pode ajudá-lo na leitura de textos e publicações acadêmicos, que muitas vezes são as melhores fontes de pesquisa para assuntos como normalização, modelagem e projeto de banco de dados.

Variáveis de relação

Os termos “variável de relação” e “relação”, de um modo simplista, podem ser interpretados como o equivalente matemático para “tabela”.

Chaves

Chave candidataÉ um atributo ou conjunto de atributos que são únicos para cada registro. Para cada tabela podemos ter uma ou várias chaves desse tipo. Por exemplo, uma tabela de clientes pode possuir duas chaves candidatas: Código e CPF.

Chave primáriaEntre as chaves candidatas, escolhemos uma para ser o identificador principal da tabela – o atributo escolhido passa então a ser chamado de chave primária. Em alguns bancos de dados a chave primária é referenciada como PK (do inglês, Primary Key).

Chaves alternativas - São as chaves candidatas que não foram definidas como chave primária.

Chave estrangeira - É o atributo ou conjunto de atributos que faz a ligação com uma chave candidata de outra tabela. Muitas vezes a chave estrangeira é referenciada como FK (do inglês, Foreign Key).

Dependência Funcional (DF)

Sempre que um atributo X identifica um atributo Y, dizemos que entre eles há uma dependência funcional. Temos, portanto, que X é o determinante e que Y é o dependente. A representação é: XèY (lê-se X determina Y ou Y é dependente de X). Veja um exemplo:

Dada uma determinada cidade (não considerando cidades homônimas) sabemos o seu estado e com o estado temos o país. Isso é representado da seguinte forma:

cidade è estado

estado è país

Em outras palavras, estado é funcionalmente dependente de cidade e país é funcionalmente dependente de estado. Ou ainda, estado é dependente de cidade e país é dependente de estado. E por último, cidade determina estado e estado determina país.

Trivialidade

A dependência funcional trivial indica que um determinante com mais de um atributo pode determinar seus próprios membros quando isolados. Veja um exemplo:

{banco, agência} è banco (DF trivial, pois banco é parte do determinante)

{banco, agência} è agência (DF trivial, pois agência é parte do determinante)

Quando um determinante identifica outro atributo qualquer, temos uma dependência funcional não trivial (essa DF é a que nos interessa no processo de normalização):

{banco, agência} è cidade (DF não trivial, pois cidade não faz parte do determinante)

Transitividade

Se um atributo X determina Y e se Y determina Z, podemos dizer que X determina Z de forma transitiva. Outra leitura é: existe uma dependência funcional transitiva de X para Z. Veja um exemplo:

cidade è estado

estado è país

cidade è país (cidade determina país de forma transitiva)

DF Irredutível à esquerda

Dizemos que o lado esquerdo de uma dependência funcional é irredutível quando o determinante está em sua forma mínima. Temos a forma mínima quando não é possível reduzir a quantidade de atributos determinantes sem perder a dependência funcional. Exemplo:

{cidade, estado} è país

Esta DF não está na forma irredutível à esquerda, pois podemos ter somente o estado como determinante:

estado è país

Nota: Nem sempre estar na forma irredutível à esquerda significa possuir um determinante com apenas uma coluna.

Anomalias de Atualização

São os problemas causados quando efetuamos uma inserção, atualização ou deleção em uma base de dados não normalizada. Como exemplo, observe a tabela da figura 2 e as considerações a seguir:

  • A tabela mistura dados do fornecedor, dados do fornecimento e dados de localização do fornecedor;
  • Não existem tabelas auxiliares – todos as informações estão armazenadas nessa tabela;
  • Nenhum campo pode ser nulo;
  • A chave primária é composta pelos campos CódFornecedor e CódPeça.

Imagem

Com base nisso, podemos identificar anomalias para cada operação DML:

Anomalias no comando INSERT - Não podemos inserir a localização de um fornecedor até que ele forneça pelo menos uma peça. De fato, a tabela não mostra que o fornecedor F5 está localizado em Atenas (ele existe na vida real, mas não pode ser representado).

Ainda temos outro problema: sempre que cadastrarmos o fornecimento de uma peça teremos que repetir os dados do fornecedor (campos Nome, Status e Cidade).

Anomalias no comando UPDATE - O valor do campo Cidade aparece repetido várias vezes na tabela. Essa redundância aumenta a probabilidade de erros durante uma atualização. Por exemplo, se o fornecedor F1 se mudar de Londres para Amsterdã, teremos que pesquisar todos os registros correspondentes e atualizá-los, tomando o cuidado de não deixar os dados inconsistentes (uma ocorrência de F1 pode receber Amsterdã e outra Londres). Observe que o mesmo problema se aplica aos campos Nome, Status e Preço.

O campo valor representa outra anomalia: ele é calculado pela multiplicação de preço e qtde. Se alterarmos o preço unitário ou a quantidade, teremos que atualizar também o campo valor– um passo a mais que pode ser esquecido.

Anomalias no comando DELETE - Se eliminarmos todos os registros de um fornecedor, apagaremos não apenas a ligação com as peças, mas também a informação de que o fornecedor está localizado em uma determinada cidade (os problemas de INSERT e DELETE são na realidade duas faces da mesma moeda).

Decomposição sem perdas

O processo de normalização envolve a quebra ou decomposição de uma tabela em partes menores. Essa decomposição tem que ser reversível, de modo que nenhuma informação seja perdida no processo. O fato de uma decomposição ser ou não sem perdas está intimamente ligado ao conceito de dependência funcional visto anteriormente. Veja as considerações abaixo, com base na figura 3:

  • Na decomposição da letra a é possível restaurar a tabela original, pois com o nome do funcionário obtemos o sexo e o salário correspondentes (decomposição sem perdas);
  • Na letra b não é possível obter a tabela original, pois para o funcionário “Carlos”, de sexo “M”, encontraremos dois salários na segunda tabela.

Imagem

Figura 3 NE: (Mudar o nome “Relação” para “Tabela”).

Junção (Join)

Junção é o processo inverso da decomposição sem perdas - veja letra a na figura 3. Como temos um atributo comum (Funcionario), podemos obter a tabela original fazendo a junção das duas tabelas decompostas.

Projeções

Significa considerar apenas alguns campos de uma tabela. Na figura 4 vemos uma projeção que inclui os campos a,b,d e f.

Imagem

Figura 4

Importância da Normalização

Os benefícios mais importantes obtidos com a normalização de uma base de dados são:

Tabelas flexíveis e de fácil manutenção - Uma base sem as anomalias de atualização e com uma estrutura de armazenamento eficaz é mais simples de atualizar e evoluir (embora o processo de consulta se torne mais complexo, pois a normalização geralmente aumenta o número de tabelas do banco).

Eliminação de redundâncias – Sem redundâncias, as tabelas ficam menores, mais consistentes e menos sujeitas a discrepâncias. Sempre que puder, evite o que o mercado chama de “redundância controlada” – como o nome já diz, ela cria a necessidade de controle adicional e aumenta a complexidade.

PASSO A PASSO EM NORMALIZAÇÃO

Para efeitos didáticos, vamos trabalhar com o seguinte exemplo: em uma determinada empresa, os produtos recebidos de um fornecedor são registrados em um formulário próprio, que pode ser visualizado na figura 5. Vamos informatizar esse processo criando uma base de dados para armazenar as informações deste formulário.

Imagem

A primeira versão da base pode ser vista na figura 6. Essa estrutura ainda não pode ser carregada em um banco de dados relacional, pois possui campos multivalorados (CodItem, CodProd, Produto, Quantidade, Preço, Total Item) e não se apresenta no formato de tabela.

NE: Refazer as figuras incluindo o campo “data” (ver arquivo formulário.xls)

Imagem

Figura 6.

1a Forma Normal (1FN)

O primeiro passo para normalizar a estrutura da figura 6 é aplicar as regras da primeira forma normal. Podemos dizer que uma entidade está na primeira forma normal quando cada atributo contém somente um valor, em somente um lugar. Essa exigência também é conhecida como atomicidade de dados. As regras gerais para obtenção da 1FN são:

Não podemos ter atributos multivalorados. Nesse caso, colocamos cada valor do atributo em uma linha diferente e repetimos os dados de todas as outras colunas;

Não podemos ter atributos repetidos, como Telefone1, Telefone2 etc. A solução é semelhante ao item anterior;

Todos os registros têm que ser diferentes;

A entidade não pode ter mais de duas dimensões;

Cada atributo deve ter somente um tipo de dado. Uma violação comum dessa regra, por exemplo, é a criação de um campo para armazenar o CPF e o CNPJ, alternadamente. Esse cenário deve ser evitado pois cria complicações para a evolução da regra de negócio.

Observe na figura 7 a tabela gerada após a aplicação da 1FN.

Chave Primária

Chave Primária

Para trabalhar com a 2FN precisamos definir uma chave primária. Na tabela recém-criada, a chave escolhida é formada pelos campos Nro_nota e CodItem (pintados de cinza), pois através deles determinamos todos os outros campos. Por exemplo:

Com o número da nota, determinamos os campos Data, CodForn, Nome, Telefone e Endereço:

NroNota è { Data, CodForn, Nome, Telefone, Endereço}

Com o número da nota e o código do item determinamos os demais campos:

{NroNota, CodItem} è {CodProd, Produto, Quantidade, Preço, TotalItem}

2a Forma Normal (2FN)

Essa forma normal visa a diminuição da redundância e o desagrupamento de informações. Com a 2FN, uma tabela passa a representar uma quantidade menor de entidades (o ideal é que cada entidade seja armazenada em apenas uma tabela) - observe que a tabela da figura 7 agrupa as entidades Nota Fiscal, Item da Nota, Fornecedor e Produto.

A definição da segunda forma normal é: uma tabela está em 2FN se estiver em 1FN e todo atributo não-chave for determinado por todos os campos da chave primária. Em outras palavras, é necessário eliminar as dependências funcionais parciais.

A tabela do exemplo viola a 2FN pois os campos Data, CodForn, Nome, Telefone e Endereço não são determinados pela chave primária completa (o campo CodItem não é necessário para identificar essas informações):

NroNota è {Data, CodForn, Nome, Telefone, Endereço}

Como regra geral, a 2FN deve ser aplicada através dos passos:

1) Eleger a chave primária da tabela;

2) Verificar as dependências funcionais parciais;

3) Mover os campos não enquadrados na 2FN para uma nova tabela, fazendo a decomposição sem perdas;

4) Na tabela criada, repetir os passos 1 à 4 até eliminar a DF parcial.

O resultado pode ser visualizado na figura 8. Observe que todos os campos, nas duas tabelas, são agora determinados por suas chaves primárias completas – garantindo a 2FN. Note também que o resultado da aplicação desta forma normal são tabelas mais simples, que representam as entidades com mais proximidade.

Imagem

Nota: Se a chave primária possui apenas um campo ou é composta por todos os campos, a tabela já está automaticamente na 2FN.

3a Forma Normal (3FN)

A 3FN dá continuidade ao objetivo da 2FN: reduzir as redundâncias, desagrupando as tabelas de forma que cada uma represente apenas uma entidade. Na figura 8, a tabela 1 agrupa informações sobre as entidades Nota e Fornecedor e a tabela 2 agrupa informações sobre as entidades Item Nota e Produto.

A técnica utilizada pela 3FN é a identificação e eliminação da transitividade. Dizemos que uma tabela está na 3FN se também estiver na 2FN e todo atributo não chave for determinado de forma não transitiva pela chave primária. Em outra leitura, dizemos que todo atributo não chave deve ser determinado somente pela chave primária.

Vamos analisar a tabela criada na 2FN. Observe que os campos Nome, Telefone e Endereço podem ser determinados tanto pela chave primária quanto pelo campo CodForn:

NroNota è {Data, CodForn, Nome, Telefone, Endereço}

CodForn è {Nome, Telefone, Endereço}

Assim, esses campos possuem dependência funcional transitiva com a chave primária:

NroNota è CodForn è {Nome, Telefone, Endereço}

Na segunda tabela também temos transitividade:

{NroNota,CodItem} è CodProd è {Produto, Preço}

Outro tipo de violação da 3FN são os campos calculados, que também possuem transitividade. Na 3FN, todos os campos calculados são removidos da base de dados. Veja a representação:

{NroNota,CodItem} è {Preço,Quantidade} è {TotalItem}

Para adequar as tabelas à 3FN, seguimos um roteiro semelhante ao usado na 2FN:

1) Mover os campos com transitividade para uma nova tabela;

2) Criar uma chave primária na tabela nova com o(s) campo(s) da tabela original que determinava(m) diretamente os campos movidos.

3) Na nova tabela, repetir os passos 1, 2 e 3, até eliminar totalmente a transitividade.

Observe na figura 9 o formato final das tabelas, conseguido após a aplicação da 3FN. Nesse ponto temos a organização ideal para a base de dados, pelos motivos a seguir:

1) A decomposição foi feita sem perdas – através de junções, podemos recuperar a tabela da figura 7;

2) As quatro entidades (Nota, Item Nota, Fornecedor e Produto) possuem tabelas exclusivas, eliminando o agrupamento de informações e a redundância;

3) As tabelas foram separadas de tal forma que as anomalias de atualização não poderão ocorrer;

4) As tabelas são fáceis de evoluir e manter. Por exemplo, se quisermos incluir os dados de um produto que ainda não tenha sido fornecido, podemos inserir sua descrição na tabela Produtos. Observe que isso não era possível até a aplicação da 3FN;

5) Do ponto de vista relacional, os dados estão armazenados e distribuídos de forma eficiente.

Imagem

Integridade

As formas normais visam a consistência da base de dados sob o aspecto da eliminação de redundâncias, mas não garantem que as informações certas serão inseridas no lugar certo – um ponto crucial para que o banco se mantenha íntegro ao longo do tempo. Para que as tabelas obtidas com a aplicação das formas normais façam sentido, é preciso existir regras de integridade. Veja alguns exemplos:

1) Não podemos inserir um produto na tabela Item Nota que não tenha sido cadastrado na tabela Produto;

2) Não podemos apagar um registro na tabela Nota sem apagar o conjunto de registros correspondentes da tabela Item Nota;

3) Não podemos alterar o código de um fornecedor sem alterar todas as tabelas que o referenciam.

Basicamente, existem quatro tipos de regras de integridade: de entidade, de domínio, referencial e definida pelo usuário - elas serão detalhadas nos próximos números da SQL Magazine.

Desnormalização

A normalização não se preocupa com a performance de obtenção dos dados e sim com a melhor forma de organizá-los. Como a normalização geralmente aumenta o número de tabelas, há uma tendência de queda de performance nas consultas – que passam a necessitar de mais joins para que sejam efetuadas.

A desnormalização é o processo inverso, onde o administrador do banco abre mão de algumas regras das formas normais com o objetivo de otimizar as consultas. É importante notar que isso não é o mesmo que ter uma base de dados não-normalizada: a desnormalização acontece sempre depois da aplicação das formas normais.

A desnormalização é, na maioria do casos, desaconselhável e deve ser aplicada somente quando o administrador tiver certeza de que ela trará benefícios reais. Alguns profissionais fazem suposições sobre a perda de performance e já projetam desnormalizando, sem fazer nenhum benchmark. O problema disso é que em muitos casos o ganho de velocidade não surge efetivamente ou não paga o custo das anomalias de atualização geradas pela desnormalização. Vale notar que nem sempre os joins degradam performance, pois a maioria dos SGBDs mantém o conteúdo de tabelas pequenas em cache e disponibilizam diversos outros recursos para a otimização de consultas.

Como exemplo, imagine que nossa aplicação acesse massivamente um relatório contendo o campo calculado TotalItem (eliminado na 3FN). Para evitar o cálculo do total a todo momento, podemos abrir mão da 3FN e criar o campo fisicamente na tabela de itens de nota, arcando com a responsabilidade de mantê-lo devidamente atualizado. Observe que a idéia e a justificativa são questionáveis – a desnormalização deve ser aplicada somente se o administrador tiver provas de que o ganho de performance será real e trará benefícios.

Recomendo a leitura do artigo publicado por Craig Mullins em http://www.tdan.com/i001fe02.htm - o especialista mostra diversos casos comuns no uso da desnormalização.

Algumas dúvidas comuns sobre a desnormalização:

O que dizer quando a desnormalização é utilizada para fins históricos? No exemplo citado, se o preço de um produto for alterado, todos os dados de fornecimento serão modificados. Repetir o campo preço na tabela Item_Nota, para manter o histórico, é uma boa prática?

R: Este caso não se trata de desnormalização. O que temos são dois atributos com sutil diferença: Preço atual e Preço histórico.

Devemos sempre normalizar até o fim? Por exemplo, geralmente os campos cidade, bairro, UF e CEP apresentam repetição - é correto criarmos tabelas auxiliares para cada um?

R: Depende. Se a aplicação realizar pesquisas nestes campos, sim - imagine se quisermos saber a quantidade de clientes em cada bairro e este estiver cadastrado de forma diferente em cada registro. Tal como no site dos Correios, hoje vemos vários sistemas que a partir do CEP trazem todas as demais informações do endereço, deixando claro que várias tabelas foram utilizadas.

CONCLUSÃO

A normalização, apesar de extremamente útil no projeto de um banco de dados, não é remédio para todos os males, já que nem todas as redundâncias ou anomalias de atualização podem ser eliminadas através das formas normais. Além disso, a normalização não deve substituir uma boa análise do negócio da aplicação – ela é apenas uma ferramenta de apoio para o projetista.

BOX: Atributos Multivalorados

O objetivo principal da 1FN é transformar estruturas não padronizadas em tabelas, para que possam ser armazenadas em um banco de dados relacional. De fato, podemos dizer que toda tabela já está na 1FN; caso contrário, ela não seria uma tabela.

O caso mais comum de violação da 1FN são estruturas que possuem atributos multivalorados, repetidos ou aninhados. Na 1FN, esses atributos devem ser “planificados”, fazendo com que cada célula armazene somente um valor. Existem duas formas de enquadrar os atributos multivalorados na 1FN:

1) Expandir o atributo multivalorado em vários registros, repetindo o valor dos demais campos – essa foi a solução escolhida neste artigo e é a que mais se aproxima da definição criada por Codd, o pai da normalização.

2) Criar uma tabela auxiliar para conter o atributo multivalorado – essa opção é adotada por alguns autores pela praticidade. No entanto, em alguns casos essa abordagem pode induzir a erros. Por exemplo, observe o aninhamento de três tabelas na figura 10 (vários gostos dentro de dependentes e vários dependentes dentro de pai).

Utilizando a primeira abordagem chegamos facilmente à estrutura da figura 11. Agora, imagine a dificuldade em criar direto as tabelas auxiliares. Teremos que verificar quem é a chave, se a tabela auxiliar terá outra chave, se precisará de uma tabela “auxiliar da auxiliar” etc. Em suma, será necessário utilizar os conceitos de chave primária, dependência funcional, dependência funcional irredutível à esquerda e decomposição sem perdas, que são utilizados na 2FN. Certamente, nesse caso, projetistas iniciantes podem cometer erros com muito mais facilidade.

Imagem

Figura 10:

Imagem

Figura 11:

BIBLIOGRAFIA

  • C.J.DATE
    • Introdução a Sistemas de Banco de Dados – 7ª edição americana
  • Henry F. Korth e Abraham Silberschatz
    • Sistema de banco de Dados – 2ª edição
  • Carlos Alberto Heuser
    • Projeto de Banco de Dados – 4a edição
  • Michael J. Hernadez
    • Aprenda a Projetar seu Próprio Banco de Dados