![](/img/trans.png)
[英]What is the most efficient way to query on columns in belongs_to association in Rails 3?
[英]In a Rails 3 many to many association, what is the most efficient way to query objects based on conditions on their associations?
我有一个多对多模型关系:
class Movie
has_many :movie_genres
has_many :genres, :through => :movie_genres
class Genre
has_many :movie_genres
has_many :movies, :through => :movie_genres
class MovieGenre
belongs_to :movie
belongs_to :genre
我想查询具有特定类型但不与另一类型相关联的所有电影。 示例: 所有属于动作类但非戏剧类的电影 。
我所做的是这样的:
action_movies = Genre.find_by_name('action').movies
drama_movies = Genre.find_by_name('drama').movies
action_not_drama_movies = action_movies - drama_movies
有更有效的方法吗? 应当注意,查询可能变得更加复杂,例如: 所有动作电影而不是戏剧电影或浪漫史和喜剧电影
可能使用范围更好,这是示例和说明(但未经测试),请在影片模型中创建范围,如下所示
电影.rb
scope :action_movies, joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', 'action')
scope :drama_movies, joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', 'drama')
在您的控制器中,您可以按以下方式调用
@action_movies = Movie.action_movies
@drama_movies = Movie.drama_movies
@action_not_drama_movies = @action_movies - @drama_movies
如果要动态,则可以将参数发送给作用域,以下是使用块的作用域。
scope :get_movies, lambda { |genre_request|
joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', genre_request)
}
genre_request =作用域的参数变量
在您的控制器中
@action_movies = Movie.get_movies('action')
@drama_movies = Movie.get_movies('drama')
通过避免通过通过sql语句从动作影片集中删除戏剧影片,而不必为所有动作和戏剧Movie
实例化Movie
实例,确实可以提高效率。
基本组成部分是动态范围,类似于widjajayd提出的内容
class Movie
...
# allows to be called with a string for single genres or an array for multiple genres
# e.g. "action" will result in SQL like `WHERE genres.name = 'action'`
# ["romance", "comedy"] will result in SQL like `WHERE genres.name IN ('romance', 'comedy')`
def self.of_genre(genres_names)
joins(:genres).where(genres: { name: genres_names })
end
...
end
您可以将该范围用作构建块来获取所需的电影
所有动作片而不是戏剧片 :
Movie
.of_genre('action')
.where("movies.id NOT IN (#{Movie.of_genre('drama').to_sql}")
这将导致一个sql子查询。 使用联接会更好一些,但是对于大多数情况来说应该足够了,并且最好使用联接替代方法。
如果您的应用程序在Rails 5应用程序中,您甚至可以输入
Movie
.of_genre('action')
.where.not(id: Movie.of_genre('drama'))
所有属于动作类但非戏剧类的电影或属于爱情和喜剧类的所有电影
因为它是Rails 3应用程序,所以您必须手动输入移动大多数sql的内容,并且不能大量使用范围。 or
方法仅在rails 5中引入。因此这意味着必须键入:
Movie
.joins(:genres)
.where("(genres.name = 'action' AND movies.id NOT IN (#{Movie.of_genre('drama').to_sql}) OR genres.name IN ('romance', 'comedy')" )
再说一次,如果在Rails 5应用程序中,它将更加简单
Movie
.of_genre('action')
.where.not(id: Movie.of_genre('drama'))
.or(Movie.of_genre(['romance', 'comedy']))
看不到一种通过一个查询来完成此操作的方法(无论如何也不是不使用子查询)。 但是我认为这是一个更好的选择:
scope :by_genres, lambda { |genres|
genres = [genres] unless genres.is_a? Array
joins(:genres).where(genres: { name: genre }).uniq
}
scope :except_ids, lambda { |ids|
where("movies.id NOT IN (?)", ids)
}
scope :intersect_ids, lambda { |ids|
where("movies.id IN (?)", ids)
}
## all movies that are action but not drama
action_ids = Movie.by_genres("action").ids
drama_movies = Movie.by_genres("drama").except_ids(action_ids)
## all movies that are both action and dramas
action_ids = Movie.by_genres("action").ids
drama_movies = Movie.by_genres("drama").intersect_ids(action_ids)
## all movies that are either action or drama
action_or_drama_movies = Movie.by_genres(["action", "drama"])
在Rails中,除了原始SQL之外,还可以与之相交。 但是我认为这通常不是一个好主意,因为它仍然需要多个查询,并且可能使代码依赖于所使用的数据库。
我最初的回答很幼稚。 我将其保留在此处,以便其他人不会犯同样的错误:
使用joins
,您可以通过一个查询获取它:
Movie.joins(:genres).where("genres.name = ?", "action").where("genres.name != ?", "drama")
正如评论中指出的那样,这也将获得所有兼具动作和戏剧性的电影。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.