【问题标题】:How to avoid N + 1 queries with self-referential table in Rails如何避免在 Rails 中使用自引用表进行 N + 1 次查询
【发布时间】:2020-09-28 03:42:32
【问题描述】:

在这种情况下如何正确使用includes来避免N + 1个查询:

我有一组可以嵌套的类别(即它们形成一棵树)。例如:

  • 教学
    • 课程1
      • 办公时间
      • 讲座
    • 课程2
      • 办公时间
      • 讲座
  • 研究
    • 项目一
    • 项目 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),但行为没有改变。)

【问题讨论】:

  • 这是一个非常好的问题。在 rails 中默认使用 Category.include(:descendants) 这样的东西会很好。

标签: ruby-on-rails activerecord select-n-plus-1


【解决方案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" = $1

否则你会发现我现在认为是你的问题:

Category.includes(:children).where(user_id: <user_id>)
# SELECT "categories".* FROM "categories" WHERE "categories"."user_id" = $1
# SELECT "categories".* FROM "categories" WHERE "categories"."parent_id" IN ($1, $2, ...)

【讨论】:

  • 不幸的是,这不起作用。我最终接受了@Sean 的建议。
  • 我必须再看一遍才能确定;但是,如果我没记错的话,它只返回了那些有孩子的类别,而且它仍然展示了 N+1 个查询。
【解决方案2】:

includes 通常用于急切加载另一个表。在这种情况下,您已经可以访问正在搜索的表 categories

您可以做的是构建一个类别的哈希/字典,这需要一次调用您的categories 表来生成哈希。

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) 时间且无需额外的数据库查询。

【讨论】:

  • 工作就像一个魅力。我很惊讶我不得不“自己动手”。 Rails 似乎对其他所有东西都有一个内置的技巧。
  • 我完全明白了,我经常使用祖先 gem,我发现自己的情况和你一样,当你在同一张表中询问后代时,它会生成 N+1。很高兴我能帮上忙。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-30
  • 1970-01-01
  • 2017-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多