【问题标题】:Select all projects that have matching tags选择所有具有匹配标签的项目
【发布时间】:2011-04-04 14:33:45
【问题描述】:

我正在努力寻找最有效的方法来解决这个问题,但我必须先告诉你,我把它弄得一团糟。环顾四周,没有发现任何相关的东西,所以就这样吧。

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

以这个表为例:
(下面重新创建表的sql代码)

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

选择项目 1 应返回所有项目。
选择项目 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 个项目_to_tags 关系的所有查询的平均结果。所有查询都更改为询问 project_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 EXPLAIN 以及时间

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

用于复制/粘贴和乱七八糟的 SQL 代码。

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

【问题讨论】:

  • @Emtucifor 非常感谢!这个问题很有趣。
  • @OMG Ponies 你的 JOINS 使 MySQL 泵!谢谢!
  • @jdelard 很棒的 SQL!我发现它很容易阅读,并且可以很快看出它非常有效。然而,OMG Ponies 仍然以几微秒的优势击败了你。非常感谢!
  • 您是否包含了我更新的查询?我很想知道它是如何堆积起来的。您解决了 OMG Ponies 的哪些问题?
  • @Emtucifor 测试了两者! ;) 您可以在我的问题页面中查看结果,该页面已更新以反映测试、explain 的结果以及每个查询 100 次点击的平均结果。

标签: sql mysql tags left-join join


【解决方案1】:

在以下任何一种情况下,如果您不知道PROJECT.num/PROJECT_TO_TAGS.project_id,则必须加入PROJECTS 表以获取id 值以找出它关联的标签.

使用 IN

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)

使用 EXISTS

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)

使用 JOINS(这是最有效的!)

DISTINCT 是必要的,因为 JOIN 可能会在结果集中出现重复数据...

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

【讨论】:

  • 非常感谢!你的 SQL 看起来很漂亮!尽管如此,所有 3 种可能性都需要全表扫描。我几乎试图避免这种情况(我在看起来像意大利面条的 SQL 代码中这样做)
【解决方案2】:

像这样...?

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
   )

这是 2 次搜索和一次扫描。

更新

从 jdelard 的书中取出一页,一个微小的修改将我的查询切换为优于他的查询(当然我在 SQL Server 上执行此操作,这意味着我取出了他的 GROUP BY 并放入了 DISTINCT,因此在 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 或聚合,并使用半连接而不是完全连接,因此不必连接每一行。否则,它们在语义上基本相同。

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

【讨论】:

  • 非常好,谢谢!全表扫描让我有点失望,项目表可以(并且将)增长到相当大的大小。我特别喜欢不展示原始项目,即使没有被问到,也是意图。谢谢!
【解决方案3】:

怎么样...(以项目 1 为例)

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

也许在 projects_to_tags 中为 tag_id 添加一个单独的索引,这样你就可以单独使用它,而不是复合。不再键入 ALL。 (表扫描) 将 1 都替换为 4 也可以获得所需的结果。

【讨论】:

  • 完美。简单、优雅且性能非常明智的解决方案。爱它。谢谢!
  • 请解释一下 MySql 如何对选择列表中没有聚合的列进行分组?
  • 另外,我不知道有人还在编写非 ANSI 联接……在 2010 年!?!?! :)
  • @Emtucifor:这是 ANSI-89 连接语法,但我同意这些天它应该是 ANSI-92 语法。此外,GROUP BY 中的“隐藏”列记录了行为:dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html
  • @OMG Ponies - 有趣。这就像一个默认的 First() 或 Any() 聚合。
猜你喜欢
  • 2015-10-06
  • 1970-01-01
  • 2022-09-30
  • 2016-11-22
  • 1970-01-01
  • 1970-01-01
  • 2011-04-16
  • 1970-01-01
相关资源
最近更新 更多