2010-09-27 37 views
2

我有一个投票系统有两个模型:Item(id,name)和Vote(id,item_id,user_id)。帮助优化ActiveRecord查询(投票系统)

这里是我到目前为止的代码:

class Item < ActiveRecord::Base 
    has_many :votes 

    def self.most_popular 
    items = Item.all #where can I optimize here? 
    items.sort {|x,y| x.votes.length <=> y.votes.length}.first #so I don't need to do anything here? 
    end 
end 

有几件事情不对的,主要是我检索所有项目的记录,然后使用Ruby来计算的普及。我几乎可以肯定,这有一个简单的解决方案,但我不能完全明白这一点。

我宁愿收集记录并在初始查询中运行计算。这样,我可以在查询中添加一个简单的:limit => 1(或LIMIT 1)。

任何帮助将是伟大的 - 重写所有ActiveRecord甚至原始SQl。后者实际上会让我更清楚地了解要执行的查询的性质。

回答

3

集团,按次数排序,然后取第一个项目。在轨道3这个代码是:

Vote.group(:item_id).order("count(*) DESC").first.item 

在轨道2,这应该工作:

Vote.all(:order => "count(*) DESC", :group => :item_id).first.item 
+1

美丽!这里是我从这个派生的Rails 2.x语法:Vote.find(:all,:group =>“item_id”,:order =>“count(*)DESC”,:limit => 1).first.item – user94154 2010-09-27 21:45:32

+0

不要忘记:include =>:item(请参阅我的答案),否则当您打电话给Vote#项目时,您会做额外的查询。如果您可以先调用Vote#,那么还要组合:limit => 1和Array#首先看起来多余。 – 2010-09-28 04:56:49

0

也许有更好的方式红宝石做到这一点,但在SQL(MySQL的至少),你可以尝试这样的东西来获得前10的排名:按商品编号票

SELECT i.id, i.name, COUNT(v.id) AS total_votes 
FROM Item i 
LEFT JOIN Vote v ON (i.id = v.item_id) 
GROUP BY i.id 
ORDER BY total_votes DESC 
LIMIT 10 
0

处理这一个简单的方法是将计票字段添加到项目,并更新每次都有投票。 Rails曾经为你自动做这件事,但不确定在2.x和3.0中是否仍然如此。在任何情况下,使用Observer模式或仅在Vote模型中输入“after_save”就足够了。

然后,您的查询非常简单,只需在查询中添加“VOTE_COUNT DESC”命令即可。

+0

谢谢,但我不想处理数据完整性问题。我宁愿坚持这一个“真相的一个版本”。如果性能/缩放成为问题,也许我会实现这样的事情。 – user94154 2010-09-27 21:50:02

1

sepp2k有正确的想法。如果您不使用Rails 3,则相当于:

Vote.first(:group => :item_id, :order => "count(*) DESC", :include => :item).item