You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ecto中递归预加载分类的所有子分类?

Recursively Preload All Nested Subcategories in Ecto

Got it, let's fix this recursive subcategory preloading issue! Your current query only grabs the first level of subcategories, but we can solve this with two main approaches—one database-side (most efficient) and one Elixir-side (simpler for small datasets).

The best way to fetch all nested levels in a single query is using a recursive Common Table Expression (CTE). This lets the database handle the recursion, avoiding N+1 query problems and keeping things fast even for deep category trees.

First, your existing schema is already set up correctly (we'll tweak the association name for Elixir conventions):

schema "categories" do
  field :name, :string
  has_many :sub_categories, Category, foreign_key: :parent_id
end

Now here's the recursive query to load all nested subcategories:

def get_root_categories_with_all_subs do
  Repo.all(
    from c in Category,
      # Define the recursive CTE
      with_cte("recursive_categories", as: [
        # Base case: get all root categories (no parent)
        root <- from c in Category, where: is_nil(c.parent_id), select: c,
        # Recursive case: join subcategories to their parent categories from the CTE
        recursive sub <- from c in Category, join: p in "recursive_categories", on: c.parent_id == p.id, select: c
      ]),
      # Join and preload subcategories for every category in the CTE
      left_join: sub in assoc(c, :sub_categories),
      preload: [sub_categories: sub],
      # Only include categories that are part of our recursive tree
      where: c.id in subquery(from rc in "recursive_categories", select: rc.id)
  )
end

When you run this, each root category's sub_categories will contain its direct children, and each of those children will have their own sub_categories populated—all the way down to the leaf nodes.

Approach 2: Elixir Recursive Function (Simpler, Less Efficient)

If you prefer avoiding SQL CTE syntax, you can use an Elixir recursive function to load each level of subcategories. Note: This will run one query per level of your category tree, so it's better for small or shallow trees.

def load_nested_subcategories(categories) do
  # Preload all direct subcategories for the current list of categories
  preloaded_categories = Repo.preload(categories, :sub_categories)
  
  # Recursively load subcategories for each child
  Enum.map(preloaded_categories, fn category ->
    %{category | sub_categories: load_nested_subcategories(category.sub_categories)}
  end)
end

# Usage:
root_categories = Repo.all(from c in Category, where: is_nil(c.parent_id))
root_categories_with_all_subs = load_nested_subcategories(root_categories)

Key Notes

  • CTE is better for performance: It fetches all data in a single round trip to the database, which is critical for large or deep category trees.
  • Elixir recursion is simpler: Great for small datasets or when you want to avoid writing SQL-specific code.

内容的提问来源于stack exchange,提问作者DokiCRO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:21:48