[英]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.count
。 votes.count + comments.count + ratings.count
。 votes.count + comments.count + ratings.count
。 votes.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 thebelongs_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.