如何为每条记录预加载一个关联?

时间:2017-02-20 23:47:10

标签: elixir phoenix-framework ecto

说我有以下Ecto型号:

  • ForumCategoryhas_many :forums
  • Forumbelongs_to :forum_categoryhas_many :forum_topics
  • ForumTopicbelongs_to :forum

然后我要加载ForumCategory的所有条目及其预先加载的所有关联Forum条目和每条条目的最新ForumTopic

ForumCategory - > Forum - > ForumTopic

论坛类别的预加载论坛很简单:

ForumCategory
|> preload(:forums)

现在我们还想预加载最新的论坛主题,但我们只想为每个论坛预加载最新的主题,因此我们不会从数据库加载整个论坛主题列表(将会有很多人)

forum_topics_query = ForumTopic
                     |> order_by([desc: :inserted_at])
                     |> limit(1)
ForumCategory
|> preload([forums: [forum_topics: ^forum_topics_query]])
|> Repo.all

这似乎乍一看似乎很有效,但很快就会发现这个查询只返回一个论坛主题,而不是每个论坛一个,而是一个论坛主题,从实际的SQL查询中也可以看出执行:

SELECT f0."id", f0."title", f0."body", f0."pinned", f0."forum_replies_count", f0."deleted_at", f0."inserted_at", f0."updated_at", f0."forum_id", f0."user_id", f0."forum_id"
FROM "forum_topics" AS f0 WHERE (f0."forum_id" = ANY($1))
ORDER BY f0."forum_id", f0."inserted_at" DESC
LIMIT 1

所以我的问题是,如何使Ecto预加载不仅仅是一个记录,而是每个论坛类别预先加载一个论坛?

1 个答案:

答案 0 :(得分:0)

我最终得到了这个使用连接和窗口函数组合的解决方案:

defmodule MyApp.ForumTopic do
  def latest_over_forum(per \\ 1) do
    from outer in __MODULE__,
      join: inner in fragment("""
        SELECT *, row_number() OVER (
          PARTITION BY forum_id
          ORDER BY inserted_at DESC
        ) FROM forum_topics
      """),
      where: inner.row_number <= ^per and inner.id == outer.id
  end
end

forum_topics_query = ForumTopic |> ForumTopic.latest_over_forum
ForumCategory
|> preload([forums: [forum_topics: ^forum_topics_query]])
|> Repo.all