我有五张桌子。标签,流,streams_tags,帖子,posts_tags。 streams_tags和posts_tags只是标签,流和帖子的连接表。
我想要从posts表中选择包含通过streams_tags表与流关联的所有标记的所有帖子。例如,如果流具有关联的标签“cat,dog”,则应返回具有关联标签“cat,dog”的所有帖子。只要它包含“猫,狗”标签,该帖子除了“猫,狗”之外还有更多标签并不重要。
标签: id |名称
流: id |名称
streams_tags: id | stream_id | TAG_ID
帖子:id |名称
posts_tags:id | post_id | TAG_ID
答案 0 :(得分:1)
此查询应返回指定的结果集:
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是否为streams.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的“all”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
注意:要获取来自streams_tags(对于特定stream_id)的标记计数在HAVING子句中可用,必须将其包含在查询的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 */
)