Práticas de design de banco de dados, parte 2

Na primeira parte desse artigo, iniciamos a discussão sobre algumas boas práticas que são particularmente úteis. Nenhuma delas são específicas a algum produto e todas deveriam, portanto, ser aplicáveis a qualquer banco de dados que estiver sendo usado. Nessa segunda parte, será explanada uma introdução acessível ao tópico da normatização de banco de dados e as cinco Formas Normais. Será discutido também outros possíveis usos para uma base de dados em um projeto.

Normatização

Nenhuma discussão sobre o design de banco de dados relacionais estaria completa sem uma seção sobre normatização. Um banco de dados normatizado evita certas anomalias ao inserir atualizar ou apagar dados e, portanto, ajuda a manter a consistência dos dados no banco de dados.
Porém, a ausência de anomalias é apenas o resultado tangível de um benefício maior da normatização – a saber a correta identificação e modelagem das entidades. As anomalias de inserção, atualização e remoção a que nos referimos são consequências de redundâncias introduzidas por separações impróprias ou inadequadas entre entidades distintas.  O procedimento de normatização é, portanto, não apenas uma pequena tarefa técnica que deve ser feita fora de principio, mas pode ativamente ajudar a melhorar o entendimento do domínio do negócio.

Lamentavelmente, o tratamento dado a normatização é frequentemente proibitivamente formal, e sofre por causa de uma terminologia especial e bastante não intuitiva. Isso é uma infelicidade já que o resultado de um processo de normatização frequentemente evoca a reação de que tudo nada mais é do que senso comum. Tentarei oferecer aqui uma explanação das expressões que você costuma encontrar na literatura especializada a medida que elas surjam nas discussões a seguir.

Visão geral

A normatização é um processo no qual o design do banco de dados é transformado, ou  descomposto, em designs diferentes, mas equivalentes. O esquema resultante é equivalente ao original no sentido de que nenhuma informação é perdida quando se passa de uma forma para outra.
O processo de normatização consiste de uma sequência de projeções – isto é, alguns atributos são extraídos de uma tabela para formar uma nova. Em outras palavras, as tabelas são dividas verticalmente, A decomposição é sem perdas, apenas se você puder restaurar a tabela original pela junção de suas projeções.

Através de cada decomposição sem perdas, é possível transformar um esquema original em um resultado que satisfaz certas condições, conhecidas como Formas Normais:

  • A Primeira Forma Normal (1NF) aborda a estrutura de uma tabela isolada.
  • A Segunda (2NF) e Terceira Forma Normal (3NF), e as Formas Normais de Boyce-Codd (BCNF) abordam relacionamentos um para um e um para muitos.
  • A Quarta (4NF) e Quinta Formas Normais (5NF) lidam com relacionamentos muitos para muitos.

Essas Formas Normais forma uma hierarquia de modo que um esquema terá uma forma normal tão alta quanto automaticamente preencha os critérios da Forma Normal imediatamente inferior.

A Quinta Forma Normal é a forma normal final com relação as projeções e junções – é garantidamente livre de anomalias que podem ser eliminadas pelo uso de projeções.

Na discussão a seguir, qualquer menção a chaves refere-se as chaves conceituais formadas a partir dos dados do negócio, e não as surrogate keys que podem ter sido definidas.

Primeira Forma Normal

Uma tabela está na Primeira Forma Normal (1NF), se todas as entradas  estão com valor escalar. Os bancos de dados relacionais já na construção já que entradas com valor de vetor são proibidas. Dados com valor de vetor (isso é, entradas que possuem mais de um valor por cada linha) são referenciadas como grupos de repetição.

As seguintes relações violam a 1NF por que SupplierID forma um grupo de repetição (aqui e no exemplos seguintes, campos de chave primária estarão em negrito):


{ PartID, Supplier1ID, Supplier2ID, Supplier3ID }

Grupos de repetição indicam um relacionamento um para muitos – em outras palavras, um relacionamento no qual os banco de dados relacionais são tratados usando chaves estrangeiras. Observe que o problema desses grupos de repetição não podem ser resolvidos pela adição de nenhuma quantidade de campos a um registro; mesmo se o número de elementos dos dados com valor de vetor sejam fixos, finitos e pré-determinados, a busca por um valor em todos esses campos paralelos seria proibitivamente pesado.
Para alcançar a forma 1NF, elimine os grupos de repetição pela criação de tabelas separados para cada conjunto de dados relacionados.
Para demonstrar as anomalias típicas que ocorrem em tabelas que são apenas 1NF, considere o seguinte exemplo:


