2010-06-08 122 views
3

我在MySQL数据库,位置和标签中有两个表格,第三个表格LocationsTagsAssoc将两个表格关联起来,并将它们视为多对多关系。在MySQL中选择多对多关系

表结构如下:

Locations 
--------- 
ID int (Primary Key) 
Name varchar(128) 

LocationsTagsAssoc 
------------------ 
ID int (Primary Key) 
LocationID int (Foreign Key) 
TagID int (Foreign Key) 

Tags 
---- 
ID int (Primary Key) 
Name varchar(128) 

因此,每个位置可以被标记与多个tagwords,并且每个tagword可以被标记到多个位置。

我想要做的是只选择标有的地方所有提供的标签名称。例如:

我希望所有标记为“树”和“秋千”的地点。应选择位置“公园”,但位置“森林”不应该。

任何有识之士将不胜感激。谢谢!

回答

6

有两种方法可以做到这一点。我更喜欢第一种方式,这是自联接每个标签:

SELECT l.* 
FROM Locations l 
JOIN LocationsTagsAssoc a1 ON a1.LocationID = l.ID 
JOIN Tags t1 ON a1.TagID = t1.ID AND t1.Name = ? 
JOIN LocationsTagsAssoc a2 ON a2.LocationID = l.ID 
JOIN Tags t2 ON a2.TagID = t2.ID AND t2.Name = ? 
JOIN LocationsTagsAssoc a3 ON a3.LocationID = l.ID 
JOIN Tags t3 ON a3.TagID = t3.ID AND t3.Name = ?; 

其他方式也可以,但是用GROUP BY在MySQL中往往会引入临时表,性能很慢:

SELECT l.* 
FROM Locations l 
JOIN LocationsTagsAssoc a ON a.LocationID = l.ID 
JOIN Tags t ON a.TagID = t.ID 
WHERE t.Name IN (?, ?, ?) 
GROUP BY l.ID 
HAVING COUNT(*) = 3; 
+0

谢谢比尔。这是有道理的,并且运作良好。这两个查询在5个标记名查询上的速度大致相同 - 我还没有进行过任何测试。优秀的答案 - 谢谢。 – 2010-06-08 23:17:27

+0

我可以从经验中证实,尽管后一种方法看起来更好(对我来说,无论如何),但对于大型表格,第一种方法可能会*效率更高。 – 2014-11-29 04:55:16

+1

@NickF,是的,我发现自连接方法有更好的性能。在我的演示文稿中查看结果[SQL Query Patterns,Optimized](http://www.slideshare.net/billkarwin/sql-query-patterns-optimized) – 2014-11-29 19:13:59

0

您需要的位置不存在给定标签,而该标签未出现在位置LocationsTagsAssoc表中。

您可以像下面那样用IN()指定给定的标签,或者加入到包含它们的另一个表中。

SELECT l.* 
FROM Locations AS l 
WHERE NOT EXISTS (
    SELECT NULL FROM Tags AS t 
    WHERE NOT EXISTS (
     SELECT NULL FROM LocationsTagsAssoc AS lt 
     WHERE lt.LocationId = l.ID 
      AND lt.TagID = t.ID 
    ) 
     AND t.ID IN (1, 2, 3,...) 
)