排除具有多对多关系的行

Posted

技术标签:

【中文标题】排除具有多对多关系的行【英文标题】:Exclusion of rows with many-to-many relationships 【发布时间】:2011-10-10 13:52:22 【问题描述】:

我有三个表:posts、tags 和 posts_has_tags(它们促进了帖子和标签之间的多对多关系)。一篇文章可以有任意数量的标签。

“帖子”表包含以下列: 发帖 文字

“标签”表有这些: 身份标签 名字

至于“posts_has_tags”表: post_idposts tags_idtags

我不能做的是提出一个查询来选择所有帖子,除了那些在分配给它们的“名称”列中具有特定值的标签(或标签)的帖子。似乎它应该包含“不存在”,但我无法完全理解它。

提前感谢您的帮助。

编辑:

另外,是否可以同时将结果集限制为某些标签?例如:

要排除的标签:a、b 包含标签:c

带有标签“a”、“f”的帖子不会进入结果集(因为如果包含标签,则没有)。 带有标签“a”、“b”、“c”的帖子也不会进入结果集(因为它的“a”和“b”是被排除的标签)。 带有标签'c'的帖子,'f'确实会进入结果集,因为'c'是包含的标签。

最终编辑 我终于找到了一个似乎有效且性能相当不错的解决方案:http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/

【问题讨论】:

不清楚(已编辑部分),请您解释一下更好的方式。(重定向?)。 【参考方案1】:

您可以使用反连接。

SELECT p.* 
FROM posts p
LEFT JOIN post_has_tags pt ON (pt.post_id = p.id)
LEFT JOIN tags t ON (t.id = pt.tag_id AND t.name IN ('test','test1','test2'))
WHERE t.id IS NULL
GROUP BY p.id

如果你想强制包含其他标签,你可以做另一个连接。

SELECT p.* 
FROM posts p
LEFT JOIN post_has_tags pt ON (pt.post_id = p.id)
LEFT JOIN tags t ON (t.id = pt.tag_id AND t.name IN ('a','b'))
INNER JOIN tags t2 ON (t2.id <> t.id AND t2.id = pt.tag_id AND t2.name IN ('c')) 
WHERE t.id IS NULL
GROUP BY p.id

这将优先考虑排除而不是包含。 如果您想优先考虑包含,请将内部联接替换为:

INNER JOIN tags t2 ON (t2.id = pt.tag_id AND t2.name IN ('c')) 

【讨论】:

你不需要GROUP BY p.id 吗? @ypercube,是的,您需要SELECT DISTINCT 或更快的替代方案GROUP BY p.id 谢谢!您能否也看看我所做的编辑中描述的问题? 看来我选择这个答案太快了。似乎第一个将排除优先于包含的变体不起作用。无论我在“IN...”语句中放置什么标签,结果集始终为空。如果您能帮助我解决这个困难,我将不胜感激,因为我还不能真正完全理解您的代码。 第一个变体都不起作用,没有内部连接。它根本不会删除任何帖子,无论分配给它什么标签。【参考方案2】:
SELECT p.* 
FROM posts AS p
WHERE NOT EXISTS
      ( SELECT *
        FROM posts_has_tags AS pt
          JOIN tags AS t
            ON pt.tags_idtags = t.idtags
        WHERE pt.posts_idposts = p.idposts
          AND t.name = @CertainForbiddenTagName
      )

如果您有很多标签名称要禁止,请改用这个:

          AND t.name IN (List of ForbiddenTagNames)

对于您更新的第二个问题,只需添加类似的EXISTS

   AND EXISTS
       ( SELECT *
         ...
       )

【讨论】:

谢谢!您能否也看看我所做的编辑中描述的问题? 该查询是否可以优先考虑包含而不是排除?【参考方案3】:
select * from posts where idposts in 
(select posts_has_tags.posts_idposts from posts_has_tags   
join tags on tags.idtags = posts_has_tags.tags_idtags  
 where tags.name not in ('value1','value2',...))

【讨论】:

谢谢!您能否也看看我所做的编辑中描述的问题?【参考方案4】:

我终于找到了一个似乎有效且性能相当好的解决方案:http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/

【讨论】:

上面链接中提到的解决方案是针对单表的。你能告诉我它是如何解决你的问题的,因为你有三张桌子。我有类似的问题,您在编辑之前已经在上面提到过

以上是关于排除具有多对多关系的行的主要内容,如果未能解决你的问题,请参考以下文章

存在匹配时排除不匹配行的 SQL 多对多连接

在管理站点中排除具有特定值的多对多

具有额外多对多关系的 JPA 多对多

优化查询以匹配和排除多个多对多条目

选择包含所有提到的值并排除多对多关系中的重复项的名称

两个以上具有多对多关系的表[重复]