Correspondência SQL muitos para muitos
-
09-06-2019 - |
Pergunta
Estou implementando um sistema de marcação para um site.Existem várias tags por objeto e vários objetos por tag.Isso é feito mantendo uma tabela com dois valores por registro, um para os ids do objeto e o tag.
Estou procurando escrever uma consulta para encontrar os objetos que correspondem a um determinado conjunto de tags.Suponha que eu tenha os seguintes dados (no formato [objeto] -> [tags]*)
apple -> fruit red food
banana -> fruit yellow food
cheese -> yellow food
firetruck -> vehicle red
Se eu quiser combinar (vermelho), devo comprar uma maçã e um caminhão de bombeiros.Se eu quiser combinar (fruta, comida), devo pegar (maçã, banana).
Como escrevo uma consulta SQL para fazer o que quero?
@Jeremy Ruten,
Obrigado pela sua resposta.A notação usada foi usada para fornecer alguns dados de amostra - meu banco de dados possui uma tabela com 1 ID de objeto e 1 tag por registro.
Segundo, meu problema é que preciso obter todos os objetos que correspondam a todas as tags.Substituindo seu OR por um AND assim:
SELECT object WHERE tag = 'fruit' AND tag = 'food';
Não produz resultados quando executado.
Solução
Dado:
- tabela de objetos (id da chave primária)
- tabela objecttags (chaves estrangeiras objectId, tagid)
tabela de tags (id da chave primária)
SELECT distinct o.* from object o join objecttags ot on o.Id = ot.objectid join tags t on ot.tagid = t.id where t.Name = 'fruit' or t.name = 'food';
Isso parece ao contrário, já que você quer e, mas o problema é que 2 tags não estão na mesma linha e, portanto, um e não produz nada, já que uma única linha não pode ser uma fruta e um alimento ao mesmo tempo.Esta consulta normalmente produzirá duplicatas, porque você obterá 1 linha de cada objeto, por tag.
Se você realmente deseja fazer um e neste caso, você precisará de um group by
, e um having count = <number of ors>
na sua consulta, por exemplo.
SELECT distinct o.name, count(*) as count
from object o join objecttags ot on o.Id = ot.objectid
join tags t on ot.tagid = t.id
where t.Name = 'fruit' or t.name = 'food'
group by o.name
having count = 2;
Outras dicas
Oh meu Deus, posso ter interpretado mal seu comentário original.
A maneira mais fácil de fazer isso em SQL seria ter três tabelas:
1) Tags ( tag_id, name )
2) Objects (whatever that is)
3) Object_Tag( tag_id, object_id )
Em seguida, você pode fazer praticamente qualquer pergunta que desejar sobre os dados de maneira rápida, fácil e eficiente (desde que indexe adequadamente).Se você quiser ser sofisticado, também pode permitir tags com várias palavras (há uma maneira elegante e uma maneira menos elegante, posso imaginar).
Presumo que é isso que você tem, então este SQL abaixo funcionará:
A maneira literal:
SELECT obj
FROM object
WHERE EXISTS( SELECT *
FROM tags
WHERE tag = 'fruit'
AND oid = object_id )
AND EXISTS( SELECT *
FROM tags
WHERE tag = 'Apple'
AND oid = object_id )
Existem também outras maneiras de fazer isso, como:
SELECT oid
FROM tags
WHERE tag = 'Apple'
INTERSECT
SELECT oid
FROM tags
WHERE tag = 'Fruit'
@Kyle:Sua consulta deve ser mais parecida com:
SELECT object WHERE tag IN ('fruit', 'food');
Sua consulta estava procurando linhas onde a tag fosse fruta AND comida, o que é impossível visto que o campo só pode ter um valor, não os dois ao mesmo tempo.
Combine a sugestão de Steve M. com a de Jeremy e você obterá um único registro com o que procura:
select object
from tblTags
where tag = @firstMatch
and (
@secondMatch is null
or
(object in (select object from tblTags where tag = @secondMatch)
)
Agora, isso não escala muito bem, mas conseguirá o que você está procurando.Acho que há uma maneira melhor de fazer isso, para que você possa facilmente ter um número N de itens correspondentes sem muito impacto no código, mas atualmente isso me escapa.
Eu recomendo o seguinte esquema.
Objects: objectID, objectName
Tags: tagID, tagName
ObjectTag: objectID,tagID
Com a seguinte consulta.
select distinct
objectName
from
ObjectTab ot
join object o
on o.objectID = ot.objectID
join tabs t
on t.tagID = ot.tagID
where
tagName in ('red','fruit')
Eu sugiro que sua tabela tenha 1 tag por registro, assim:
apple -> fruit
apple -> red
apple -> food
banana -> fruit
banana -> yellow
banana -> food
Então você poderia simplesmente
SELECT object WHERE tag = 'fruit' OR tag = 'food';
Se você realmente quiser fazer do seu jeito, você pode fazer assim:
SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %';