按相关多对多关联的计数获取记录顺序
Get records order by count of related Many to Many association
场景:
https://www.funtraker.com 正在列出电影、电视节目和游戏。在每个资源(电影、电视节目等)的显示页面上,我们要列出相关资源。
架构:
class Movie < AR::Base
has_many :resource_genres, as: :resource
has_many :genres, through: :resource_genres
end
class ResourceGenre
belongs_to :resource, polymorphic: true
end
现在我想根据匹配的类型获取相关电影的列表(如果两部电影都具有“喜剧”类型,则两部电影是相关的)。这些相关电影需要按最大匹配类型数排序。
这里是示例电影和预期输出。
#Input
Movie Genres
Movie 1: horror, comedy, action, war
Movie 2: action, thriller, crime, animation
Movie 3: comedy, war, action, thriller
Movie 4: crime, animation, action, war
#Expected output
movie1.related_movies => [ movie3, movie2 ]
movie4.related_movies => [ movie2, remaining-three-movies-in-any-order ]
movie3.related_movies => [ movie1, movie2, movie4]
希望问题有意义。
更新:寻找 SQL 唯一的解决方案。我不需要将结果缓存到任何其他 table.
如果您在 rails 代码中找到解决方案,那么它可能会解决您的问题。
def related_movies
scores_hash = {}
Movie.joins(:resource_genres).where('resource_genres.genre_id' => resource_genres.pluck(&:genre_id)).where.not(id: self.id).distinct.find_each do |movie|
scores_hash[movie] = (movie.resource_genres.pluck(:genre_id) & self.resource_genres.pluck(:genre_id)).count
end
Hash[scores_hash.sort_by { |movie, score| -score }].keys
end
加入后需要按照电影id的组数排序
resource_genres
,看看下面的纯SQL方法:
方法#1(单一查询)
双重加入 resource_genres
table 自身以维护自身类型 ID:
def related_movies
Movie.select("movies.*, COUNT(*) AS group_count").
joins(:resource_genres).
joins("JOIN resource_genres rg ON rg.genre_id = resource_genres.genre_id").
where("rg.resource_type = 'Movie'
AND rg.resource_id = ?
AND movies.id != ?", self.id, self.id).
group('movies.id').
order('group_count DESC')
end
方法 #2(2 个查询)
在单独的查询中从自身 resource_genres
中提取 genre_ids
。
def related_movies
Movie.select("movies.*, COUNT(*) AS group_count").joins(:resource_genres).
where("resource_genres.genre_id IN (?)
AND movies.id != ?", self.resource_genres.pluck(:genre_id), self.id).
group('movies.id').
order('group_count DESC')
end
场景:
https://www.funtraker.com 正在列出电影、电视节目和游戏。在每个资源(电影、电视节目等)的显示页面上,我们要列出相关资源。
架构:
class Movie < AR::Base
has_many :resource_genres, as: :resource
has_many :genres, through: :resource_genres
end
class ResourceGenre
belongs_to :resource, polymorphic: true
end
现在我想根据匹配的类型获取相关电影的列表(如果两部电影都具有“喜剧”类型,则两部电影是相关的)。这些相关电影需要按最大匹配类型数排序。
这里是示例电影和预期输出。
#Input
Movie Genres
Movie 1: horror, comedy, action, war
Movie 2: action, thriller, crime, animation
Movie 3: comedy, war, action, thriller
Movie 4: crime, animation, action, war
#Expected output
movie1.related_movies => [ movie3, movie2 ]
movie4.related_movies => [ movie2, remaining-three-movies-in-any-order ]
movie3.related_movies => [ movie1, movie2, movie4]
希望问题有意义。
更新:寻找 SQL 唯一的解决方案。我不需要将结果缓存到任何其他 table.
如果您在 rails 代码中找到解决方案,那么它可能会解决您的问题。
def related_movies
scores_hash = {}
Movie.joins(:resource_genres).where('resource_genres.genre_id' => resource_genres.pluck(&:genre_id)).where.not(id: self.id).distinct.find_each do |movie|
scores_hash[movie] = (movie.resource_genres.pluck(:genre_id) & self.resource_genres.pluck(:genre_id)).count
end
Hash[scores_hash.sort_by { |movie, score| -score }].keys
end
加入后需要按照电影id的组数排序
resource_genres
,看看下面的纯SQL方法:
方法#1(单一查询)
双重加入 resource_genres
table 自身以维护自身类型 ID:
def related_movies
Movie.select("movies.*, COUNT(*) AS group_count").
joins(:resource_genres).
joins("JOIN resource_genres rg ON rg.genre_id = resource_genres.genre_id").
where("rg.resource_type = 'Movie'
AND rg.resource_id = ?
AND movies.id != ?", self.id, self.id).
group('movies.id').
order('group_count DESC')
end
方法 #2(2 个查询)
在单独的查询中从自身 resource_genres
中提取 genre_ids
。
def related_movies
Movie.select("movies.*, COUNT(*) AS group_count").joins(:resource_genres).
where("resource_genres.genre_id IN (?)
AND movies.id != ?", self.resource_genres.pluck(:genre_id), self.id).
group('movies.id').
order('group_count DESC')
end