1

我有一个带有一些 has_many 关联的 Post 模型。

class Post < ActiveRecord::Base
 ...
 has_many :votes
 has_many :comments
 has_many :ratings
end

我想要一个按 ( ) 排序帖子的查询votes.count + comments.count + ratings.count

例如,如果我的帖子有 3 票、2 条评论和 1 分,则其排序“指标”的值为 6。我该怎么做?

我还想要第二个查询,它使用相同的 3 个参数(投票、评论、评分)对其进行排序,但还添加了与 成反比的第四个参数created_at,因此较新的帖子将排名较高,而较旧的帖子将排名较低。总之,排序指标将类似于:

( F*(1/created_at) + votes.count + comments.count + ratings.count),其中 F 是比例因子。我该怎么做?

4

3 回答 3

4

我建议您在这里使用AR 计数器缓存

4.1.2.4:counter_cache

:counter_cache选项可用于更有效地查找所属对象的数量。
[...]
尽管在:counter_cache包含belongs_to声明的模型上指定了该选项,但必须将实际列添加到关联模型中。

因此,您将修改相应的belongs_to声明以包含该:counter_cache选项:

class Vote < ActiveRecord::Base
  belongs_to :post, :counter_cache => true
end
# Similarly for the other two...

然后posts在迁移中将计数器列添加到您的表中:

def change
  change_table :posts do |t|
    t.integer :votes_count
    #...
  end
end

您还需要迁移来初始化现有Posts 的计数器。

然后,您将拥有计数器作为模型的属性,您可以这样说:

Post.where(...).order('posts.votes_count + posts.comments_count + posts.ratings_count')

如果要包含,created_at则可以使用extract(epoch from created_at)将时间戳作为方便的双精度值来获取,可以在算术表达式中使用。


这样做的缺点是,如果您偏离 The One True Path To Rails Nirvana 的一根头发(或它真正要去的地方),计数器可能会不同步,因此您需要小心不要触摸数据库你自己,总是通过联想来创造和毁灭事物。我还建议您构建一个可以不时运行的快速'n'dirty sanity checker,以确保计数器正确。

如果您很高兴成为 PostgreSQL 特定的,那么您可以抛弃:counter_cache => true废话和随之而来的所有脆弱性,并使用数据库中的触发器来维护缓存的计数器值。

于 2013-04-16T04:24:57.310 回答
3

这是关于算法的。

对于非常简单的算法,查询是可以的。当您的想法越来越多时,需要更复杂的方法,并且查询将不再适用。

我建议您再构建一个名为“score”的字段来存储计算结果。创建记录时它有一个初始值。然后,每次你更新其中一个因素——投票、评论、评分时,你都会触发一个钩子来再次计算“分数”。

当你的算法改变时,你安排一个工人再次计算所有记录的“分数”。

对于订购,只需简单地按“分数”订购。

于 2013-04-16T04:24:30.783 回答
1

是否有理由需要在数据库中完成此操作?如果不是,我建议您在找到所有记录及其包含的关联后使用 sort_by ruby​​ 方法。就像是:

# In the post model 
class Post < ActiveRecord::Base
  def custom_metric
    votes.size + comments.size + ratings.size
  end
end

# In post controller
@posts = Post.where(id: ..).includes(:votes, :comments, :ratings).sort_by(&:custom_metric)

对于要对对象进行排序的其他方式,您可以遵循相同类型的逻辑。这种方法与其他建议的方法相当快,并且不会导致任何数据非规范化。无论数据库的状态如何,查询都将始终返回所需的结果。

于 2013-04-16T05:02:19.717 回答