{ CustomerID, OrderID, CustomerAddress, OrderDate }

Note os seguinte problemas:

  • Inserção: Não é possível adicionar um registro para um cliente que nunca fez um pedido.
  • Atualização: Para alterar o endereço de um cliente, essa mudança teria que ser repetida para todos os pedidos existentes do cliente.
  • Remoção: Apagar o último pedido de uma cliente causaria a perda de todas as informações sobre o cliente.

Dependência funcional

A Segunda e Terceira Funcional visa as dependências entre atributos, especialmente entre os campos chave e não-chave.
Por definição, uma chave pode determinar um único registro: Conhecer a chave determina os valores de todos os outros atributos na tabela, de forma que dada uma chave, os valores de todos os outros atributos na linha são fixos.

Esse tipo de relacionamento pode ser formalizado como segue. Considere X e Y atributos (ou conjunto de atributos) de um dado relacionamento. Assim Y é dependente funcionalmente de X se, quando dois registros tem o mesmo valor de X, eles também tem o mesmo valor de Y. Nesse caso, X é chamado determinante e Y é chamado dependente. Já que para qualquer X deve existir um único Y, esse relacionamento representa uma dependência funcional de valor único. Se o conjunto de atributos do determinante é o menor possível (no sentido de que depois de separar um ou mais atributos de X, o conjunto restante de atributos não determina mais Y), então a dependência é chamada irredutível.

Note que a dependência funcional é um relacionamento semântico: é a lógica do negócio do domínio do problema, representado pela relação, a qual determina se certo X determina um Y.

Segunda Forma Normal

Uma tabela está na Segunda Forma Normal (2NF) se todo campo não-chave é um fato sobre a chave inteira. Em outras palavras, uma tabela está na 2NF se estiver na 1NF e todos os atributos não-chaves são dependentes funcionalmente da chave primária (isto é, a dependência é irredutível).
Claramente, a 2NF é relevante apenas quando a chave é composta (isso, consiste de vários campos). O exemplo seguinte descreve uma tabela que não está na 2NF já que o atributo WarehouseAddress  depende apenas de WareHouseID mas não de PartID:


{ PartID, WarehouseID, Quantity, WarehouseAddress }

Para alcançar a 2NF, crie tabelas separadas para os conjuntos de valores que aplicam-se a registros múltiplos e relacione essas tabelas com chaves estrangeiras. Os determinantes da tabela inicial tornam-se as chaves primárias das tabelas resultantes.

Terceira Forma Normal

Uma relação está na Terceira Forma Normal (3NF) se estiver na 2NF e nenhum e seus atributos for um fato sobre outro campo não-chave. Em outras palavras, nenhum campo não-chave depende funcionalmente de outro campo não-chave. (Cada dependência indireta é conhecida como dependência transitiva).
O exemplo a seguir viola a 3NF já que Location depende funcionalmente de DepartmentID:


{ EmployeeID, DepartmentID, Location }

Para alcançar a 3NF, elimine os campos que não dependem na chave da tabela original e adicione-os a tabela cuja chave primária seja sua determinante.
Rsumindo o processo de normalização incluindo a Terceira Forma Norma:

Todo campo de um registro precisa depender da Chave (1NF), da Chave inteira (2NF) e de Nada além da Chave (3NF).

Forma Normal de Boyce-Codd

A Forma Norma de Boyce-Codd (BCNF) é uma extensão da 3NF no caso de haver duas ou mais chaves candidatas que são compostas e sobrepostas (isto é, elas tem ao menos um campo em comum). Se essas condições não forem preenchidas, as duas Formas Normais (3NF e BCNF) são equivalentes. Uma tabela está na BCNF se, e somente se, apenas os determinantes forem candidatos a chave.

Na seguinte tabela, tanto {SupplierID, PartID} quanto {SupplierName, PartID} são candidatas a chave. A tabela não está na BCNF já que contém dos determinantes (SupplierID e SupplierName) que não são candidatos a chave (SupplierID e SupplierName são determinantes, já que elas determinam uma a outra).


{ SupplierID, PartID, SupplierName, Quantity }

Todavia, qualquer uma das decomposições está na BCNF:


{ SupplierID, SupplierName }
{ SupplierID, PartID, Quantity }

ou


{ SupplierName, SupplierID }
{ SupplierName, PartID, Quantity }

Para alcanças a BCNF, remove os determinantes que não são candidatas a chave.

Formas Normais e relacionamentos Muitos para muitos

