用于选择具有左连接子查询的记录的空白结构
Blank structs for selecting records with left joined subquery
我有一些简单的 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
我想做的是检索所有论坛类别的列表及其最后一个主题和这个主题的最后一个 post:
...
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
...
但我觉得奇怪的是,如果论坛类别没有主题(因此也没有 post),查询绑定 lft
和 lfp
不会被选为 nil
但它们被选为 ForumTopic
和 ForumPost
结构,其所有字段都具有 nil
,因此我的折叠功能失败。
但是如果我删除子查询并改为执行类似的操作:
...
|> join(:left, [fc], lft in ForumTopic, lft.id == last_topic_id(fc.id))
...
其中 last_topic_id
是一个片段宏,它触发子查询以查找最后一个主题 ID,然后一切都按预期工作,我得到 nil
s 而不是空白模式。
有人可以解释为什么事情会这样吗?最好的解决方法是什么?
PS 我不喜欢后一种选择,因为它涉及编写大片段,并且从 SQL 性能的角度来看可能要慢得多。
我确信这可能会作为一个问题直接报告给 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
我有一些简单的 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
我想做的是检索所有论坛类别的列表及其最后一个主题和这个主题的最后一个 post:
...
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
...
但我觉得奇怪的是,如果论坛类别没有主题(因此也没有 post),查询绑定 lft
和 lfp
不会被选为 nil
但它们被选为 ForumTopic
和 ForumPost
结构,其所有字段都具有 nil
,因此我的折叠功能失败。
但是如果我删除子查询并改为执行类似的操作:
...
|> join(:left, [fc], lft in ForumTopic, lft.id == last_topic_id(fc.id))
...
其中 last_topic_id
是一个片段宏,它触发子查询以查找最后一个主题 ID,然后一切都按预期工作,我得到 nil
s 而不是空白模式。
有人可以解释为什么事情会这样吗?最好的解决方法是什么?
PS 我不喜欢后一种选择,因为它涉及编写大片段,并且从 SQL 性能的角度来看可能要慢得多。
我确信这可能会作为一个问题直接报告给 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