简体   繁体   English

Rails查询中的自定义排序

[英]Custom ordering in Rails query

I have a Post model with some has_many associations. 我有一些has_many关联的Post模型。

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

I want a query that orders the posts by ( votes.count + comments.count + ratings.count ). 我想要一个查询,以按( votes.count + comments.count + ratings.countvotes.count + comments.count + ratings.countvotes.count + comments.count + ratings.countvotes.count + comments.count + ratings.count )对帖子votes.count + comments.count + ratings.count

For example, if I a post had 3 votes, 2 comments, and 1 rating, its ordering "metric" would have a value of 6. How would I do this? 例如,如果我的帖子具有3票,2条评论和1个评分,则其排序“指标”的值为6。我该怎么做?

I also want a second query that orders it with the same 3 parameters (votes,comments,ratings), but also adds a 4th parameter that is inverse proportional to created_at , so newer posts would be ranked highly and older posts would be ranked lower. 我还希望第二个查询使用相同的3个参数(投票,评论,评分)对它进行排序,但还要添加一个与created_at成反比的第4个参数,因此,新帖子的排名较高,而旧帖子的排名较低。 In summary the ordering metric would be something like: 总而言之,排序指标类似于:

( F*(1/created_at) + votes.count + comments.count + ratings.count ), where F is a scaling factor. F*(1/created_at) + votes.count + comments.count + ratings.count ),其中F是比例因子。 How would I do this? 我该怎么做?

This is about algorithm. 这是关于算法的。

Query is okay for very simple algorithm. 对于非常简单的算法,查询是可以的。 When your ideas growing, more complex methods are required, and query will no long fit. 当您的想法不断发展时,就需要更复杂的方法,查询将不再适用。

I suggest you to build one more field named "score" to store the calculated result. 我建议您再建立一个名为“分数”的字段来存储计算结果。 It has an initial value when you create the record. 创建记录时,它具有初始值。 Then, every time you update one of the factors - votes, comments, ratings, you trigger a hook to calculate the "score" again. 然后,每次更新因素之一-投票,评论,评分时,您都会触发一个挂钩以再次计算“得分”。

When your algorithm changed, you arrange a worker to calculate "score" for all of the records again. 当算法改变时,您安排工作人员再次为所有记录计算“得分”。

For ordering, just simply order them by "score". 要订购,只需按“分数”订购它们。

I'd recommend that you use an AR counter cache here: 我建议您在此处使用AR计数器缓存

4.1.2.4 :counter_cache 4.1.2.4:counter_cache

The :counter_cache option can be used to make finding the number of belonging objects more efficient. :counter_cache选项可用于更有效地查找所属对象的数量。
[...] [...]
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. 尽管:counter_cache选项是在包含belongs_to声明的模型上指定的,但实际的列必须添加到关联的模型中。

So you'd modify the corresponding belongs_to declarations to include the :counter_cache option: 因此,您需要修改相应的belongs_to声明以包含:counter_cache选项:

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

and then add counter columns to your posts table in a migration: 然后在迁移中将计数器列添加到您的posts表中:

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

You'll also want a migration to initialize the counters for your existing Post s. 您还需要迁移以初始化现有Post的计数器。

Then you'll have the counters as properties of your models and you can say things like: 然后,将计数器作为模型的属性,您可以说出类似这样的内容:

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

If you want to include created_at then you could use extract(epoch from created_at) to get the timestamp as a convenient double precision value that you can use in arithmetic expressions. 如果要包括created_at则可以使用extract(epoch from created_at)来获取时间戳,作为方便的双精度值,可以在算术表达式中使用。


The downside to this is that the counters can get out of sync if you stray but a hair from The One True Path To Rails Nirvana (or where ever it is really going ;) so you'll need to be careful not to touch the database yourself and always go through the associations to create and destroy things. 不利的一面是,如果您误入歧途,计数器可能会失去同步,而如果您从Rails Nirvana的真实路径(或实际发生的任何地方)发了疯,那么您需要注意不要触摸数据库。自己,并始终通过协会来创造和摧毁事物。 I'd also recommend that you build a quick'n'dirty sanity checker that you can run every now and then to make sure the counters are correct. 我还建议您构建一个快速运行的脏检查程序,该检查程序可以不时运行以确保计数器正确。

If you're happy to be PostgreSQL-specific then you could ditch the :counter_cache => true nonsense and all the brittleness that comes with it and use triggers in the database to maintain the cached counter values. 如果您愿意成为特定于PostgreSQL的对象,则可以:counter_cache => true废话以及它附带的所有易碎性,并使用数据库中的触发器来维护缓存的计数器值。

Is there a reason why this needs to be done in the database? 为什么需要在数据库中完成此操作? If not I would suggest you use the sort_by ruby method, after finding all of the records and their included associations. 如果不是,我建议您在找到所有记录及其包含的关联之后,使用sort_by ruby​​方法。 Something like: 就像是:

# 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)

You can follow the same type of logic for the other way you want to sort your objects. 您可以按照相同的逻辑类型对对象进行排序。 This method will be comparably fast to the others suggested and will have the bonus of not causing any data denormalization. 与建议的其他方法相比,该方法将很快,并且具有不会引起任何数据非规范化的优点。 The query will always return the desired result regardless of the state of your database. 该查询将始终返回所需的结果,而不管数据库的状态如何。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM