【问题标题】:Get records order by count of related Many to Many association按相关多对多关联的计数获取记录顺序
【发布时间】:2015-01-20 15:36:18
【问题描述】:

场景:

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 的解决方案。我不需要将结果缓存在任何其他表中。

【问题讨论】:

  • 您将使用多少条记录?对几百部电影使用 ActiveRecord 可以正常工作,但此查询无法扩展。
  • @eabraham 实际上我只需要前 10 条相关记录 :)
  • 前 10 名是指前 10 名吗?
  • @eabraham 是的,前 10 名表示前 10 个条目。参赛作品将按最大匹配数排序,因此前十名也将是前 10 名:)

标签: ruby-on-rails postgresql join activerecord related-content


【解决方案1】:

加入后需要按电影ID的组数排序 resource_genres,看看下面的纯SQL方法:

方法#1(单一查询)

双重加入resource_genres 表以维护自身类型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

【讨论】:

  • 我现在选择第二个答案。将在几天内进行基准测试并在此处发布哪个查询更有效。感谢您的回答并祝贺赏金:) BTW 我错过了:“movies.*, COUNT(*) AS group_count”
  • 我很高兴它有帮助?。我通常更喜欢方法 1,尝试将基准保持在每张表 1000 多条记录上,并告诉我们。
【解决方案2】:

如果您在 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

【讨论】:

  • 更好的连接方法是Movie.joins(:genres).where('genres.id IN(?)', genres.pluck(:id)),我正在寻找仅限 SQL 的解决方案 :)
  • 如果您对genre_name 有索引,那么我的解决方案和您的解决方案的性能将相同:)
  • 你可以处理X.related_movies不应该返回X
  • 在幕后Movie.joins(:genres) 是3 个表的连接,每次调用movie.genres 也是如此。因此,您可以考虑使用 resource_genres 表,同时添加一些急切加载。
  • @mohameddiaa27,感谢您指出这一点。我已经进行了更改,非常感谢您的帮助:)
猜你喜欢
  • 2011-04-21
  • 1970-01-01
  • 2014-09-24
  • 2017-04-22
  • 1970-01-01
  • 2022-01-12
  • 2014-11-14
  • 2015-06-01
  • 1970-01-01
相关资源
最近更新 更多