正确连接多个多对多表 - MySQL 查询
Correctly join multiple many-to-many tables - MySQL query
一个看似普通的SQL查询真的让我一头雾水。
情况是这样的。
我有 3 个通用表(此处为简化版本):
Movie
id | title
-----------------------
1 | Evil Dead
-----------------------
2 | Bohemian Rhapsody
....
Genre
id | title
-----------------------
1 | Horror
-----------------------
2 | Comedy
....
Rating
id | title
-----------------------
1 | PG-13
-----------------------
2 | R
....
还有 2 个多对多表来连接它们:
Movie_Genre
movie_id | genre_id
Movie_Rating
movie_id | rating_id
最初的挑战是编写一个查询,使我能够获取属于多种类型的电影(例如恐怖喜剧或科幻动作片)。
谢天谢地,我能够在这里找到这个解决方案
MySQL: Select records where joined table matches ALL values
但是,获取属于多个多对多表的记录的正确选项是什么?例如。 R级恐怖喜剧。没有子查询(或只有一个子查询)有没有办法做到这一点?
一种方法使用相关子查询:
select m.*
from movies m
where (select count(*)
from movie_genre mg
where mg.movie_id = m.id
) > 1 and
(select count(*)
from movie_rating mr
where mr.movie_id = m.id
) > 1 ;
对于 movie_genre(movie_id)
和 movie_rating(movie_id)
上的索引,这可能具有相当合理的性能。
以上可能是最有效的方法。但是,如果您想避免子查询,一种方法是:
select mg.movie_id
from movie_genres mg join
movie_ratings mr
on mg.movie_id = mr.movie_id
group by mg.movie_id
having count(distinct mg.genre_id) > 0 and
count(distinct mr.genre_id) > 0;
比上面更有效的是在 join
:
之前聚合
select mg.movie_id
from (select movie_id
from mg_genres
group by movie_id
having count(*) >= 2
) mg join
(select movie_id
from mg_ratings
group by movie_id
having count(*) >= 2
) mr
on mg.movie_id = mr.movie_id;
尽管您声明要避免子查询,但具有讽刺意味的是,没有子查询的版本可能是这三个选项中性能最差的。
E.g. rated R horror comedies
您可以 join
所有表格,按电影聚合并使用 HAVING
子句过滤:
select m.id, m.title
from movies m
inner join movie_genre mg on mg.movid_id = m.id
inner join genre g on g.id = mg.genre_id
inner join movie_rating mr on mr.movie_id = m.id
inner join rating r on r.id = mr.rating_id
group by m.id, m.title
having
max(r.title = 'R') = 1
and max(g.title = 'Horror') = 1
and max(g.title = 'Comedy') = 1
您还可以使用几个 exists
条件以及相关的子查询:
select m.*
from movie m
where
exists (
select 1
from movie_genre mg
inner join genre g on g.id = mg.genre_id
where mg.movie_id = m.id and g.title = 'R')
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Horror'
)
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Comedy'
)
一个看似普通的SQL查询真的让我一头雾水。 情况是这样的。 我有 3 个通用表(此处为简化版本):
Movie
id | title
-----------------------
1 | Evil Dead
-----------------------
2 | Bohemian Rhapsody
....
Genre
id | title
-----------------------
1 | Horror
-----------------------
2 | Comedy
....
Rating
id | title
-----------------------
1 | PG-13
-----------------------
2 | R
....
还有 2 个多对多表来连接它们:
Movie_Genre
movie_id | genre_id
Movie_Rating
movie_id | rating_id
最初的挑战是编写一个查询,使我能够获取属于多种类型的电影(例如恐怖喜剧或科幻动作片)。
谢天谢地,我能够在这里找到这个解决方案 MySQL: Select records where joined table matches ALL values
但是,获取属于多个多对多表的记录的正确选项是什么?例如。 R级恐怖喜剧。没有子查询(或只有一个子查询)有没有办法做到这一点?
一种方法使用相关子查询:
select m.*
from movies m
where (select count(*)
from movie_genre mg
where mg.movie_id = m.id
) > 1 and
(select count(*)
from movie_rating mr
where mr.movie_id = m.id
) > 1 ;
对于 movie_genre(movie_id)
和 movie_rating(movie_id)
上的索引,这可能具有相当合理的性能。
以上可能是最有效的方法。但是,如果您想避免子查询,一种方法是:
select mg.movie_id
from movie_genres mg join
movie_ratings mr
on mg.movie_id = mr.movie_id
group by mg.movie_id
having count(distinct mg.genre_id) > 0 and
count(distinct mr.genre_id) > 0;
比上面更有效的是在 join
:
select mg.movie_id
from (select movie_id
from mg_genres
group by movie_id
having count(*) >= 2
) mg join
(select movie_id
from mg_ratings
group by movie_id
having count(*) >= 2
) mr
on mg.movie_id = mr.movie_id;
尽管您声明要避免子查询,但具有讽刺意味的是,没有子查询的版本可能是这三个选项中性能最差的。
E.g. rated R horror comedies
您可以 join
所有表格,按电影聚合并使用 HAVING
子句过滤:
select m.id, m.title
from movies m
inner join movie_genre mg on mg.movid_id = m.id
inner join genre g on g.id = mg.genre_id
inner join movie_rating mr on mr.movie_id = m.id
inner join rating r on r.id = mr.rating_id
group by m.id, m.title
having
max(r.title = 'R') = 1
and max(g.title = 'Horror') = 1
and max(g.title = 'Comedy') = 1
您还可以使用几个 exists
条件以及相关的子查询:
select m.*
from movie m
where
exists (
select 1
from movie_genre mg
inner join genre g on g.id = mg.genre_id
where mg.movie_id = m.id and g.title = 'R')
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Horror'
)
and exists (
select 1
from movie_rating mr
inner join rating r on r.id = mr.rating_id
where mr.movie_id = m.id and r.title = 'Comedy'
)