1

我的评分系统非常简单,无论是用户喜欢还是他不喜欢该文章。Rails 3:按等级对ActiveRelation进行排序从另一个表中计数

基本上,我有这样的事情,它完美的作品:

class Article 
    has_many :ratings 
    def self.by_ratings 
    all.sort_by{|article| (article.ratings.where(like: true).size).to_f/(article.ratings.size) } 
    end 
end 

正如您可以猜到,我们的应用程序成为数据库,流量的增加巨大,这部分代码成为瓶颈。

我正在尝试在纯sql中重写它以提高性能。我也试图把它作为一个ActiveRelation与其他条件链接起来。也试着写SQL,但没有成功。有任何想法吗 ?

谢谢!

使用:

  • 的Rails 3.0.10
  • ActiveRecord的
  • SQLite的3(我们可以切换到PostgreSQL)

回答

1

我一直没能测试这个SQL在你的模型,但你可以尝试:

select articles_ratings.*, likes/total rating from (select articles.*, SUM(if(articles.like, 1, 0)) likes, count(*) total from articles JOIN ratings on article_id = articles.id GROUP BY articles.id) articles_ratings ORDER BY rating desc

这应该有希望给你一个文章的列表,按他们的评级(从最高到最低)排序。如果可行的话,我可以尝试跟进一些轨道。

编辑正如@socjopa所建议的那样,如果您没有试图立即将其转换为生产,我的下一个建议是将此查询移动到视图。像对待任何其他ActiveRecord一样对待它,并将其与您的文章相关联。

使用适当的索引,视图应该相当快,但可能并非真的有必要在运行时每次计算评估值。如果性能不在您需要的位置,您可能还需要考虑在“文章”表中存储评级列。只要文章评分被修改或创建,您就可以简单地更新此评分。

也就是说,这种性能应该是当前迭代的昼夜。

+0

是的,我想出了一个像这样的SQL。考虑到切换到mongodb或其他nosql,因为我们遇到了很多麻烦,让事情干净,快速和灵活。 – Hartator

+0

我相信这个查询应该很快(取决于你的数据集的大小),并且肯定会推荐_cache列方法(下面列出)作为你最有效的解决方案。你可以通过将这个视图移到一个视图(下面还要介绍)来清理SQL,为你提供一些更清晰的rails代码。祝你好运 –

1

从你的by_ratings方法,我明白你想按照最喜欢的评论/评级排序的文章。

我们可以重写方法为这样一个范围:

scope :by_ratings, select('articles.*, ((select count(id) from ratings where article_id = articles.id) - count(article_id)) as article_diff_count') 
.joins(:ratings).group('article_id').where('like = ?',true).order('article_diff_count asc') 

我选择了difference instead of ratio比较总收视率之间并喜欢上收视率,因为这应该是对SQL引擎更轻。希望这可以帮助。

1

我在做一些假设你实现,就像我不认为你有你的评级模型value场,可以是1的“喜欢”,-1表示“不喜欢”,等

开始搭配:

class Article 
    has_one :rating_value, :class_name => 'RatingValue' 
end 

class RatingValue < ActiveRecord::Base 
    belongs_to :article 
    set_table_name "rating_values" 
end 

所以,在迁移您生成视图(例如Postgres的):

execute %q{CREATE OR REPLACE VIEW rating_values AS 
SELECT ratings.article_id, sum(ratings.value) AS value 
    FROM ratings 
    GROUP BY ratings.article_id;} 

给你有一个这样的数据库视图,可以使你需要一个排序范围:

scope :ascend_by_rating, {:joins => %Q{ 
LEFT JOIN "rating_values" 
ON rating_values.article_id = article.id }, 
              :order => "rating_values.value ASC"} 

比红宝石的排序尝试多比较有效

+0

我同意将这个查询移动到一个视图,因为它使事情变得更清洁(或者可能只是上面描述的文章表中的一列)。我相信OP需要相对于评分总数的评分,在这种情况下,他只会得到一个正整数或负整数,但它不会给出整体“分数”的概念 –

2

你需要的是一个缓存列。

class AddRatingCacheToArticles < ActiveRecord::Migration 

    def self.up 
    add_column :articles, :rating_cache, :decimal, :default => 0.0 
    end 

    def self.down 
    remove_column :articles, :rating_cache 
    end 

end 

2)Article定义的更新方法,该方法将做计数::

class Article < ActiveRecord::Base 
    has_many :ratings 

    def update_rating_cache 
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f 
    update_attribute(:rating_cache, current_rating) 
    end 
end 

3)设置回调

1)为了屁股缓存列创建迁移在Rating触发update_rating_cache方法时,他们保存:

class Rating < ActiveRecord::Base 
    belongs_to :article 

    after_save :update_article_rating_cache 
    after_destroy :update_article_rating_cache 

    def update_article_rating_cache 
    article.update_rating_cache if article 
    end 
end 

4)现在是超级容易被评级为你的物品进行分类:

class Article < ActiveRecord::Base 
    has_many :ratings 

    def self.by_ratings 
    order('rating_cache DESC') 
    end 

    def update_rating_cache 
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f 
    update_attribute(:rating_cache, current_rating) 
    end 
end 

和可以用作ActiveRelation!

祝你好运:)

+0

并且不要忘记初始化您现有文章的评级缓存:'Article.all.each {| article | article.update_rating_cache}' – jbescoyez

+0

如果OP可以轻松更改数据库并且不需要快速生产修复,那么我还会推荐这种方法。 –

相关问题