Depois de muito tempo sem desenvolver nada muito complexo, que não passasse de pequenos scripts para gerenciamento de processos ou rotinas variadas no Linux, comecei a desenvolver um sistema em PHP para a minha empresa. Muito e muito tempo sem usar SQL, mas ainda com algumas lembranças de conceitos aprendidos na faculdade (tendo prática com MS SQL Server), com a cara e a coragem, fui à modelagem do banco de dados.
Criei algumas tabelas e seus campos (ou colunas) e comecei a relembrar dos conceitos de relacionamentos entre as tabelas (1:1, 1:N e M:N) e como usar o conceito de chaves estrangeiras para relacionar tabelas. Para quem ainda não estudou ou trabalhou com isso, o conceito é muito simples, vou tentar exemplificar usando o conceito de uma relação 1:N (um para N).
Digamos que temos uma tabela para cadastro de faturas de uma empresa composta basicamente pelo nome do cliente, data de emissão, data de vencimento, serviços prestados, valor unitário e valor total. Bom, imagine que a quantidade de serviços prestados e o valor unitário de cada um deles são variáveis para cada fatura, ou seja, em uma fatura você pode ter um serviço apenas ou vinte serviços distintos, sendo assim, como lidar com isso no banco de dados? Criar uma quantidade máxima possível de serviços para uma fatura e criar essa quantidade de campos na tabela do banco? Algo como servico_1, servico_2, …, servico_N? Bom, se você cadastrar apenas um serviço haverão N-1 registros nulos na sua tabela, sendo algo extremamente lusitano, hehehe.
Resolvemos isso da seguinte forma, pegamos os campos que não possuem quantidades variáveis (nome do cliente, data de emissão, data de vencimento e valor total) e colocamos em uma primeira tabela e pegamos os campos que possuem quantidades variáveis (serviços prestados e valor unitário) e colocamos em uma segunda tabela. Neste caso, a primeira tabela será a tabela pai e a segunda tabela será a tabela filha, onde para cada tabela pai, posso ter várias tabelas filhas, ou seja, para cada fatura, posso ter vários serviços (e respectivos valores), por isso este é um exemplo 1:N. Visualmente, ficaria algo como ilustra a figura a seguir:
Podemos notar que a tabela fatura_servicos possui uma chave primária a mais (fatura_id), o que significa que, além da sua própria chave primária (id), ela possui outra coluna onde ficará registrado o id da tabela pai a que ela pertence. Sendo assim, se eu tiver uma fatura com o id “1”, todas as faturas_servicos que tiverem o valor “1” na coluna fatura_id, serão pertencentes à fatura “1”, ou seja, esta chave identifica a qual tabela pai ela pertence.
Na prática seria algo como:
Tabela fatura:
| Field | Type | Null | Key | Default | Extra |
|---|---|---|---|---|---|
| id | int(10) unsigned | NO | PRI | auto_increment | |
| nome_cliente | varchar(45) | NO | (null) | (null) | (null) |
| data_emissao | datetime | NO | (null) | (null) | (null) |
| data_vencimento | datetime | NO | (null) | (null) | (null) |
| valor_total | float | NO | (null) | (null) | (null) |
Tabela fatura_servicos:
| Field | Type | Null | Key | Default | Extra |
|---|---|---|---|---|---|
| id | int(10) unsigned | NO | PRI | auto_increment | |
| servicos | varchar(45) | NO | (null) | (null) | (null) |
| valor_unitario | float | NO | (null) | (null) | (null) |
| fatura_id | int(10) unsigned | NO | PRI | (null) | (null) |
Realizamos uma inserção na tabela fatura através do comando:
INSERT INTO fatura (nome_cliente, data_emissao, data_vencimento, valor_total) VALUES (‘Rafael Puga’, ’2009-01-01′, ’2009-01-06′, ’200,00′);
Pegamos o ID da ultima inserção feita no banco de dados e salvamos na variável @last;
SELECT @last := LAST_INSERT_ID();
Inserimos serviços na tabela “fatura_servicos”, registrando sua tabela pai com a variável @last:
INSERT INTO fatura_servicos (servicos, valor_unitario, fatura_id) VALUES (‘Manutencao de micro-computador’, ’50,00′, @last);
INSERT INTO fatura_servicos (servicos, valor_unitario, fatura_id) VALUES (‘Manutencao de notebook’, ’150,00′, @last);
Pronto, nossos dados foram inseridos no banco e já estão devidamente relacionados. Para constatar isso fazendo alguns SELECT’s:
SELECT * FROM fatura WHERE id=@last;
SELECT * FROM fatura_servicos WHERE fatura_id=@last;
Poderíamos usar um JOIN para relacionar os resultados de ambas as tabelas em apenas em uma consulta, porém não vou abordar isso no momento.
Como pôde ser visto, o uso de chaves estrangeiras é apenas um conceito que pode ser trabalhado sem necessidade de alguma configuração especifica na tabela, porém em alguns bancos de dados, como o MySQL, podemos dizer ao banco que aquilo é uma chave estrangeira, tendo como vantagem um controle maior do banco que vai ajudar a evitar inconsistência de dados. Por exemplo, neste caso, se criamos uma fatura e cadastramos três serviços nela e excluirmos a entrada na tabela registro (pai), teremos entradas órfãs em fatura_servicos, causando então a inconsistência de dados.
O que fazemos neste caso então é definir quem é a chave estrangeira para o banco de dados com a seguinte sintaxe:
[CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (index_col_name, …)
REFERENCES tbl_name (index_col_name,…)
[ON DELETE
[ON UPDATE
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION
É importante sabermos que o uso de FOREIGN KEYS só é suportado pelo engine InnoDB do MySQL (geralmente é usado o MyISAM por padrão, devendo ser explicitamente definido então). No caso dos bancos de dados utilizados aqui como exemplo, a criação das tabelas e chaves estrangeiras seria da seguinte forma:
– —————————————————–
– Tabela `mydb`.`fatura`
– —————————————————–
DROP TABLE IF EXISTS `mydb`.`fatura` ;
CREATE TABLE IF NOT EXISTS `mydb`.`fatura` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`nome_cliente` VARCHAR(45) NOT NULL ,
`data_emissao` DATETIME NOT NULL ,
`data_vencimento` DATETIME NOT NULL ,
`valor_total` FLOAT NOT NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;
——————————————————-
– Tabela `mydb`.`fatura_servicos`
– —————————————————–
DROP TABLE IF EXISTS `mydb`.`fatura_servicos` ;
CREATE TABLE IF NOT EXISTS `mydb`.`fatura_servicos` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`servicos` VARCHAR(45) NOT NULL ,
`valor_unitario` FLOAT NOT NULL ,
`fatura_id` INT UNSIGNED NOT NULL ,
PRIMARY KEY (`id`, `fatura_id`) ,
INDEX `fk_fatura_servicos_fatura` (`fatura_id` ASC) ,
CONSTRAINT `fk_fatura_servicos_fatura`
FOREIGN KEY (`fatura_id` )
REFERENCES `mydb`.`fatura` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Note que no final das configurações dos campos da tabela, definimos o tipo no banco (InnoDB). Na tabela “fatura_servicos”, podemos ver a definição da chave estrangeira conforme a sintaxe padrão previamente comentada. Outra coisa importante de notarmos são as duas opções de ações no fim da criação “tabela_faturas”. Vemos as ações ON DELETE e ON UPDATE que podem ser configuradas com as opções RESTRICT, CASCADE, SET NULL, NO ACTION, que significam:
CASCADE: Se houver um DELETE da tabela pai, as tabelas filhas também serão deletadas, evitando inconsistência de dados. Se houver um UPDATE na chave primária da tabela pai, as chaves estrangeiras das tabelas filhas também serão atualizadas.
SET NULL: Caso uma a tabela pai seja deletada ou sua chave primária seja alterada, as tabelas filhas terão as suas chaves estrangeiras setadas para NULL (exceto quando forem criadas com o parâmetro NOT NULL).
NO ACTION: A tentativa de deletar ou atualizar uma tabela pai será ignorada caso ela tenha tabelas filhas.
RESTRICT: O mesmo que NO ACTION.
Apesar de a explicação ter sido baseada nas duas condições (DELETE e UPDATE), elas podem receber configurações individuais, como por exemplo, setar que ao deletar a ação será um CASCADE e ao atualizar um NO ACTION.
A partir de configurado, podemos usar normalmente as tabelas para fazer nossas query’s e mais tranquilos com a ajuda do próprio banco no gerenciamento dos relacionamentos. É basicamente isso, espero que seja útil a muitos!
Bom, por enquanto é só.
Atenciosamente,
Rafael Puga
Fontes:
http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html
http://dev.mysql.com/doc/refman/5.0/en/example-foreign-keys.html
Orkut: MySQL – Brasil


20 comentários
Feed de comentários deste artigo
julho 28, 2009 às 11:40 am
Rael Max
hmm, o ORM do django cuida disso pra mim. 8)
Ótima explicação sobre foreignkeys deu pra sacar bem como funciona.
agosto 27, 2009 às 12:45 pm
Henrique
Poxa parabéns, me ajudou muito
fevereiro 10, 2010 às 1:22 pm
Julio
Muito boa a explicação!
maio 6, 2010 às 1:50 pm
Óscar Alves
Muito bom! Finalmente aprendi de uma vez por todas…longa vida pra você
maio 16, 2010 às 5:35 pm
Fabio
estava apanhando pra fazer foreign key no mysql, está muito bem explicado!
obrigado.
maio 16, 2010 às 7:32 pm
Rafael Puga
Fico feliz em saber que o post está sendo útil =)
junho 28, 2010 às 9:17 am
Alex Xavier
Parabéns estava precisando de um post deste para o serviço que terei que fazer para empresa que trabalho.
agosto 31, 2010 às 2:15 pm
Elias K
Valeu pelo post. Ajudou muito a entender o conceito. Também ajudou para uma solução de BD que eu precisava. Como sou iniciante, o caminho das pedras vale ouro.
[]s
novembro 12, 2010 às 10:04 am
Thaigo Paes
Belo tutorial, foi pro favoritos pra repassar qdo me questionarem
dezembro 3, 2010 às 1:04 pm
Pablo
Parabéns, excelente post, muito obrigado!
fevereiro 10, 2011 às 2:43 pm
leonardo
Dúvida:
O campo FK realmente receberá o valor do campo referenciado?
março 7, 2011 às 8:34 am
Ruben
Aproveitando a pergunta do Leonardo, podemos referenciar qualquer campo da outra tabela? Podemos (ou devemos) referencia o ID primario?
março 23, 2011 às 1:05 pm
luciana
Obrigada pelo código, funcionou que foi uma beleza!!!
abril 1, 2011 às 3:02 pm
manoel
Ótimo, explicação basta clara, tinha uma pequena duvida sobre os tipos ações, mas depois de ler este artigo, tudo ficou muito claro.
julho 29, 2011 às 2:10 pm
Romulo Lima
Era exatamento o que eu estava procurando, bem objetivo e claro.
Excelente artigo.
abril 9, 2012 às 3:18 pm
jjojdf
vc é caraaa do banco de dados legal
setembro 26, 2011 às 9:04 am
Milton
Gostei muito do Post…
Agora, as chaves estrangeiras, que funcionam com UPDATE e DELETE como vc disse, funcionam tb com INSERT?
novembro 10, 2011 às 10:05 am
Ivini Designer
Um professor da faculdade me disse que isso não ocorria no Mysql, deletar um registro pai e automaticamente suas filhas, mas discordei com ele, e achei esse post, só não sabia que funciona no ENGINE = innoDB.
Valeu, excelente.
novembro 21, 2011 às 6:05 pm
Kyokai Paia
Nossa
obrigado e parabens
dezembro 20, 2011 às 11:38 am
Edson Lima
Cara belissimo post, estava procurando algo parecido valeu mesmo.