如何LEFT JOIN两个表并使用MySQL从子查询中的主查询中排除多行?

如何LEFT JOIN两个表并使用MySQL从子查询中的主查询中排除多行?

问题描述:

I'm new to php and MySQL and started exploring both by programming a custom CMS.

I have a table called news containing lots of news entries which are auto incremented by a primary key news_id:

news (news_id, title, subtitle, ...)

I have another table called news_tags containing tag titles which are auto incremented by a primary key news_tag_id:

news_tags (news_tag_id, news_tag_title)

Because each news entry can be tagged with multiple tag titles and each tag title can describe multiple news entries both tables are many-to-many-relation. So if a news entry gets tagged I store their relations in a third table called news_tags_relations holding rows with news_id and news_tag_id:

news_tags_relations (news_id, news_tag_id)

Now I want to give the user the possibility to choose tags in order to tag a news entry which may or may not have tags already.

So I need to SELECT all tag titles and their title id that are not linked to any news entry at all and also all tag titles that are linked to other news entries but were not already chosen for the news entry the user is working on.

This works perfectly fine if the news entry's news_id is linked to one tag only:

SELECT news_tags.news_tag_title, news_tags_relations.news_id, news_tags.news_tag_id
FROM news_tags
LEFT JOIN news_tags_relations ON news_tags.news_tag_id = news_tags_relations.news_tag_id
WHERE news_tag_title != (

SELECT news_tag_title
FROM news_tags_relations
JOIN news_tags ON news_tags_relations.news_tag_id = news_tags.news_tag_id
WHERE news_tags_relations.news_id = '".$news_id."'
)

But if the picked news_id is linked to two or more tags I get:

1242 - Subquery returns more than 1 row

This makes sense to me because number of tags linked to that news_id is > than 1 and it seems like MySQL can't handle subqueries returning multiple rows.

If the picked news_id is not linked to any tag I get empty result. I tried to fix that with replacing

WHERE news_tag_title != (

with

WHERE news_tag_title IS NULL OR news_tag_title != (

which didn't change the result being 0.

This was closest I could get yet.

How can I modify my Query to make it work for entries that are linked to multiple tags or no tags at all?

I finally solved it. Thanks to @PlantTheIdea the subquery works which removes all rows from the result set of the LEFT JOIN that contain news_tag_titles already assigned to other news_ids. Criteria for "excluding" these rows is the news_tag_title by news_tag_title NOT IN detected by the subquery. News entries that were not assigned to any news_tag_title occur in the result set because their news_id is NULL and inculded by WHERE news_tag_title IS NULL. Finally I had to add GROUP BY news_tag_title in order to avoid duplicate results of news_tag_titles that occur when current '".$news_id."' is not assigned to any news_tag_titles

So the working query is:

SELECT news_tags.news_tag_title, news_tags_relations.news_id, news_tags.news_tag_id FROM news_tags LEFT JOIN news_tags_relations ON news_tags.news_tag_id = news_tags_relations.news_tag_id WHERE news_tag_title IS NULL OR news_tag_title NOT IN ( SELECT news_tag_title FROM news_tags_relations JOIN news_tags ON news_tags_relations.news_tag_id = news_tags.news_tag_id WHERE news_tags_relations.news_id = '".$news_id."' ) GROUP BY news_tag_title

You can rewrite your query without using a subquery as well just add another condition in your ON() clause

SELECT DISTINCT
nt.news_tag_title,
nt.news_tag_id
FROM 
news_tags nt
LEFT JOIN news_tags_relations ntr
ON nt.news_tag_id = ntr.news_tag_id
AND ntr.news_id !='".$news_id."'

Above query will join rows news_tags_relations where news_tag_id from news_tags is equal to news_tag_id from news_tags_relations and the news_id from news_tags_relations is not equal to your current news id the one you are going to assign tags and hence your using left join so all rows will be returned according to ON nt.news_tag_id = ntr.news_tag_id AND ntr.news_id !='".$news_id."' criteria.Note i have not used the WHERE filter it applies on the whole result set ,i have used one more condition in ON() clause so all the tags will return except the ones who were already assigned