Correspondance plusieurs-à-plusieurs SQL
-
09-06-2019 - |
Question
J'implémente un système de balisage pour un site Web.Il existe plusieurs balises par objet et plusieurs objets par balise.Ceci est accompli en conservant une table avec deux valeurs par enregistrement, une pour les identifiants de l'objet et la balise.
Je cherche à écrire une requête pour trouver les objets qui correspondent à un ensemble de balises donné.Supposons que j'aie les données suivantes (au format [object] -> [tags]*)
apple -> fruit red food
banana -> fruit yellow food
cheese -> yellow food
firetruck -> vehicle red
Si je veux faire correspondre (rouge), je devrais acheter une pomme et un camion de pompier.Si je veux assortir (fruits, nourriture), je devrais obtenir (pomme, banane).
Comment écrire une requête SQL pour faire ce que je veux ?
@Jérémy Ruten,
Merci pour votre réponse.La notation utilisée a été utilisée pour donner des exemples de données - ma base de données a une table avec 1 identifiant d'objet et 1 balise par enregistrement.
Deuxièmement, mon problème est que j'ai besoin d'obtenir tous les objets correspondant à toutes les balises.En remplaçant votre OR par un AND comme ceci :
SELECT object WHERE tag = 'fruit' AND tag = 'food';
Ne donne aucun résultat lors de l'exécution.
La solution
Donné:
- table d'objets (identifiant de clé primaire)
- table objecttags (clés étrangères objectId, tagid)
table de balises (identifiant de clé primaire)
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';
Cela semble à l'envers, puisque vous voulez and, mais le problème est que 2 balises ne sont pas sur la même ligne, et par conséquent, an and ne donne rien, puisqu'une seule ligne ne peut pas être à la fois un fruit et un aliment.Cette requête produira généralement des doublons, car vous obtiendrez 1 ligne de chaque objet, par balise.
Si vous souhaitez vraiment faire un et dans ce cas, vous aurez besoin d'un group by
, et un having count = <number of ors>
dans votre requête par exemple.
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;
Autres conseils
Oh mon Dieu, j'ai peut-être mal interprété votre commentaire original.
La manière la plus simple de procéder en SQL serait d'avoir trois tables :
1) Tags ( tag_id, name )
2) Objects (whatever that is)
3) Object_Tag( tag_id, object_id )
Vous pouvez ensuite poser pratiquement toutes les questions que vous souhaitez sur les données rapidement, facilement et efficacement (à condition de les indexer de manière appropriée).Si vous voulez faire preuve de fantaisie, vous pouvez également autoriser les balises multi-mots (il existe une manière élégante et une manière moins élégante à laquelle je peux penser).
Je suppose que c'est ce que vous avez, donc ce SQL ci-dessous fonctionnera :
La manière littérale :
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 )
Il existe également d'autres façons de le faire, telles que :
SELECT oid
FROM tags
WHERE tag = 'Apple'
INTERSECT
SELECT oid
FROM tags
WHERE tag = 'Fruit'
@Kyle :Votre requête devrait ressembler davantage à :
SELECT object WHERE tag IN ('fruit', 'food');
Votre requête recherchait des lignes où la balise était à la fois un fruit ET un aliment, ce qui est impossible étant donné que le champ ne peut avoir qu'une seule valeur, pas les deux en même temps.
Combinez la suggestion de Steve M. avec celle de Jeremy, vous obtiendrez un seul disque avec ce que vous recherchez :
select object
from tblTags
where tag = @firstMatch
and (
@secondMatch is null
or
(object in (select object from tblTags where tag = @secondMatch)
)
Maintenant, cela ne s’adapte pas très bien, mais cela obtiendra ce que vous recherchez.Je pense qu'il existe une meilleure façon de procéder afin que vous puissiez facilement avoir un nombre N d'éléments correspondants sans beaucoup d'impact sur le code, mais cela m'échappe actuellement.
Je recommande le schéma suivant.
Objects: objectID, objectName
Tags: tagID, tagName
ObjectTag: objectID,tagID
Avec la requête suivante.
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')
Je suggère de faire en sorte que votre table ait 1 balise par enregistrement, comme ceci :
apple -> fruit
apple -> red
apple -> food
banana -> fruit
banana -> yellow
banana -> food
Alors tu pourrais juste
SELECT object WHERE tag = 'fruit' OR tag = 'food';
Si vous voulez vraiment procéder à votre manière, vous pouvez le faire comme ceci :
SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %';