TransWikia.com

Qual a melhor forma de se construir uma tabela MySQL com itens de receitas? Todos ingredientes em uma linha ou um ingrediente por linha?

Stack Overflow em Português Asked by Leandro Marzullo on October 6, 2020

Estou acostumado a criar sites com poucos registros de banco de dados, portanto não me preocupo com a velocidade de consulta e não tenho experiência com grande volumes de informações.

Mas agora estou criando um site de receitas, com o objetivo de ter mais de 100 mil receitas e estou com uma dúvida na estruturação da tabela MYSQL na parte de ingredientes.

Como montar para que a busca seja mais rápida?

Pensem em 2 formas de montar a tabela de ingredientes:

Forma 1: 
id|idreceita|listadeingredientes
1 |   701   |1/2 de feijão;sal a gosto;2 folhas de louro;pimenta a gosto;2,5 litros agua

Forma 2
id|idreceita|ingrediente
1 |   701   |1/2 de feijao
2 |   701   |sal a gosto
3 |   701   |2 folhas de louro
4 |   701   |pimenta a gosto
5 |   701   |2,5 listros de agua

A busca seria feita com:

SELECT FROM tabela_ingredientes WHERE (listadeingredientes ou ingrediente) LIKE '%louro%'

Minha dúvida: é melhor ter uma tabela com 100.000 registros, onde o campo de ingredientes será grande (na média 200 caracteres) ou uma tabela com 800.000 registros onde o campo ingredientes será menor, terá na média 25 caracteres

3 Answers

Cem mil parece uma quantidade quase irrisória e não muda nada no que está fazendo, então não se preocupe. Ter 200 caracteres também é irrisório.

Ter menos tabelas sempre torna mais rápido, se souber o que está fazendo. Sem saber qualquer coisa pode dar problema.

Toda modelagem de dados deve ser feita baseada em requisitos reais existentes e bem definidos. Já fez isso? Só você os sabe e por isso só você pode responder a pergunta com propriedade.

Existe algum motivo para manter estes itens em outra tabela? Existe alguma combinação? Acesso de forma individual ao item ao acesso à receita através do item? Parece que não parece ser uma descrição sem formatação específica e sem muito critério. Mesmo que vá fazer uma busca pelo texto muda pouco se está tudo em uma tabela separado. Sendo apenas uma descrição, posso ter interpretado errado pelo exemplo dado, então crie apenas uma coluna para guardar os ingredientes.

Para separar você tem que ter uma justificativa, tem que ter um motivo que faça precisar o dado separado. Você tem?

Nos comentários (leia abaixo para pegar o contexto) eu coloquei que é possível fazer isto se separar a quantidade das descrição do item, mas isso só vale se for para fazer normalização completa, ou seja se você usar um id e aí ter controle que a descrição do ingrediente é sempre a mesma, porque se deixar a pessoa escrever o nome do ingrediente em cada receita não terá padronização, então terá pouca ou nenhuma vantagem, se não tiver desvantagem, o que acho que terá. E isso só seria útil para um controle bem mais complexo, que não parece ser o caso do AP.

O uso do LIKE é ineficiente em ambos os casos, deveria procurar por uma solução de FTS.

Correct answer by Maniero on October 6, 2020

Eu vejo pelo menos três passos para se fazer a solução ideal:

  1. Resolva o problema;
  2. Faça a solução ficar bonita;
  3. Faça a solução ficar rápida;

Parece que você está começando pelo item 3, se preocupando se a solução será rápida sem antes nem ter uma solução para o problema.

Nos comentários você colocou um requisito de extrema importância para o problema:

"sim, pq vai ter uma busca específica por ingredientes, por exemplo, sobrou pimentão na geladeria, que tipo de receita posso fazer com pimentão" [sic] (fonte)

Isso indica que a partir de um determinado ingrediente você precisa localizar as receitas. Mais que isso, você terá que procurar não só por ingrediente, mas por quantidade também. Se a ideia é permitir uma busca de receitas que aproveitem os ingredientes disponíveis, do que adiantaria trazer uma receita que exige dois pimentões se tenho apenas um?

Também existe o problema de normalização do nome do ingrediente. Eu pesquiso por "pimentão", mas há receitas cadastradas com "PIMentões", "Pimentoes", "Pitentoes", "pimentao", "pimentão verde", "pimentão vermelho", "pimento" etc. Isso seria um sério problema na tarefa de trazer um resultado de uma busca fiel.

Você contorna esse problema criando uma tabela para gerenciar os ingredientes:

create table ingredientes (
  ingrediente_id integer not null auto_increment,
  ingrediente_nome varchar(255) not null,
  primary key (ingrediente_id)
);

Isso na forma mais básica. Você pode incrementar a tabela conforme suas outras necessidades; por exemplo, você pode criar uma coluna que sinaliza se determinado ingrediente possui glúten e com isso já classificar uma receita como livre de glúten, pode adicionar colunas com informações nutricionais do ingrediente e com isso estimar valores nutricionais da receita (calorias, sódio, etc), pode criar uma coluna que sinaliza se determinado ingrediente é de origem animal e com isso classificar uma receita como vegana. As possibilidades são muitas.

Para as receitas, você criará outra tabela:

create table receitas (
  receita_id integer not null auto_increment,
  receita_titulo varchar(255) not null,
  primary key (receita_id)
);

