Não vou começar falando de SQL, como geralmente é feito, mesmo que copiando trechos da Wikipedia.
Vejamos um conceito importante e interessante, mas que é fatalmente sonegado aos programadores, é o conceito de entidade. Basta tentar pesquisar por este termo, e notar como o conteúdo é escasso.
Talvez por isso, os resultados são sistemas mal modelados, não normalizados, em que a aplicação tenta resolver diversos problemas da estrutura, e onde qualquer novo requisito, gera um grande transtorno, pois a análise para criação do modelo, não foi feita corretamente, de modo a deixar a estrutura robusta e expansível.
Um exercício rápido
- Volante, Pára-choque, Retrovisores, Câmbio, Chassis, Rodas, Motor, Portas…
O que lhe veio a mente? Qual objeto do nosso mundo agrega essas partes que citei? Espero que a sua resposta seja ‘um carro’. Faz parte do senso comum.
Mais uma vez
- Galhos, Folhas, Raíz, Tronco, Copa, Calotas..
Uma árvore? Só as ‘calotas’ que parecem não estar no lugar correto, pois não combinam com os outros itens, e não conseguimos imaginar que as calotas, devam pertencer a nossa árvore.
Melhor mover as ‘calotas’ para o ‘carro’ lá de cima, para se encaixar melhor em uma normalidade aceitável.
Ao iniciar o levantamento de informações, o processo é mais ou menos parecido com esse. O carro, e a árvore são as nossas entidades e cada um dos itens que citei, são os atributos particulares dessas entidades. A idéia é saber a quem deve pertencer cada atributo, e sermos capazes de definirmos as entidades do nosso sistema.
Formalmente
- Uma entidade possui atributos.
- Os atributos são as características e não devem conter um grupo de informações.
- Não existem entidades com menos de 2 atributos. Logo, cada entidade, é em si, um grupo de atributos.
Trazendo de forma livre para a nossa realidade, cada Entidade é uma tabela, e cada Atributo é cada uma das colunas dessas tabelas.
O ponto que quero levantar é a compreensão do conceito. Se tenho que fazer um cadastro de usuário, preciso pensar o que a entidade usuário significa para o meu sistema e o que preciso saber dela ou não.
CREATE TABLE `test`.`usuario` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`nome` VARCHAR( 50 ) NOT NULL
) ENGINE = InnoDB;
A nossa entidade usuario possui um `nome`, e um `id`, que é um identificador único desse objeto no nosso modelo.
Agora o cliente nos informa que ele precisa saber dos usuarios dele, pelo menos:
- um telefone residencial, e
- um celular.
Num primeiro momento, a nossa reação será adicionar essas informações à entidade usuário, pois o telefone e o celular pertencem respectivamente a cada usuário nosso.
ALTER TABLE `usuario` ADD `telefone` VARCHAR( 14 ) NOT NULL ,
ADD `celular` VARCHAR( 14 ) NOT NULL
Mas se nosso chefe vem e nos informa que agora ele precisa também do Telefone Comercial do nosso usuário e quem sabe talvez um Telefone de Contato? É nesse momento que o nosso modelo pode naufragar ou não.
Adicionar mais essas 2 colunas a tabela seria um erro no ponto de vista da modelagem de negócios, pois estaríamos novamente, subestimando a aplicação. E se lá pra frente, ele decidir incluir o Pager (ainda usam isso? hahaha), ou o id Nextel do usuário?
Nesta hora, deveria começar a ficar claro que essa expansão clama por uma nova entidade na nossa modelagem.
ALTER TABLE `usuario` DROP `telefone` , DROP `celular` ;
Estrutura da nova entidade
Dados cadastrados:
Portanto, se precisamos de inserir mais formas de contato, basta adicionar um registro a essa tabela. E isso pode ser facilmente feito na nossa aplicação, sem a necessidade de alterarmos a estrutura do modelo.
INSERT INTO `test`.`contato` (`id`, `nome`) VALUES (NULL, 'Nextel');
A única questão não respondida ainda é como vincular os dados do usuário a esta nossa nova entidade. Faremos então outra tabela, já bem normalizada, da seguinte forma:
CREATE TABLE `test`.`contato_usuario` (
`id_usuario` INT NOT NULL ,
`id_contato` INT NOT NULL ,
`descricao` VARCHAR( 70 ) NOT NULL
) ENGINE = InnoDB;
Essa nossa nova tabela será responsável por fazer a ligação entre as nossas entidades.
A relação entre as tabelas é evidente e o nosso JOIN para resgatar esses dados fica da seguinte forma:
SELECT `usuario`.`id` AS `id_usuario` , `usuario`.`nome` AS `nome_usuario` ,
`contato`.`nome` AS `nome_contato` , `contato_usuario`.`descricao`
FROM `usuario`
INNER JOIN `contato_usuario` ON `usuario`.`id` = `contato_usuario`.`id_usuario`
INNER JOIN `contato` ON `contato`.`id` = `contato_usuario`.`id_contato`
Saída:
Continuando, surgem novos atributos:
data de nascimento, cidade natal, email, estado civil..
Precisamos dividir esses atributos nas nossas entidades. A data de nascimento, a cidade natal e estado civil cabem muito bem na entidade usuário. Já o email, podemos colocar na entidade contato, concorda?
Se mais adiante precisarmos de um ‘email_secundario’, para proteção do sistema, onde o usuário poderá resgatar a senha dele na nossa aplicação, bastará novamente, inserirmos um registro na entidade contato, sem a menor alteração do modelo que criamos.
Esse particionamento que fizemos tem o seu custo. A maior complexidade do modelo, a queda de performance (afinal, agora consultamos 3 tabelas e não apenas uma). E em contrapartida, temos os ganhos que essa modelagem trouxe a estrutura da nossa aplicação.
Analise bem os requisitos do seu sistema antes de implementá-lo. Não tome todo e qualquer tutorial como lei ou verdade absoluta.
Dump completo do SQL usado:
-- phpMyAdmin SQL Dump
-- version 3.3.8.1
-- http://www.phpmyadmin.net
--
-- Servidor: localhost
-- Tempo de Geração: Mar 29, 2011 as 02:54 AM
-- Versão do Servidor: 5.5.8
-- Versão do PHP: 5.3.4
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Banco de Dados: `test`
--
-- --------------------------------------------------------
--
-- Estrutura da tabela `contato`
--
CREATE TABLE IF NOT EXISTS `contato` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nome` varchar(40) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
--
-- Extraindo dados da tabela `contato`
--
INSERT INTO `contato` (`id`, `nome`) VALUES
(1, 'Telefone Residencial'),
(2, 'Telefone Comercial'),
(3, 'Celular'),
(4, 'Nextel');
-- --------------------------------------------------------
--
-- Estrutura da tabela `contato_usuario`
--
CREATE TABLE IF NOT EXISTS `contato_usuario` (
`id_usuario` int(11) NOT NULL,
`id_contato` int(11) NOT NULL,
`descricao` varchar(70) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Extraindo dados da tabela `contato_usuario`
--
INSERT INTO `contato_usuario` (`id_usuario`, `id_contato`, `descricao`) VALUES
(1, 2, '(11) 1234-5678'),
(1, 4, '55*11*1111'),
(2, 1, '(12) 4321-8765'),
(2, 2, '(11) 8765-4321'),
(2, 4, '55*22*2222');
-- --------------------------------------------------------
--
-- Estrutura da tabela `usuario`
--
CREATE TABLE IF NOT EXISTS `usuario` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nome` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Extraindo dados da tabela `usuario`
--
INSERT INTO `usuario` (`id`, `nome`) VALUES
(1, 'Bruno'),
(2, 'Silveira');