Exclusion of rows with many-to-many relationships

旧城冷巷雨未停 提交于 2019-12-23 16:05:19

问题


I have three tables: posts, tags and posts_has_tags (which facilitate the many-to-many relationship between posts and tags). A post can have any number of tags.

The 'posts' table has the following columns: idposts text

The 'tags' table has these: idtags name

As for the 'posts_has_tags' table: posts_idposts tags_idtags

What I can't do is come up with a query to select all posts except for those which have a tag (or tags) with a certain value in the 'name' column assigned to them. It seems that it should contain a "NOT EXISTS", but I can't really fully wrap my head around it.

Thank you for your help in advance.

EDIT:

Also, is it possible to restrict the result set to certain tags at the same time? For example:

tags for exclusion: a, b tags for inclusion: c

Post with tags 'a', 'f' doesn't make it into result set (because none if it's tags are included). Post with tags 'a', 'b', 'c' doesn't make it into result set either (because it's 'a' and 'b' are excluded tags). Post with tags 'c', 'f' does make it into result set, because 'c' is the included tag.

FINAL EDIT I finally found a solution that seems to be working and reasonably well-performing: http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/


回答1:


You can use an anti-join.

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

If you want to force other tags to be included, you do another join.

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

This will prioritize exclusion over inclusion.
If you want to prioritize inclusion then replace the inner join with:

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



回答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
      )

If you have many tag names to forbid, use this instead:

          AND t.name IN (List of ForbiddenTagNames)

For your updated 2nd question, just add a similar 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:


I finally found a solution that seems to be working and reasonably well-performing: http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/



来源:https://stackoverflow.com/questions/7713806/exclusion-of-rows-with-many-to-many-relationships

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!