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.

Foi útil?

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 %';
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top