A Quarta e Quinta Formas Normais aplicam-se as situações que envolvem relacionamentos muitos para muitos. Nos banco de dados relacionais, esses relacionamentos são expressos através de tabelas com referências cruzadas.
Como exemplo, considere um caso de matricula em uma classe. Cada estudante pode ser matriculado em uma ou mais classes e cada classe pode conter um ou mais estudantes. Obviamente, existe um relacionamento muitos para muitos entre classes e estudantes. Esse relacionamento pode ser representado pela tabela de referências cruzadas Estudante/Classe:


{ StudentID, ClassID }

A chave para essa tabela é a combinação de StudentID e ClassID. Para evitar a violação da 2NF, todas as outras informações sobre cada estudante e sobre cada classe são armazenadas em tabelas Estudante e Classe separadas.
Note que cada StrudentID determina não um único ClassID, mas um conjunto bem definido de valores. Esse tipo de comportamento é referenciado como dependência multi-valorada de ClasseID em StudentID.

Quarta Forma Normal

Uma tabela está na Quarta Forma Normal (4NF) se estiver na 3NF e não representa dois ou mais relacionamentos muitos para muitos independentes.
Considere um exemplo com dois relacionamentos muitos para muitos, entre estudantes e classes e entre classes e professores. Além desses, um relacionamento muitos para muitos entre estudantes e professores está implícito, Porém, as regras do negócio não restringem essa relação de nenhuma forma – a combinação de StudentID e TeacherID não contém nenhuma informação adicional além da informação implícita pelos relacionamentos estudante/classe e classe/professor. Consequentemente, esses relacionamentos são independentes um do outro – eles não possuem nenhuma restrição adicional. A seguinte tabela é, dessa forma, uma violação da 4NF:


{ StudentID, ClassID, TeacherID }

Como exemplo do que essa anomalia pode causar, observe que não é possivel adicionar uma nova classe ensinada por um professor sem adiciona um estudante a essa classe.

Para alcançar a 4NF, represente cada relacionamento muitos para muitos independente através de sua própria tabela de referência cruzada.

Quinta Forma Normal

Uma tabela está na Quinta Forma Normal (5NF) se estiver na 4NF e o conteúdo da informação não puder ser reconstruída a partir de várias tabelas contendo menos atributos.

Considere novamente o exemplo estudante/classe/professor, mas agora assuma que exista um relacionamento adicional entre estudantes e professores. O exemplo anterior de tabela está agora na 4NF, já que todos os relacionamentos que descreve estão inter-relacionados. Porém, não está na 5NF, já que pode ser reconstruída a partir de três tabelas de referência cruzadas, cada uma representando um dos relacionamentos muitos para muitos.


{ StudentID, ClassID }
{ ClassID,   TeacherID }
{ TeacherID, StudentID }

Para alcançar a 5NF, isole os relacionamentos muitos para muitos, introduzindo o número de novas tabelas necessário para representar todas as restrições do domínio de negócios.

Contextualização da Normalização

Na prática, muitas base de dados são des-normalizadas em maior ou menor grau. A razão mais frequente para isso tema ver com performance – um banco de dados des-normalizado possivelmente precisa de menos junções e pode, assim, recuperar os dados mais rapidamente.

Embora esse raciocínio possa ser verdade, as ressalvas usuais contra a otimização prematura aplicam-se aqui com em tudo o mais. Primeiro, você deveria determinar suficientemente se existe um problema de performance e se a des-normalização proposta melhora o desempenho antes introduzir um desing conceitualmente não tão ótimo.

Além disso, uma esquema não normatizado pode ser difícil de atualizar. As verificações de integridade adicionais que serão necessárias neste caso podem compensar os ganhos de desempenho para consultas obtidas através de des-normalização.

Finalmente, deve ser observado que lidar como relacionamentos muitos para muitos aumenta a incidência de alguns problemas que não podem ser completamente resolvidos através da normatização (O artigo de Chris Date, “Normalization is no Panacea, na seção Recursos, cobre esse tópico).

Tabelas de histórico e registro de eventos

Além de armazenar os dados que são necessários para dar suporte aos negócios primários do sistema em construção, o banco de dados também pode possibilitar um local para registrar informações que são úteis para propósitos técnicos internos, como administração e manutenção do sistema em si.

Tabelas de histórico

Em um sistema real, você pode querer preservar os histórico das alterações dos dados no banco de dados. Isso pode ser conseguido com o uso de uma tabela de histórico (ou backup), e os gatilhos apropriados para os INSERT, DELETE ou UPDATE.

