HABTM查询帮助

时间:2009-12-09 00:51:42

标签: mysql sql has-and-belongs-to-many sql-match-all

我在'articles'和'tags'之间有HABTM关系

问题:我只是在寻找带有“体育”和“户外”标签的文章,而不是仅包含其中一个标签的文章。

我试过了:

SELECT DISTINCT article.id, article.name FROM articles
inner JOIN tags ON (tags.name IN ('outdoors', 'sports')
inner JOIN articles_tags ON articles_tags.article_id = article.id AND articles_tags.tag_id = tags.id

...但它让我得到的文章只有运动,只有户外运动+户外运动

问题使用什么是正确的查询? (我正在使用MySQL)

2 个答案:

答案 0 :(得分:1)

试试这个:

SELECT a1.id, a1.name FROM articles a1
    JOIN tags t1 ON t1.name ='outdoors'
    JOIN articles_tags at1 ON at1.article_id = a1.id AND at1.tag_id = t1.id
    JOIN tags t2 ON t2.name = 'sports'
    JOIN articles_tags at2 ON at2.article_id = a1.id AND at2.tag_id = t2.id

答案 1 :(得分:0)

有两种常见的解决方案。

  • 第一个解决方案使用GROUP BY计算与“户外”或“体育”匹配的每篇文章的标记,然后仅返回包含这两个标记的组。

    SELECT a.id, a.name
    FROM articles AS a
    INNER JOIN articles_tags AS at ON (a.id = at.article_id)
    INNER JOIN tags AS t ON (t.id = at.tag_id)
    WHERE t.name IN ('outdoors', 'sports')
    GROUP BY a.id
    HAVING COUNT(DISTINCT t.name) = 2;
    

    此解决方案对某些人来说更具可读性,添加值更为直接。但是MySQL中的GROUP BY查询往往会产生一个损害性能的临时表。

  • 另一个解决方案使用每个不同标记JOIN。通过使用内部联接,查询自然会限制与您指定的所有标记匹配的文章。

    SELECT a.id, a.name
    FROM articles AS a
    INNER JOIN articles_tags AS at1 ON (a.id = at1.article_id)
    INNER JOIN tags AS t1 ON (t1.id = at1.tag_id AND t1.name = 'outdoors')
    INNER JOIN articles_tags AS at2 ON (a.id = at2.article_id)
    INNER JOIN tags AS t2 ON (t2.id = at2.article_id AND t2.name = 'sports');
    

    假设tags.namearticles_tags.(article_id,tag_id)都有UNIQUE个约束,则不需要DISTINCT个查询修饰符。

    这种类型的查询倾向于在MySQL上优于GROUP BY解决方案,假设您已经定义了适当的索引。


在评论中重新提出你的后续问题,我会做这样的事情:

SELECT a.id, a.name, GROUP_CONCAT(t3.tag) AS all_tags
FROM articles AS a
INNER JOIN articles_tags AS at1 ON (a.id = at1.article_id)
INNER JOIN tags AS t1 ON (t1.id = at1.tag_id AND t1.name = 'outdoors')
INNER JOIN articles_tags AS at2 ON (a.id = at2.article_id)
INNER JOIN tags AS t2 ON (t2.id = at2.article_id AND t2.name = 'sports');
INNER JOIN articles_tags AS at3 ON (a.id = at3.article_id)
INNER JOIN tags AS t3 ON (t3.id = at3.article_id);
GROUP BY a.id;

这仍然只会找到同时包含“户外”和“体育”标签的文章,但它会将这些文章进一步加入所有标签。

这将为每篇文章返回多行(每个标记一行),因此我们再次使用GROUP BY将每篇文章减少到一行。 GROUP_CONCAT()返回相应组中值的逗号分隔列表。