【问题标题】:HABTM Query helpHABTM 查询帮助
【发布时间】:2010-12-24 15:57:33
【问题描述】:

“文章”和“标签”之间存在 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)

【问题讨论】:

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


    【解决方案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
    

    【讨论】:

      【解决方案2】:

      有两种常见的解决方案。

      • 第一个解决方案使用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() 返回相应组中值的逗号分隔列表。

      【讨论】:

      • 感谢您的解释...此外,是否可以在此查询中返回与匹配文章关联的所有标签?