Cada tabela do banco de dados deve ter uma tabela de histórico, espelhando o histórico inteiro da tabela principal. Se as entradas da tabela principal tiverem que ser atualizadas, todo o conteúdo da registro será primeiro copiado para a tabela do histórico antes que a atualização for feita. Da mesma forma, registros removidos da tabela principal são copiados antes da remoção na tabela principal. A tabela de histórico sempre tem o mesmo nome da primária, mais um sufixo anexado, como _Hist.

As entradas da tabela de histórico são sempre anexadas no final. A tabela de histórico, dessa forma, cresce estritamente monotônica no decorrer do tempo. Será necessário periodicamente salvar registros antigos em uma arquivo. Esses registros podem, em virtude disso, não estarem disponíveis de imediato para consulta.

Os atributos da tabela de histórico devem ser exatamente os mesmo dos atributos da tabela primária. A tabela de histórico adiciona apenas a data e hora da alteração à tabela primária. O tipo é uma das seguintes opções: Create, Update ou Delete.

Mudar a estrutura da tabela primária afeta a tabela de histórico. Quando um atributo é adicionado a tabela primária, é adicionada a tabela de histórico também. Quando um atributo é removido primária, o atributo correspondente não é removido da histórico. Ao invés disso, esse campo é deixado em branco (NULL) em todos os registros futuros. Consequentemente, a tabela de histórico não apenas cresce em comprimento ao longo do tempo, mas também em largura.

Note que a escolha de usar um mecanismo de histórico não afeta nem o layout global  do banco de dados, nem as aplicações que acessar a tabela principal. Durante o desenvolvimento, você pode provavelmente dispensar o registro das alterações dessa maneira e deixar a criação do histórico e os gatilhos necessários para a hora da instalação.

Registro de eventos para diversão e lucro

Um banco de dados pode ser usado como registro de eventos. A noção de evento é ampla, variando desse depuração e informações específicas do sistema, até eventos que são específicos do domínio do negócio. Possíveis candidatos para eventos que podem ser objetos de registro em banco de dados incluem:

  • Transações que fazem alterações em dados persistentes
  • Transações que cruzam as fronteiras de componentes
  • Erros e exceções
  • Envio de mensagens ao usuário
  • Eventos que envolvem transações financeiras
  • Alterações de estado em entidades do negócio

Uma tabela EventLog para registro de informações contém ao menos esses campos:

  • Timestamp (data e hora)
  • EventType (uma classificação)
  • Details (uma descrição)

Opcionalmente, podemos identificar o dono ou origem do evento. O conceito de dono pode identificar um usuário ou admin logado no sistema, mas pode também descrever um módulo do sistema. Em aplicações que lidam como transações financeiras, campos adicionais (opcionais) podem identificar a origem e o destino da transação.

Tabelas de configuração de Sistema

Finalmente, é possível usar um banco de dados como um armazém centralizado de dados de configurações. Usualmente essa informação é mantida distribuída em diversos arquivos de texto, como scripts de inicialização ou arquivos de propriedades. Um banco de dados pode fornecer um local único e gerenciável para essas informações.

Além de parâmetros de inicialização, que são geralmente fornecidos ao sistema durante a abertura, pode-se pensar também em armazenar propriedades necessárias durante a execução, como strings de localização e mensagens.
Por fim, o banco de dados pode ser um local para manter documentação do sistema. Isso é mais útil, naturalmente, para informações que estão naturalmente em forma tabular (ao invés de texto livre), como listas de número de portas associadas ou chaves de memória compartilhadas. Mas essa abordagem não é limitada a códigos, Um dicionário de dados, que define os valores permitidos para cada campo, é uma necessidade para qualquer projeto não trivial. Isso também pode se tornar acessível a todos os desenvolvedores e administradores ao se armazenar os dados em um banco de dados.

Em qualquer caso, os dados são armazenados em pares simples chave/valor. Atributos adicionais para a tabela pode conter comentários ou ponteiros (URLs) para documentação relevante.

A vantagem principal de manter essas informações em um banco de dados é que o banco de dados fornece um repositório central para todas as informações relevantes, ao contrário a típica abordagem na qual os dados são espalhados por diversos arquivos.

Sumário e Recursos

Nesse artigo, cobrimos a normatização do banco de dados e as cinco Formas Normais. No processo de normatização, um desing original do banco de dados é transformado em um equivalente, que evita certas anomalias ao inserir, atualizar ou remover registro.s A normatização adequada também ajuda a identificar entidades corretamente. Também foi discutido alguns usos possíveis de uma base de dados.

Recursos

Traduzido de