ECTO-递归计算嵌套关联

时间:2019-01-10 11:26:57

标签: elixir ecto nested-sets

我有以下架构:

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的子文件夹和每个子文件夹中的合同数量,以及每个子文件夹是否归入子孙文件夹及其合同的树中。

1 个答案:

答案 0 :(得分:0)

对于诸如此类的问题,您将需要递归公用表表达式,Ecto本身还不支持这些表达式(请参阅:https://github.com/elixir-ecto/ecto/pull/2757)。我们必须使用片段(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返回带有root_id及其子元素的所有行,我们在这些行上加入契约(我假设folder_id模式的Contract列)。最后,我们使Ecto返回所有返回行的计数。