Isso na forma mais básica. Você pode incrementar a tabela conforme suas outras necessidades; por exemplo, você pode criar uma coluna que informa o tempo de preparo da receita (improvável, pois talvez seria melhor definir o tempo de cada passo do modo de preparo ao invés disso), você pode definir uma coluna que defina para quais refeições a receita é melhor adequada (almoço, lanche, jantar, etc).

E, finalmente, como uma receita pode ter mais de um ingrediente e um ingrediente pode estar em mais de uma receita, temos uma relação clara de N:N, necessitando uma tabela de associação entre ingredientes e receitas. Essa tabela deverá possuir as duas chaves estrangeiras que associará o ingrediente à receita e outras colunas com informações da associação. Por exemplo, a receita 1, pimentão recheado, possui o ingrediente 1, pimentão verde. Mas quantos? Ou seja, a tabela de relação precisará definir a quantidade de ingrediente na receita. A relação entre a receita 1 e o ingrediente 1 terá quantidade 3. Mas 3 o quê? Unidades, xícaras, colheres? Neste caso provavelmente seriam unidades. Assim, a relação, além de definir a quantidade, deverá definir a unidade de medida. Se colocar como uma coluna de texto voltamos ao problema inicial: faltará normalização nos valores. Uma receita pode possuir "1 colher de sal", outra "1 culher de sal", outra "1 colheres de sal", etc.

Acho que, se você está seguindo a resposta, já entendeu o próximo passo:

create table medidas (
  medida_id integer not null auto_increment,
  medida_nome_singular varchar(255) not null,
  medida_nome_plural varchar(255),
  primary key (medida_id)
);

E, na tabela de relação teremos mais uma relação: chave estrangeira para medidas.

create table receitas_ingredientes (
  receita_ingrediente_id integer not null auto_increment,
  receita_id integer not null,
  ingrediente_id integer not null,
  receita_ingrediente_quantidade float not null,
  medida_id integer not null,
  primary key (receita_ingrediente_id),
  foreign key (receita_id) references receitas(receita_id),
  foreign key (ingrediente_id) references ingredientes(ingrediente_id),
  foreign key (medida_id) references medidas(medida_id)
);

Isso na forma mais básica. Você pode incrementar a tabela conforme suas outras necessidades; por exemplo, você pode criar uma coluna que define um fator de multiplicação para diferentes quantidades de porções. Se minha receita consta para utilizar 1 pimentão que rende 1 porção, caso eu queria que para duas porções retorne a quantidade 2, bastaria definir o fator como 1.0. Mas talvez eu não precise duplicar também a quantidade de sal nesse caso, então o fator poderia ser 0.65, assim a quantidade para duas porções seria 1.3x a quantidade para uma. Isso permitiria que suas receitas ficassem dinâmicas conforme a necessidade do seu usuário.

Tenho um pimentão verde em casa, o que eu posso fazer de almoço?

select receitas.*
from receitas_ingredientes
join receitas using (receita_id)
where ingrediente_id = 1  # Pimentão verde
and receita_ingrediente_quantidade <= 1  # Quantidade 1
and medida_id = 1;  # Unidade de medida: unidade

É eficiente? Não sei, mas resolve seu problema. Passo 1 concluído. Agora você pode rever todos os requisitos do seu projeto (acredito que já tenha feito isso antes de começar a estruturar o banco de dados) e, com isso, você poderá deixar sua solução bonita. Você pode criar todas as colunas que necessitar, criar funções, procedures, etc. Passo 2 concluído. Teste o sistema, está rápido? Ótimo, você conseguiu que uma solução bonita também fosse suficientemente rápida - na maioria dos casos, quando os passos 1 e 2 são bem executados, o passo 3 pode ser ignorado.

Não está rápido? Reveja os passos 1 e 2. Talvez a solução que implementou não foi a melhor. Identifique os gargalos, veja se pode implementar de outra forma; se sim, reimplemente e teste novamente. Se seu problema é realmente tão específico assim que mesmo fazendo muito bem os passos 1 e 2 sua solução ainda não atende aos requisitos de performance você poderá analisar como contorná-los. Você pode criar views, views materializadas, criar estruturas de somente leitura, fazer camadas de cache, etc. É difícil dizer qual será a solução sem conhecer os gargalos. Isso somente você poderá dizer - se realmente for necessário.

Como o Maniero disse na resposta dele, um banco de dados consegue trabalhar com milhões de registros. Alguns milhares não devem ser um problema. Se for, volte ao passo 1. A título de curiosidade, aqui temos umas 15 tabelas que passam de 300 mil registros, cinco tabelas que passam de 1 milhão de registros (a maior passa de 15.5 milhões) e até hoje não tivemos problema com performance.

Answered by Woss on October 6, 2020

Do jeito que colocou não faz muita diferença, mas separar os ingredientes em uma outra tabela pode permitir busca por ingrediente considerando a quantidade, porém na estrutura da tabela teria a coluna nome e quantidade:

id|idreceita|quantidade|nome
1 |   701   |1/2       | feijao
2 |   701   |a gosto   | sal 
3 |   701   |2         | folhas de louro
4 |   701   |a gosto   | pimenta 
5 |   701   |2,5 l     | agua

Mas observe que a quantidade não é algo tão simples de lidar, há litros, mililitros, xícaras, colheres, kilogramas, gramas, unidades seria interessante converter todos os itens para um padrão, por exemplo, todos os líquidos usarem litros e todos os sólidos usarem gramas

Se achar que vale apena o trbalho terá uma busca mais exata, pois a pessoa quer fazer algo com o pimentão dela, mas ela só tem 1, se houver receitas com 2 ou mais não lhe adianta

Answered by Costamilam on October 6, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP