【问题标题】:many-to-many query多对多查询
【发布时间】:2011-02-13 01:45:00
【问题描述】:

我有一个问题,我不知道什么是更好的解决方案。 好的,我有 2 个表:posts(id,title),posts_tags(post_id,tag_id)。 我有下一个任务:必须选择带有标签 id 的帖子,例如 4、10 和 11。 不完全是,post 可以同时具有任何其他标签。 那么,我怎样才能做到更优化呢?在每个查询中创建临时表?或者可能是某种存储过程? 将来,用户可以要求脚本选择带有任意数量标签的帖子(它可能只有 1 个标签或同时有 10 个标签),我必须确保我将选择的方法是解决我的问题的最佳方法。 对不起我的英语,谢谢关注。

【问题讨论】:

    标签: sql mysql database-design many-to-many


    【解决方案1】:

    此解决方案假定 post_tags 中的 (post_id, tag_id) 强制为 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
    

    虽然它不是所有可能的标签组合的解决方案,但它很容易创建为动态 SQL。要更改其他标签集,请更改 IN () 列表以包含所有标签,并更改 COUNT(*) = 以检查指定的标签数量。此解决方案相对于将一堆 JOIN 级联在一起的优势在于,您在更改请求时不必添加 JOIN 甚至额外的 WHERE 术语。

    【讨论】:

      【解决方案2】:
      select id, title
      from posts p, tags t
      where p.id = t.post_id
      and tag_id in ( 4,10,11 ) ;
      

      ?

      【讨论】:

      • 它可以返回带有标签 4 或标签 10 或标签 11 的帖子。但我需要将这三个标签全部放在一篇帖子中。问题就在这里:)
      【解决方案3】:

      这行得通吗?

      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)))
      

      【讨论】:

        【解决方案4】:

        您可以通过存储按字母顺序排序的帖子标签名称的单向哈希来进行时间存储权衡。

        当一个帖子被标记时,执行select t.name from tags t inner join post_tags pt where pt.post_id = [ID_of_tagged_post] order by t.name。连接所有标签名称,使用 MD5 算法创建一个散列,并将值插入到您的帖子旁边的列中(或插入到由外键连接的另一个表中,如果您愿意)。

        当你想搜索特定的标签组合时,只需执行(记得对标签名称进行排序)select from posts p where p.taghash = MD5([concatenated_tag_string])

        【讨论】:

          【解决方案5】:

          这会选择所有具有 任何 个标签(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)) 
          

          或者你可以使用这个:

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

          (两者都将以相同的方式进行优化)。

          这会选择所有具有所有标签(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))
          

          in 子句中的标签列表是动态变化的(在所有情况下)。

          但是,最后一个查询并不是很快,所以你可以使用这样的代替:

           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;
          

          动态变化的部分现在在第二个语句(插入)中。

          【讨论】:

          • 这将选择具有 1、2 或 3 个所需标签的帖子,而不是全部三个。如果表示为 JOIN,它会写得更清楚(并且执行得更快)。
          • 我还为第一种情况添加了连接代码。虽然,一个体面的查询优化器会将其视为与带有 exists 子句的查询相同。
          猜你喜欢
          • 2010-09-12
          • 2013-09-06
          • 2022-01-18
          • 2010-11-26
          • 2017-09-30
          • 1970-01-01
          • 2011-12-29
          相关资源
          最近更新 更多