mysql在另一个连接表的结果上查询连接表

时间:2013-01-09 21:30:52

标签: mysql

我有五张桌子。标签,流,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

1 个答案:

答案 0 :(得分:1)

SQL Fiddle demonstration here


此查询应返回指定的结果集:

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 */ 
       )