代码之家  ›  专栏  ›  技术社区  ›  Frankie

选择具有匹配标记的所有项目

  •  5
  • Frankie  · 技术社区  · 15 年前

    我正试图找到最有效的方法来处理这件事,但我必须告诉你,我把事情搞得一团糟。环顾四周,没有发现任何相关的东西,就这样。

    如何选择与所需项目具有相似标记的所有项目?


    (下面是重新创建表的sql代码)

    project 1 -> tagA | tagB | tagC
    project 2 -> tagA | tagB
    project 3 -> tagA
    project 4 -> tagC
    


    选择项目4只应返回项目1

    到目前为止,我的查询非常依赖于左联接,可以肯定的是,有更好的方法可以做到这一点:

    SELECT all_tags.project_id, all_tags.tag_id, final.title, tag.tag
    FROM projects AS p
    LEFT JOIN projects_to_tags AS pt ON p.num = pt.project_id
    LEFT JOIN projects_to_tags AS all_tags ON pt.tag_id = all_tags.tag_id
    LEFT JOIN projects AS final ON all_tags.project_id = final.num
    LEFT JOIN tags AS tag ON all_tags.tag_id = tag.tag_id
    WHERE p.num = 4
    GROUP BY final.num
    

    谢谢大家的意见。我想我会和你们分享一个100k项目数据库的所有查询的平均结果,100k标签数据库有100k项目\u到\u标签的关系。所有查询都已更改为请求项目1。

    0.0160 sec - OMG Ponies - Using JOINS  
    0.0208 sec - jdelard  
    0.2581 sec - OMG Ponies - Using EXISTS  
    0.2777 sec - OMG Ponies - Using IN  
    0.5295 sec - Emtucifor - updated query  
    0.5088 sec - Emtucifor - first query  
    

    非常感谢你们。我会相应地更新我所有的查询。

    下面是所有的查询和相应的MySQL解释,以及时间

    ===============================================================================================================================================
    Emtucifor - updated query
    ===============================================================================================================================================
    Showing rows 0 - 1 (2 total, Query took 0.5295 sec)
    SELECT * 
    FROM projects AS L
    WHERE L.num !=1-- instead of <> PT2.project_id inside
    
    AND EXISTS (
    
    SELECT 1 
    FROM projects_to_tags PT
    INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
    WHERE L.num = PT.project_id
    AND PT2.project_id =1
    )
    LIMIT 0 , 30
    
    id  select_type table   type    possible_keys   key key_len ref rows    Extra
    1   PRIMARY L   ALL PRIMARY NULL    NULL    NULL    100000  Using where
    2   DEPENDENT SUBQUERY  PT2 ref project_id  project_id  4   const   1   Using index
    2   DEPENDENT SUBQUERY  PT  ref project_id  project_id  8   test.L.num,test.PT2.tag_id  12000   Using index
    
    
    
    
    ===============================================================================================================================================
    Emtucifor - first query
    ===============================================================================================================================================
    Showing rows 0 - 1 (2 total, Query took 0.5088 sec)
    SELECT * 
    FROM projects AS L
    WHERE 
    EXISTS (
    
    SELECT 1 
    FROM projects_to_tags PT
    INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
    WHERE L.num = PT.project_id
    AND PT2.project_id =1
    AND PT2.project_id <> L.num
    )
    LIMIT 0 , 30
    
    id  select_type table   type    possible_keys   key key_len ref rows    Extra
    1   PRIMARY L   ALL NULL    NULL    NULL    NULL    100000  Using where
    2   DEPENDENT SUBQUERY  PT2 ref project_id  project_id  4   const   1   Using index
    2   DEPENDENT SUBQUERY  PT  ref project_id  project_id  8   test.L.num,test.PT2.tag_id  12000   Using where; Using index
    
    
    
    
    ===============================================================================================================================================
    jdelard
    ===============================================================================================================================================
    Showing rows 0 - 1 (2 total, Query took 0.0208 sec)
    SELECT p.num, p.title
    FROM projects_to_tags pt1, projects_to_tags pt2, projects p
    WHERE pt1.project_id =1
    AND pt2.project_id !=1
    AND pt1.tag_id = pt2.tag_id
    AND p.num = pt2.project_id
    GROUP BY pt2.project_id
    LIMIT 0 , 30
    
    id  select_type table   type    possible_keys   key key_len ref rows    Extra
    1   SIMPLE  pt1 ref project_id  project_id  4   const   1   Using index; Using temporary; Using filesort
    1   SIMPLE  pt2 index   project_id  project_id  8   NULL    75001   Using where; Using index
    1   SIMPLE  p   eq_ref  PRIMARY PRIMARY 4   test.pt2.project_id 1    
    
    
    
    
    ===============================================================================================================================================
    OMG Ponies - Using IN
    ===============================================================================================================================================
    Showing rows 0 - 2 (3 total, Query took 0.2777 sec)
    SELECT p . * 
    FROM projects p
    JOIN projects_to_tags pt ON pt.project_id = p.num
    WHERE pt.tag_id
    IN (
    
    SELECT x.tag_id
    FROM projects_to_tags x
    WHERE x.project_id =1
    )
    LIMIT 0 , 30
    
    id  select_type table   type    possible_keys   key key_len ref rows    Extra
    1   PRIMARY pt  index   project_id  project_id  8   NULL    100001  Using where; Using index
    1   PRIMARY p   eq_ref  PRIMARY PRIMARY 4   test.pt.project_id  1    
    2   DEPENDENT SUBQUERY  x   ref project_id  project_id  8   const,func  12000   Using where; Using index
    
    
    
    
    ===============================================================================================================================================
    OMG Ponies - Using EXISTS
    ===============================================================================================================================================
    Showing rows 0 - 2 (3 total, Query took 0.2581 sec)
    SELECT p . * 
    FROM projects p
    JOIN projects_to_tags pt ON pt.project_id = p.num
    WHERE EXISTS (
    
    SELECT NULL 
    FROM projects_to_tags x
    WHERE x.project_id = 1
    AND x.tag_id = pt.tag_id
    )
    LIMIT 0 , 30
    
    
    
    
    ===============================================================================================================================================
    OMG Ponies - Using JOINS
    ===============================================================================================================================================
    Showing rows 0 - 2 (3 total, Query took 0.0160 sec)
    SELECT DISTINCT p . * 
    FROM projects p
    JOIN projects_to_tags pt ON pt.project_id = p.num
    JOIN projects_to_tags x ON x.tag_id = pt.tag_id
    AND x.project_id = 1
    LIMIT 0 , 30
    
    id  select_type table   type    possible_keys   key key_len ref rows    Extra
    1   SIMPLE  x   ref project_id  project_id  4   const   1   Using index; Using temporary
    1   SIMPLE  pt  index   project_id  project_id  8   NULL    75001   Using where; Using index
    1   SIMPLE  p   eq_ref  PRIMARY PRIMARY 4   test.pt.project_id  1   
    

    CREATE TABLE IF NOT EXISTS `projects` (
      `num` int(2) NOT NULL auto_increment,
      `title` varchar(30) NOT NULL,
      PRIMARY KEY  (`num`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
    
    
    INSERT INTO `projects` (`num`, `title`) VALUES(1, 'project 1'),(2, 'project 2'),(3, 'project 3'),(4, 'project 4');
    
    
    CREATE TABLE IF NOT EXISTS `projects_to_tags` (
      `project_id` int(2) NOT NULL,
      `tag_id` int(2) NOT NULL,
      KEY `project_id` (`project_id`,`tag_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
    
    
    INSERT INTO `projects_to_tags` (`project_id`, `tag_id`) VALUES(1, 1),(1, 2),(1, 3),(2, 1),(2, 2),(3, 1),(4, 3);
    
    
    CREATE TABLE IF NOT EXISTS `tags` (
      `tag_id` int(2) NOT NULL auto_increment,
      `tag` varchar(30) NOT NULL,
      PRIMARY KEY  (`tag_id`),
      UNIQUE KEY `tag` (`tag`)
    ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
    
    
    INSERT INTO `tags` (`tag_id`, `tag`) VALUES(1, 'tag a'),(2, 'tag b'),(3, 'tag c');
    
    3 回复  |  直到 15 年前
        1
  •  7
  •   Frankie    15 年前

    在下列任何情况下,如果你不知道 PROJECT.num / PROJECT_TO_TAGS.project_id ,你必须加入 PROJECTS

    SELECT p.*
      FROM PROJECTS p
      JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
     WHERE pt.tag_id IN (SELECT x.tag_id
                           FROM PROJECTS_TO_TAGS x
                          WHERE x.project_id = 4)
    

    使用存在

    SELECT p.*
      FROM PROJECTS p
      JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
     WHERE EXISTS (SELECT NULL
                     FROM PROJECTS_TO_TAGS x
                    WHERE x.project_id = 4
                      AND x.tag_id = pt.tag_id)
    

    使用连接(这是最有效的连接!)

    DISTINCT 是必需的,因为在结果集中出现重复数据。。。

    SELECT DISTINCT p.*
      FROM PROJECTS p
      JOIN PROJECTS_TO_TAGS pt ON pt.project_id = p.num
      JOIN PROJECTS_TO_TAGS x ON x.tag_id = pt.tag_id
                             AND x.project_id = 4
    
        2
  •  4
  •   jdelard    15 年前

    SELECT p.num, p.title
    FROM projects_to_tags pt1, projects_to_tags pt2, projects p
    where pt1.project_id = 1 and 
          pt2.project_id != 1 and 
          pt1.tag_id = pt2.tag_id and 
          p.num = pt2.project_id 
    group by pt2.project_id
    

    或者在项目中为标记添加一个单独的索引,这样您就可以单独使用它,而不是复合索引。没有更多的类型全部。(表格扫描) 1 具有 4 同时给出期望的结果。

        3
  •  2
  •   momo    15 年前

    像这样的?

    SELECT *
    FROM projects AS L
    WHERE
       EXISTS (
          SELECT 1
          FROM
             projects_to_tags PT
             INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
          WHERE
             L.num = PT.project_id
             AND PT2.project_id = 4
             AND PT2.project_id <> L.num
       )
    

    那是两次搜索和一次扫描。

    以jdelard的书为例,一个小小的修改就将我的查询转换为优于他的查询(当然,我是在sqlserver上这样做的,这意味着我将他的GROUP BY去掉,并在MySQL上放入一个独特的YMMV):

    SELECT *
    FROM projects AS L
    WHERE
       L.num != 4 -- instead of <> PT2.project_id inside
       AND EXISTS (
          SELECT 1
          FROM
             projects_to_tags PT
             INNER JOIN projects_to_tags PT2 ON PT.tag_id = PT2.tag_id
          WHERE
             L.num = PT.project_id
             AND PT2.project_id = 4
       )
    

    对他的查询的改进来自于不执行DISTINCT或aggregate,并且使用半连接而不是完全连接,这样就不必连接每一行。否则,它们在语义上基本相同。

    我必须记住jdelard的技巧,因为它是一个非常有用的工具。由于某些原因,查询引擎不够聪明,无法计算给定的{a=4,a!=b}然后{b!= 4}.