Reputation: 1999
I have three tables with a many to many relationships; media
, relations
, and tags
Media Table Relations Table Tags Table
| media-id | media-name | | rel-id | media-id | tag-id | | tag-id | tag-name|
------------------------- ------------------------------ --------------------
| 1 | "Media 1" | | 1 | 1 | 1 | | 1 | "tag 1" |
| 2 | "Media 2" | | 2 | 2 | 2 | | 2 | "tag 2" |
| 3 | 1 | 3 | | 3 | "tag 3" |
| 4 | 2 | 3 | | 4 | "tag 4" |
| 5 | 1 | 4 |
I need a MySQL query that when a tag or tags are given, it will return all other possible tags combinations. Here are several examples.
Given: tag 1
Returned: tag 1
, tag 3
and tag 4
Because: tag 1
is tagging Media 1
so it should return all of its tags (tag 1
, tag 3
, and tag 4
)
Given: tag 3
Returned: tag 1
, tag 2
, tag 3
, and tag 4
are returned
Because: tag 3
is tagging both Media 1
and Media 2
so all of their tags are returned (tag 1
, tag 2
, tag 3
, tag 4
)
Given: tag 2
and tag 3
Returned: tag 2
and tag 3
Because: tag 3
tags both Media 1
and Media 2
, but only Media 2
has tag 2
so just it's tags are returned and Media 1
's tags are excluded
I've searched for something like this for a while, but I haven't found anything related with arcuate answers. The closest I was able to get to this was this query here:
SELECT `tags`.`tag-name`
FROM `tags`
WHERE `tags`.`tag-id` IN (
SELECT `relations`.`tag-id`
FROM `relations`
WHERE `relations`.`media-id` IN (
SELECT `relations`.`media-id`
FROM `relations`
WHERE `relations`.`tag-id` IN (
SELECT `tags`.`id`
FROM `tags`
WHERE `tags`.`tag-name` IN ('tag 1')
)
)
)
This works great, maybe not the most effective, but I can understand it.
The only issue with this is if I were to run it on Example 3, it would return all the tags, instead of excluding tags from Media 1
.
Is there a way that I could change my MySQL query to do this?
Upvotes: 0
Views: 152
Reputation: 164194
The general solution to your problem involves multiple joins between the tables and aggregation, like this:
select t2.tag_name
from tags t
inner join relations r on r.tag_id = t.tag_id
inner join media m on m.media_id = r.media_id
inner join relations r2 on r2.media_id = m.media_id
inner join tags t2 on t2.tag_id = r2.tag_id
where t.tag_name in ('tag 2', 'tag 3')
group by t2.tag_name
having count(distinct t.tag_id) = 2
This:
('tag 2', 'tag 3')
is the list of the tags that you want the query to fetch the results.
And the number on the right side of the condition in the HAVING clause:
count(distinct t.tag_id) = 2
is the number of the items in that list.
See the demo.
In the case of only 1 queried tag the code can be simplified to:
select t2.tag_name
from tags t
inner join relations r on r.tag_id = t.tag_id
inner join media m on m.media_id = r.media_id
inner join relations r2 on r2.media_id = m.media_id
inner join tags t2 on t2.tag_id = r2.tag_id
where t.tag_name = 'tag 1'
group by t2.tag_name
Upvotes: 1
Reputation: 1270993
If you want the tags that are not used for "media_id = 1", then not exists
comes to mind:
select t.*
from tags t
where not exists (select 1
from relations r join
media m
on r.media_id = m.media_id
where m.media_name = 'Media 1' and
r.tag_id = t.tag_id
);
Upvotes: 0