Rails 4 Eager load limit subquery

2019-05-21 03:46发布

Is there a way to avoid the n+1 problem when eager loading and also applying a limit to the subquery? I want to avoid lots of sql queries like this:

Category.all.each do |category|
  category.posts.limit(10)
end

But I also want to only get 10 posts per category, so the standard eager loading, which gets all the posts, does not suffice:

Category.includes(:posts).all

What is the best way to solve this problem? Is N+1 the only way to limit the amount of posts per category?

1条回答
成全新的幸福
2楼-- · 2019-05-21 04:00

From the Rails docs

If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects

So given the following model definition

class Category < ActiveRecord::Base
  has_many :posts
  has_many :included_posts, -> { limit 10 }, class_name: "Post"
end

Calling Category.find(1).included_posts would work as expected and apply the limit of 10 in the query. However, if you try to do Category.includes(:included_posts).all the limit option will be ignored. You can see why this is the case if you look at the SQL generated by an eager load

Category.includes(:posts).all

Category Load (0.2ms)  SELECT "categories".* FROM "categories"
Post Load (0.4ms)  SELECT "posts".* FROM "posts" WHERE "posts"."category_id" IN (1, 2, 3)

If you added the LIMIT clause to the posts query, it would return a total of 10 posts and not 10 posts per category as you might expect.

Getting back to your problem, I would eager load all posts and then limit the loaded collection using first(10)

categories = Category.includes(:posts).all
categories.first.posts.first(10)

Although you're loading more models into memory, this is bound to be more performant since you're only making 2 calls against the database vs. n+1. Cheers.

查看更多
登录 后发表回答