0

我正在尝试根据它们在另一个表中是否有多行来从帖子表中加载行。采用下表结构:

帖子

post_id  post_title
-------------------
1        My Post
2        Another Post

post_tags

post_tag_id  post_tag_name
--------------------------
1            My Tag
2            Another Tag

postTags

postTag_id  postTag_tag_id  postTag_post_id
------------------------------------------
1           1               1
2           2               1

不出所料, post 和 post_tags 存储了帖子和标签,而 postTags 则加入了哪些帖子有哪些标签。

我通常会做的加入表格是这样的:

SELECT * FROM (`posts`)
JOIN `postTags` ON (`postTag_post_id` = `post_id`)
JOIN `post_tags` ON (`post_tag_id` = `postTag_tag_id`)

然后我会有关于标签的信息,并且可以在查询的后面有额外的东西来搜索搜索词等的标签名称,然后在我有与搜索词匹配的帖子后进行 GROUP。

我想要做的只是从帖子中选择标签 1 和标签 2 的帖子,我无法为它计算出 SQL。我认为它需要在实际的 JOIN 中完成,而不是有一个 WHERE 子句,因为当我运行上面的连接时,我显然会得到两行,所以我不能有类似的东西

WHERE post_tag_id = 1 AND post_tag_id = 2

因为每一行只有一个 post_tag_id,我无法检查一行中同一列的不同值。

我试图做的是这样的:

SELECT * FROM (`posts`)
JOIN `postTags` ON (postTag_tag_id = 1 AND postTag_tag_id = 2)
JOIN `post_tags` ON (`post_tag_id` = `postTag_tag_id`)

但是当我运行它时返回 0 个结果;我之前在 JOINS 中为类似的事情设置了这样的条件,我确信它已经接近了,但如果这不起作用,我就无法确定该怎么做。

我至少在正确的轨道上吗?希望我没有遗漏一些明显的东西。

谢谢。

4

4 回答 4

2

您正试图要求 postTags 行同时是一件事和另一件事。

您要么需要对 post_tags 和 postTags 进行两次连接,以便同时获得两者。或者您可以说帖子可以有这两者之间的任何标签,并且标签的总数必须等于两个(假设帖子不能多次与同一个标签相关)。

第一种方法:

SELECT *
FROM `posts` as p
WHERE p.`post_id` IN (SELECT pt.`postTag_post_id`
                      FROM `postTags` as pt
                      WHERE pt.`postTag_tag_id` = 1)
AND p.`post_id` IN (SELECT pt.`postTag_post_id`
                    FROM `postTags` as pt
                    WHERE pt.`postTag_tag_id` = 2);

第二种方法:

SELECT *
FROM posts as p
WHERE p.post_id IN (SELECT pt.postTag_post_id
                    FROM (SELECT count(0) as c, pt.postTag_post_id
                          FROM postTags as pt
                          WHERE pt.postTag_tag_id IN (1, 2)
                          GROUP BY pt.postTag_post_id
                          HAVING c = 2) as pt);

我还想补充一点,如果您在第一种方法中使用 IN 或 EXISTS,那么您将不会因为您有多个标签而在同一个帖子行中有多行。这样,您稍后会保存一个 DISTINCT,这会使您的查询变慢。我在第二种方法中使用了 IN,就像我使用的经验法则一样:如果您不需要显示数据,则不需要在 FROM 部分中执行 JOIN。

于 2012-08-26T11:22:14.280 回答
1
SELECT p.*, t1.*, t2.* FROM posts p
INNER JOIN postTags pt1 ON pt1.postTag_post_id = p.id AND pt1.postTag_tag_id = 1
INNER JOIN postTags pt2 ON pt2.postTag_post_id = p.id AND pt2.postTag_tag_id = 2
INNER JOIN post_tags t1 ON t1.post_tag_id = pt1.postTag_tag_id
INNER JOIN post_tags t2 ON t2.post_tag_id = pt2.postTag_tag_id
于 2012-08-26T11:14:41.543 回答
0

假设您已经知道标签 ID (12),您可以执行以下操作:

SELECT post_id, post_title
FROM posts JOIN postTags ON (postTag_post_id = post_id)
WHERE postTag_tag_id IN (1, 2)
GROUP BY post_id, post_title
HAVING COUNT(DISTINCT postTag_tag_id) = 2

注意:如果有备用键,则不需要 DISTINCT postTags {postTag_tag_id, postTag_post_id},因为它应该是。

注意:如果您没有标签 ID(只有标签名称),则需要另一个 JOIN(朝向post_tags表格)。


顺便说一句,您应该认真考虑放弃连接表 ( ) 中的代理 PKpostTags.postTag_id并只使用自然 PK {postTag_tag_id, postTag_post_id}InnoDB 表是聚集的,聚集表中的二级索引比基于堆的表更胖更慢。此外,某些查询可以受益于将由相同标签标记的帖子物理上靠得很近(或将同一帖子的标签靠得很近,如果您反转 PK)。

于 2012-08-26T12:15:46.807 回答
0

如果没有实际构建与您相同的数据库,这很难验证,但它应该可以工作。

首先让我说,这种类型的查询在支持分析查询的数据库(Oracle、MS SQL Server)中更容易且性能更高。因此,在 MySQL 中,您必须采用旧的、糟糕的、聚合的方式。

我还想说,拥有一个将标签名称存储在 post_tags 中的表,然后将 post 标签映射到 postTags 中的帖子是令人困惑的。如果是我,我会把映射表的名字改成 post_tags_map 或者 post_tags_to_post_map。因此,您将拥有带有 post_id 的帖子、带有 post_tags_id 的 post_tags 和带有 post_tags_map_id 的 post_tags_map。并且这些 id 列将在每个表中命名相同。在其他表中具有不同名称的相同列也令人困惑。

无论如何,让我们解决您的问题。首先,您需要一个结果集,每行 1 个帖子 ID,并且只有具有标签 1 和 2 的帖子。

select postTag_post_id, count(1) cnt from (
  select postTag_post_id from postTags where postTag_tag_id in (1, 2)
) group by postTag_post_id;`

这应该会给你这样的数据:

postTag_post_id | cnt
              1 |   2

然后,您可以将该结果集加入您的帖子表。

select * from posts p,
(
  select postTag_post_id, count(1) cnt from (
    select postTag_post_id from postTags where postTag_tag_id in (1, 2)
  ) group by postTag_post_id;
) t
where p.post_id = t.postTag_post_id
and t.cnt >= 2;

如果您需要对 post_tags 表进行另一次联接以从 post_tag_name 获取 postTag_tag_id,那么您最内部的查询将如下更改:

select postTag_post_id
from postTags a,
post_tags b
where a.postTag_tag_id = b.post_tag_id
and b.post_tag_name in ('tag 1', 'tag 2');

这应该够了吧。

于 2012-08-26T11:27:02.907 回答