2010-04-27 5 views
3

J'ai un problème et je ne sais pas quelle est la meilleure solution. Ok, j'ai 2 tables: posts (id, titre), posts_tags (post_id, tag_id). J'ai la tâche suivante: doit sélectionner des messages avec des étiquettes ID par exemple 4, 10 et 11. Pas exactement, la poste pourrait avoir d'autres tags en même temps. Alors, comment je pourrais le faire plus optimisé? Créer une table temporaire dans chaque requête? Ou peut-être une sorte de procédure stockée? À l'avenir, l'utilisateur pourrait demander à un script de sélectionner des messages avec n'importe quel nombre de tags (il peut s'agir de 1 tag ou 10 en même temps) et je dois être sûr que la méthode que je choisirai . Désolé pour mon anglais, Merci pour l'attention.requête many-to-many

Répondre

1
select id, title 
from posts p, tags t 
where p.id = t.post_id 
and tag_id in (4,10,11) ; 

?

+0

Il pourrait renvoyer des messages avec l'étiquette 4 OU 10 ou 11.Mais j'ai exactement besoin de tous ces trois tags dans un seul article. Le problème est ici :) – user52005

0

Est-ce que cela fonctionne?

select * 
from posts 
where post.post_id in 
    (select post_id 
    from post_tags 
    where tag_id = 4 
    and post_id in (select post_id 
        from post_tags 
        where tag_id = 10 
        and post_id in (select post_id 
            from post_tags 
            where tag_id = 11))) 
3

Cette solution suppose que (post_id, tag_id) en post_tags est appliquée comme UNIQUE:

SELECT id, title FROM posts 
    INNER JOIN post_tag ON post_tag.post_id = posts.id 
    WHERE tag_id IN (4, 6, 10) 
    GROUP BY id, title 
    HAVING COUNT(*) = 3 

Bien que ce n'est pas une solution pour toutes les combinaisons de balises possibles, il est facile de créer aussi dynamique SQL. Pour changer pour d'autres ensembles de variables, changez la liste IN() pour avoir toutes les variables, et COUNT (*) = pour vérifier le nombre de variables spécifiées. L'avantage de cette solution par rapport à la cascade d'un ensemble de JOINs est qu'il n'est pas nécessaire d'ajouter des JOINs, ou même des termes WHERE supplémentaires, lorsque vous modifiez la requête.

+0

+1 Pour utiliser GROUP BY avec HAVING. – Joop

0

Vous pouvez effectuer un compromis de stockage de temps en stockant un hachage unidirectionnel des noms de balises de tri triées par ordre alphabétique.

Lorsqu'un message est balisé, exécutez select t.name from tags t inner join post_tags pt where pt.post_id = [ID_of_tagged_post] order by t.name. Concaténez tous les noms de balises, créez un hachage à l'aide de l'algorithme MD5 et insérez la valeur dans une colonne à côté de votre message (ou dans une autre table jointe par une clé étrangère, si vous préférez). Lorsque vous souhaitez rechercher une combinaison spécifique de variables, il suffit d'exécuter (en rappelant de trier les noms de variables) select from posts p where p.taghash = MD5([concatenated_tag_string]).

0

Ce sélectionne tous les messages qui ont tout des étiquettes (4, 10, 11):

select distinct id, title from posts 
where exists ( 
    select * from posts_tags 
    where 
    post_id = id and 
    tag_id in (4, 10, 11)) 

Ou vous pouvez utiliser ceci:

select distinct id, title from posts 
join posts_tags on post_id = id 
where tag_id in (4, 10, 11) 

(Les deux seront optimalisés la de la même façon).

Ce sélectionne tous les messages qui ont tous des balises (4, 10, 11):

select distinct id, title from posts 
where not exists ( 
    select * from posts_tags t1 
    where 
    t1.tag_id in (4, 10, 11) and 
    not exists (
     select * from posts_tags as t2 
     where 
     t1.tag_id = t2.tag_id and 
     id = t2.post_id)) 

La liste des balises dans la clause in est ce qui change de manière dynamique (dans tous les cas).

Mais, cette dernière requête n'est pas vraiment rapide, vous pouvez donc utiliser quelque chose comme ceci:

create temporary table target_tags (tag_id int); 
insert into target_tags values(4),(10),(11); 
select id, title from posts 
    join posts_tags on post_id = id 
    join target_tags on target_tags.tag_id = posts_tags.tag_id 
    group by id, title 
    having count(*) = (select count(*) from target_tags); 
drop table target_tags; 

La partie qui change dynamiquement maintenant dans la deuxième déclaration (l'insert).

+0

Ceci sélectionnera les publications avec 1, 2 ou 3 des étiquettes désirées, pas les trois. Et il serait plus clairement écrit (et s'exécuter plus vite) s'il est exprimé comme un JOIN. –

+0

J'ai ajouté du code pour sélectionner les articles qui ont tous les tags. –

+0

J'ai également ajouté le code de jointure pour le premier cas. Bien que, un optimiseur de requête décent traitera le même que la requête avec la clause exists. –