在 Ecto 查询中使用 having/3 select/3 时出现问题
Trouble with select/3 using having/3 in Ecto queries
(编辑:添加数据库表示和更新试验)
在我们的数据库中,我们有一个 Member 和一个 Membership 模式。一个会员有很多会员资格。 Membership 有字段 start_date 和 end_date。我正在尝试查询那些拥有多个会员资格的会员以及 select 这些会员资格的 start_date 和 end_date。我的问题是,有没有一种方法可以在不使用 preload/3 函数的情况下在一个查询调用中完成?
我们的数据库可以用元组表示:
# {Membership.member_id, Membership.start_date, Membership.end_date}
[
{1, ~D[2019-03-12], ~D[2020-03-11]},
{1, ~D[2019-04-05], ~D[2020-04-04]},
{3, ~D[2019-04-25], ~D[2020-04-24]},
{3, ~D[2020-06-12], ~D[2021-06-12]}
]
我试过
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
group_by: [s.start_date, s.end_date],
having: count(s) > 1,
select: {s.start_date, s.end_date}
# Output: [{~D[2019-04-25], ~D[2020-04-24]}]
但它给我的只是数据库中的第三个元素。
这是我目前正在使用的两个查询:
member_ids =
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
group_by: s.member_id,
having: count(s) > 1,
select: s.member_id
# Output: [1, 3]
data =
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
where: m.id in ^member_ids,
select: {s.start_date, s.end_date}
# Output:
# [
# {~D[2019-04-05], ~D[2020-04-04]},
# {~D[2019-03-12], ~D[2020-03-11]},
# {~D[2019-04-25], ~D[2020-04-24]},
# {~D[2020-06-12], ~D[2021-06-12]}
# ]
预期结果将是一个元组列表,例如:
[
{~D[2019-03-12], ~D[2020-03-11]},
{~D[2019-04-05], ~D[2020-04-04]},
{~D[2019-04-25], ~D[2020-04-24]},
{~D[2020-06-12], ~D[2021-06-12]}
]
您可以组合使用 array_agg
and unnest
个函数来获得所需的结果。
根据提供的信息,您似乎不需要加入 members
table 即可实现:在 memberships
上查询就足够了。
获得与您提供的结果非常相似的结果的纯 SQL 查询如下:
# select unnest(array_agg((start_date, end_date))) from memberships group by member_id having count(1) > 1;
unnest
-------------------------
(2019-03-12,2020-03-11)
(2019-04-05,2020-04-04)
(2019-04-25,2020-04-24)
(2020-06-12,2021-06-12)
(4 rows)
如你所见,这里的每一行都是记录类型。但是,如果我们将其翻译成 Ecto,我们将得到您概述的内容:
iex(1)> import Ecto.Query
Ecto.Query
iex(2)> query =
...(2)> from m in "memberships",
...(2)> having: count(1) > 1,
...(2)> group_by: m.member_id,
...(2)> select: fragment("unnest(array_agg((?, ?)))", m.start_date, m.end_date)
#Ecto.Query<from m0 in "memberships", group_by: [m0.member_id],
having: count(1) > 1,
select: fragment("unnest(array_agg((?, ?)))", m0.start_date, m0.end_date)>
iex(3)> Repo.all(query)
11:21:51.490 [debug] QUERY OK source="memberships" db=3.4ms
SELECT unnest(array_agg((m0."start_date", m0."end_date"))) FROM "memberships" AS m0 GROUP BY m0."member_id" HAVING (count(1) > 1) []
[
{~D[2019-03-12], ~D[2020-03-11]},
{~D[2019-04-05], ~D[2020-04-04]},
{~D[2019-04-25], ~D[2020-04-24]},
{~D[2020-06-12], ~D[2021-06-12]}
]
iex(4)>
如果您需要加入members
table(例如做一些记录资格),您仍然可以通过建议的方法做到这一点.例如:
select unnest(array_agg((start_date, end_date)))
from memberships
join members on members.id = memberships.member_id
where members.active
group by member_id
having count(1) > 1;
用 Ecto 表示的等效查询如下所示:
from m in "memberships",
join: member in "members", on: member.id == m.member_id,
having: count(1) > 1,
where: member.active,
group_by: m.member_id,
select: fragment("unnest(array_agg((?, ?)))", m.start_date, m.end_date))
(编辑:添加数据库表示和更新试验)
在我们的数据库中,我们有一个 Member 和一个 Membership 模式。一个会员有很多会员资格。 Membership 有字段 start_date 和 end_date。我正在尝试查询那些拥有多个会员资格的会员以及 select 这些会员资格的 start_date 和 end_date。我的问题是,有没有一种方法可以在不使用 preload/3 函数的情况下在一个查询调用中完成?
我们的数据库可以用元组表示:
# {Membership.member_id, Membership.start_date, Membership.end_date}
[
{1, ~D[2019-03-12], ~D[2020-03-11]},
{1, ~D[2019-04-05], ~D[2020-04-04]},
{3, ~D[2019-04-25], ~D[2020-04-24]},
{3, ~D[2020-06-12], ~D[2021-06-12]}
]
我试过
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
group_by: [s.start_date, s.end_date],
having: count(s) > 1,
select: {s.start_date, s.end_date}
# Output: [{~D[2019-04-25], ~D[2020-04-24]}]
但它给我的只是数据库中的第三个元素。
这是我目前正在使用的两个查询:
member_ids =
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
group_by: s.member_id,
having: count(s) > 1,
select: s.member_id
# Output: [1, 3]
data =
Repo.all from m in Member,
left_join: s in assoc(m, :memberships),
where: m.id in ^member_ids,
select: {s.start_date, s.end_date}
# Output:
# [
# {~D[2019-04-05], ~D[2020-04-04]},
# {~D[2019-03-12], ~D[2020-03-11]},
# {~D[2019-04-25], ~D[2020-04-24]},
# {~D[2020-06-12], ~D[2021-06-12]}
# ]
预期结果将是一个元组列表,例如:
[
{~D[2019-03-12], ~D[2020-03-11]},
{~D[2019-04-05], ~D[2020-04-04]},
{~D[2019-04-25], ~D[2020-04-24]},
{~D[2020-06-12], ~D[2021-06-12]}
]
您可以组合使用 array_agg
and unnest
个函数来获得所需的结果。
根据提供的信息,您似乎不需要加入 members
table 即可实现:在 memberships
上查询就足够了。
获得与您提供的结果非常相似的结果的纯 SQL 查询如下:
# select unnest(array_agg((start_date, end_date))) from memberships group by member_id having count(1) > 1;
unnest
-------------------------
(2019-03-12,2020-03-11)
(2019-04-05,2020-04-04)
(2019-04-25,2020-04-24)
(2020-06-12,2021-06-12)
(4 rows)
如你所见,这里的每一行都是记录类型。但是,如果我们将其翻译成 Ecto,我们将得到您概述的内容:
iex(1)> import Ecto.Query
Ecto.Query
iex(2)> query =
...(2)> from m in "memberships",
...(2)> having: count(1) > 1,
...(2)> group_by: m.member_id,
...(2)> select: fragment("unnest(array_agg((?, ?)))", m.start_date, m.end_date)
#Ecto.Query<from m0 in "memberships", group_by: [m0.member_id],
having: count(1) > 1,
select: fragment("unnest(array_agg((?, ?)))", m0.start_date, m0.end_date)>
iex(3)> Repo.all(query)
11:21:51.490 [debug] QUERY OK source="memberships" db=3.4ms
SELECT unnest(array_agg((m0."start_date", m0."end_date"))) FROM "memberships" AS m0 GROUP BY m0."member_id" HAVING (count(1) > 1) []
[
{~D[2019-03-12], ~D[2020-03-11]},
{~D[2019-04-05], ~D[2020-04-04]},
{~D[2019-04-25], ~D[2020-04-24]},
{~D[2020-06-12], ~D[2021-06-12]}
]
iex(4)>
如果您需要加入members
table(例如做一些记录资格),您仍然可以通过建议的方法做到这一点.例如:
select unnest(array_agg((start_date, end_date)))
from memberships
join members on members.id = memberships.member_id
where members.active
group by member_id
having count(1) > 1;
用 Ecto 表示的等效查询如下所示:
from m in "memberships",
join: member in "members", on: member.id == m.member_id,
having: count(1) > 1,
where: member.active,
group_by: m.member_id,
select: fragment("unnest(array_agg((?, ?)))", m.start_date, m.end_date))