【发布时间】:2015-08-10 11:11:02
【问题描述】:
这是我经常遇到的一个问题。关于这个问题有一些类似的问题,但都不是很完整(而且它们可能已经过时,因为 Rails 4 可能引入了有助于解决这个问题的新功能)
让我举一个问题的简单例子和已知的“解决”问题的方法:
假设我有一个User 模型和一个Post 模型,还有一个User has_many :posts
现在,我想要获得帖子最多的前五名用户。
以下是我知道的选项,但它们都有自己的缺点:
1)
users = User.all
@top_users = users.sort {|a,b| a.posts.count <=> b.posts.count}.take(5)
缺点:为每个用户发出一个数据库请求,使得这个解决方案非常慢。
2) 直接使用带有 Join 的 SQL 代码 (例如参见this question and answer)
select('users.*, COUNT(posts.id) AS posts_count').joins(:posts).group('users.id').order('posts_count DESC').take(5)
这将运行数据库中的所有排序逻辑。然而:
- 我们使用了大量特定于 DB 的代码(例如在 PostgreSQL 中,我们需要其他语法)。如果可能,最好使用 ActiveRecord 方法。
- 使用内部联接意味着永远不会返回没有任何帖子的用户。当我们想要返回没有帖子的用户时,这也是一个问题。
3) 直接将 SQL 与外部联接一起使用(参见例如 this question and answers)
User.select("users.*, COUNT(posts.id) as posts_count").joins("LEFT OUTER JOIN posts ON posts.user_id = users.id").group("posts.id").order("posts_count DESC")
这也会返回没有帖子的用户。缺点:
- 更多特定于 DB 的代码(如 #2),更难阅读。
4) 使用计数器缓存列 (有关此技术的完整说明,请参阅this Railscasts episode)
基本上,在 User 上创建一个新列,通过在每次创建或删除新帖子时更改字段中的值来跟踪该用户的当前 posts 计数。
这是非常快速和可读的。缺点是我们只能在User 上定义一个新字段后才能使用它。在许多情况下,这是可以接受的,但更难变得灵活,因为需要更改用户表才能使其在每个关联中工作,我们可能希望为其创建前五名。此外,由于这是一个缓存字段,因此存在不会触发该字段更新的数据库操作。
有没有更好(可读和有效)的方法来完成这个?首选使用内置 ActiveRecord 方法的东西。
【问题讨论】:
-
还有另一个选项可以添加到您的列表中,但有缺点。您可以将字段
posts_count添加到User模型。如果帖子的添加频率低于选择的前五名用户,那么这可能是一个不错的尝试。 -
In PostgreSQL for example we would need other syntax– 示例 #2 中的 DB 到底是什么? -
您可以尝试使用
includes(...),而不是显式的LEFT OUTER JOIN,它隐式地执行OUTER JOIN。此外,您在选项 2 和 3 中的查询似乎足够通用,以至于语法可能在您感兴趣的 SQL 实现中是通用的。 -
@Glupo:啊,是的。
cache_counter,对吧?我会将其添加到问题帖子中的可能性中。在许多情况下,这可能是一个很好的解决方案。但是,缺点当然是需要为此更改user数据库表。
标签: mysql ruby-on-rails ruby ruby-on-rails-4 activerecord