SQL Fiddle 演示在这里
此查询应返回指定的结果集:
SELECT p.id
, p.name
FROM posts p
WHERE NOT EXISTS
( SELECT 1
FROM streams_tags st
WHERE st.stream_id = 201 /* <-- specified stream_id value */
AND NOT EXISTS
( SELECT 1
FROM posts_tags pt
WHERE pt.tag_id = st.tag_id
AND pt.post_id = p.id
)
)
- 对于每一行
posts
- 查询
streams_tags
表以查找指定 stream_id 的所有标签
- 排除我们
tag_id
从表中发现“缺失”的任何帖子posts_tags
(为此post
)
注意:使用此查询,不存在的流或没有任何标签的流(即streams_tags
给定的没有行stream_id
)将匹配每个帖子。(如果不希望出现这种行为,可以修改查询以添加谓词,以便必须至少有一个匹配的标签。)
为了理解这个查询在做什么,省略最外层的查询是有帮助的,只看一下内部的查询。(以下示例中的 id 值参考了 SQL Fiddle 演示中加载的值。)
这是内部查询。我们删除了对 p.post_id 列的引用,并将其替换为文字。此查询正在检查 posts.id = 67 是否是 stream.id = 201 的“匹配”(就匹配所有标签而言)。
SELECT st.tag_id
FROM streams_tags st
WHERE st.stream_id = 201 /* <- specified stream_id */
AND NOT EXISTS
( SELECT 1
FROM posts_tags pt
WHERE pt.tag_id = st.tag_id
AND pt.post_id = 67 /* <- post we want to check for a match */
)
当我们运行该查询来检查 posts.id = 67 时,我们没有返回任何行。这意味着posts.id 67 匹配指定stream_id 的所有标签。
当我们再次运行它,指定posts.id = 68,我们得到一行。我们返回的行streams_tag.tag_id
是post_tags
.
因此,如果我们为每个 post_id 运行此查询,并检查此查询是否返回行,我们可以知道哪些帖子与指定 stream_id 的“所有”streams_tags.tag_id 匹配。这就是最外面的查询基本上在做的事情......为每个 post_id 运行这个查询。
一种完全不同的方法是获取帖子上匹配标签的“计数”,并将其与流中的标签计数进行比较。
SELECT p.id
, p.name
, sc.st_count AS st_count
FROM ( SELECT stc.stream_id
, COUNT(DISTINCT stc.tag_id) AS st_count
FROM streams_tags stc
WHERE stc.stream_id = 201
GROUP BY stc.stream_id
) sc
CROSS
JOIN posts p
LEFT
JOIN posts_tags pt
ON pt.post_id = p.id
LEFT
JOIN streams_tags st
ON st.tag_id = pt.tag_id
AND st.stream_id = sc.stream_id
GROUP
BY p.id
, p.name
HAVING COUNT(DISTINCT st.tag_id) >= sc.st_count
注意:要获得可以在 HAVING 子句中使用的 streams_tags(对于特定 stream_id)的标签计数,有必要将其包含在查询的 SELECT 列表中。(另一种选择是将该子查询移至 HAVING 子句,然后在查询中重复指定的 stream_id 值两次......
SELECT p.id
, p.name
FROM posts p
LEFT
JOIN posts_tags pt
ON pt.post_id = p.id
LEFT
JOIN streams_tags st
ON st.tag_id = pt.tag_id
AND st.stream_id = 201 /* <- specified stream_id */
GROUP
BY p.id
, p.name
HAVING COUNT(DISTINCT st.tag_id) >=
( SELECT COUNT(DISTINCT stc.tag_id) AS st_count
FROM streams_tags stc
WHERE stc.stream_id = 201 /* <- specified stream_id */
)