如何在 Rails 中使用自引用 table 避免 N + 1 次查询
How to avoid N + 1 queries with self-referential table in Rails
如何正确使用 includes
以避免在这种情况下进行 N + 1 次查询:
我有一组可以嵌套的类别(即它们形成一棵树)。例如:
- 教学
- 课程 1
- 办公时间
- 讲座
- 课程 2
- 办公时间
- 讲座
- 研究
- 项目 1
- 项目 2
- 服务
要设置此层次结构,每个 Category
记录都有 parent_id
作为外键。
这是我的 Rails 型号:
class Category < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Category", optional: true
has_many :children, class_name: "Category", foreign_key: "parent_id"
end
我使用
访问给定用户的所有类别
@categories = Category.includes(:children).where(user_id: user.id)
但是,每次调用 @categories[i].children
都会生成一个新查询。
如何正确使用 includes
以便我可以访问每个类别的子类别而无需额外查询。
(我也尝试过 @categories = Category.where(user_id: user.id).includes(:children)
,但行为没有任何变化。)
includes
通常用于预先加载另一个 table。在这种情况下,您已经可以访问正在搜索的 table categories
.
你可以做的是构建一个 Hash/Dictionary 类别,这需要调用你的 categories
table 来生成哈希。
category_hash = {}
Category.where(user_id: user.id).each do |category|
category_hash[category.id] = {}
# put whatever data you want to reference here
category_hash[category.id][:parent_id] = category.parent_id
category_hash[category.id][:name] = category.name
end
然后在将您的类别引用为 category_hash[:some_id]
时将为您提供您想要存储在哈希中的任何数据...使用 O(1) 时间并且没有额外的数据库查询。
你也必须使用 joins
,所以你可以使用 categories
和所谓的 children_categories
:
之间的关系
Category.includes(:children).joins(:children).where(user_id: <user_id>)
# SELECT "categories"."id" AS t0_r0,
# ...
# "children_categories"."id" AS t1_r0,
# ...
# FROM "categories"
# INNER JOIN "categories" "children_categories"
# ON "children_categories"."parent_id" = "categories"."id"
# WHERE "categories"."user_id" =
否则你会看到我认为你现在的问题是什么:
Category.includes(:children).where(user_id: <user_id>)
# SELECT "categories".* FROM "categories" WHERE "categories"."user_id" =
# SELECT "categories".* FROM "categories" WHERE "categories"."parent_id" IN (, , ...)
如何正确使用 includes
以避免在这种情况下进行 N + 1 次查询:
我有一组可以嵌套的类别(即它们形成一棵树)。例如:
- 教学
- 课程 1
- 办公时间
- 讲座
- 课程 2
- 办公时间
- 讲座
- 课程 1
- 研究
- 项目 1
- 项目 2
- 服务
要设置此层次结构,每个 Category
记录都有 parent_id
作为外键。
这是我的 Rails 型号:
class Category < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Category", optional: true
has_many :children, class_name: "Category", foreign_key: "parent_id"
end
我使用
访问给定用户的所有类别@categories = Category.includes(:children).where(user_id: user.id)
但是,每次调用 @categories[i].children
都会生成一个新查询。
如何正确使用 includes
以便我可以访问每个类别的子类别而无需额外查询。
(我也尝试过 @categories = Category.where(user_id: user.id).includes(:children)
,但行为没有任何变化。)
includes
通常用于预先加载另一个 table。在这种情况下,您已经可以访问正在搜索的 table categories
.
你可以做的是构建一个 Hash/Dictionary 类别,这需要调用你的 categories
table 来生成哈希。
category_hash = {}
Category.where(user_id: user.id).each do |category|
category_hash[category.id] = {}
# put whatever data you want to reference here
category_hash[category.id][:parent_id] = category.parent_id
category_hash[category.id][:name] = category.name
end
然后在将您的类别引用为 category_hash[:some_id]
时将为您提供您想要存储在哈希中的任何数据...使用 O(1) 时间并且没有额外的数据库查询。
你也必须使用 joins
,所以你可以使用 categories
和所谓的 children_categories
:
Category.includes(:children).joins(:children).where(user_id: <user_id>)
# SELECT "categories"."id" AS t0_r0,
# ...
# "children_categories"."id" AS t1_r0,
# ...
# FROM "categories"
# INNER JOIN "categories" "children_categories"
# ON "children_categories"."parent_id" = "categories"."id"
# WHERE "categories"."user_id" =
否则你会看到我认为你现在的问题是什么:
Category.includes(:children).where(user_id: <user_id>)
# SELECT "categories".* FROM "categories" WHERE "categories"."user_id" =
# SELECT "categories".* FROM "categories" WHERE "categories"."parent_id" IN (, , ...)