5

我在 MySQL 数据库中有两个表,Locations 和 Tags,第三个表 LocationsTagsAssoc 关联这两个表并将它们视为多对多关系。

表结构如下:

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)

所以每个位置可以用多个标签词标记,每个标签词可以标记到多个位置。

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

我想要所有标记有“树”和“秋千”的位置。应选择位置“公园”,但不应选择位置“森林”。

任何见解将不胜感激。谢谢!

4

2 回答 2

9

有两种方法可以做到这一点。我更喜欢第一种方式,即为每个标签自加入:

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;

来自@Erikoenig 的重新评论:

如果你想确保没有额外的标签,你可以这样做:

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

取出 WHERE 子句允许计算其他标签(如果有的话)。所以 COUNT() 可能大于 3。

或者如果计数正好是三个标签,但是这三个标签中的一些不是正确的标签,那么 HAVING 子句中的 SUM() 条件确保您想要的所有三个标签都存在于组中。

于 2010-06-08T22:41:28.607 回答
0

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

您可以使用 IN () 指定给定的标签,如下所示,或者通过加入包含它们的另一个表。

IE

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,...)
)
于 2010-06-08T22:43:44.490 回答