用于选择带有左联接子查询的记录的空白结构

时间:2019-04-09 14:49:12

标签: elixir ecto

我有一些类似的Ecto结构:

defmodule MyApp.ForumCategory do
  use MyApp.Schema

  schema "forum_categories" do
    field :name, :string

    field :last_topic, :map, virtual: true
    field :last_post, :map, virtual: true
  end
end
defmodule MyApp.ForumTopic do
  use MyApp.Schema

  schema "forum_topics" do
    field :name, :string

    timestamps()

    belongs_to :forum_category, MyApp.ForumCategory

    has_many :forum_posts, MyApp.ForumPost
  end
end
defmodule MyApp.ForumPost do
  use MyApp.Schema

  schema "forum_posts" do
    field :text, :string

    timestamps()

    belongs_to :profile, MyApp.Profile
    belongs_to :forum_topic, MyApp.ForumTopic
  end
end

我想做的是检索所有论坛类别的列表及其最后一个主题和该主题的最后一个帖子:

...
  def list do
    topics_query =
      ForumTopic
      |> join(:inner, [ft], fp in assoc(ft, :forum_posts))
      |> distinct([ft], ft.forum_category_id)
      |> order_by([ft, fp], [desc: ft.forum_category_id, desc: fp.inserted_at])

    posts_query =
      ForumPost
      |> distinct([fp], fp.forum_topic_id)
      |> order_by([fp], [desc: fp.forum_topic_id, desc: fp.inserted_at])

    categories =
      ForumCategory
      |> join(:left, [fc], lft in subquery(topics_query), lft.forum_category_id == fc.id)
      |> join(:left, [fc, lft], lfp in subquery(posts_query), lfp.forum_topic_id == lft.id)
      |> join(:left, [fc, lft, lfp], lfpp in assoc(lfp, :profile))
      |> select([fc, lft, lfp, lfpp], {fc, lft, lfp, lfpp})
      |> Repo.all()
      |> Enum.map(&fold_category_data/1)

    {:ok, %{forum_categories: categories}}
  end

  defp fold_category_data({fc, nil, nil, nil}), do: fc
  defp fold_category_data({fc, lft, lfp, lfpp}) do
    fc
    |> Map.put(:last_topic, lft)
    |> Map.put(:last_post, %{lfp | profile: lfpp})
  end
...

但是我感到奇怪的是,如果论坛类别中没有主题(因此也没有帖子),则不会将查询绑定lftlfp选为nil,而是被选择为ForumTopicForumPost的结构,其所有字段都具有nil,因此折叠功能失败。

但是,如果我删除子查询并改为执行以下操作:

...
|> join(:left, [fc], lft in ForumTopic, lft.id == last_topic_id(fc.id))
...

其中last_topic_id是一个片段宏,它触发一个子查询以查找最后一个主题ID,然后一切正常,我得到了nil而不是空白模式。

有人可以解释一下为什么事情会以这种方式起作用,什么是最好的解决方法吗?

PS我不喜欢后一种选择,因为它涉及到编写大片段,并且从SQL性能的角度来看可能要慢得多。

1 个答案:

答案 0 :(得分:1)

我很肯定可以直接将其报告给 Ecto 。我不是每天的 Ecto 用户,所以我无法猜测为什么这个决定是故意做出的,但是对我来说这似乎是个错误。

解决方法非常简单:

- defp fold_category_data({fc, nil, nil, nil}), do: fc
+ defp fold_category_data(
+   {fc, %ForumTopic{id: nil}, %ForumPost{id: nil}, nil}
+ ), do: fc