Ecto - 递归计算嵌套关联

Ecto - counting nested associations recursively

我有以下架构:

defmodule MyApp.Folder do
  use Enterprise.Web, :model

  schema "folders" do
    has_many(:contracts, MyApp.Contract)
    has_many(:child_folders, MyApp.Folder, foreign_key: :parent_id)
  end
end

如您所见,每个文件夹可以递归地包含多个子文件夹,每个子文件夹都有自己的子文件夹,依此类推。在我的文件夹控制器中,我想计算每个文件夹中包含的合同总数及其子文件夹中的所有合同等等。

说,我有一个名为 root 的文件夹。如果我想统计文件夹顶层的合同数量,我可以简单地调用length(root.contracts)。但是,我仍然没有考虑 root 的子文件夹和每个子文件夹中的合同数量,以及每个子文件夹是否下降到后代文件夹及其合同树中。

你需要递归通用 Table 表达式来解决这些问题,Ecto 本身还不支持这些(参见:https://github.com/elixir-ecto/ecto/pull/2757). We'll have to use fragments (https://hexdocs.pm/ecto/Ecto.Query.html#join/5-joining-with-fragments

您没有提到您正在使用的数据库版本,但 Postgres/Mysql/MariaDb 的最新版本都支持 CTE。我也假设 Ecto 3.

Contract 
|> join(:inner, [c], f in fragment("(
  WITH RECURSIVE RecursiveFolders AS (
   SELECT
      F.id,
      F.name,
      F.parent_id as parent_id
   FROM
      Folders F
   WHERE
      F.id = ?
   UNION
   SELECT
      F.id,
      F.name,
      F.parent_id
    FROM
      Folders F
    JOIN RecursiveFolders C ON C.id = F.parent_id
   )
   SELECT
     *
   FROM
   RecursiveFolders

)", root_id), on: c.folder_id == f.id)
|> select([c], count(c.id))

为了解释,递归 CTE return 包含 root_id 及其子行的所有行,我们在这些行上加入合同(我假设 folder_idContract 模式)。最后,我们确实使 Ecto return 成为所有 returned 行的计数。