-1

我有五张桌子。标签,流,streams_tags,帖子,posts_tags。stream_tags 和 posts_tags 只是标签、流和帖子的连接表。

我想从包含通过 streams_tags 表关联到流的所有标签的帖子表中选择所有帖子。例如,如果流具有关联标签“cat, dog”,则应返回所有带有关联标签“cat, dog”的帖子。只要帖子包含“猫,狗”标签,帖子是否有除“猫,狗”以外的更多标签都没关系。

标签: 身份证 | 姓名

流:ID | 姓名

流标签:id | stream_id | tag_id

帖子:ID | 姓名

帖子标签:id | post_id | tag_id

4

1 回答 1

1

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_idpost_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 */ 
       )
于 2013-01-10T01:12:42.663 回答