在 Ecto v2.0 中使用横向连接
Using LATERAL joins in Ecto v2.0
我正在尝试加入对 post 记录的最新评论,如下所示:
comment = from c in Comment, order_by: [desc: c.inserted_at], limit: 1
post = Repo.all(
from p in Post,
where: p.id == 123,
join: c in subquery(comment), on: c.post_id == p.id,
select: [p.title, c.body],
limit: 1
)
生成这个 SQL:
SELECT p0."title",
c1."body"
FROM "posts" AS p0
INNER JOIN (SELECT p0."id",
p0."body",
p0."inserted_at",
p0."updated_at"
FROM "comments" AS p0
ORDER BY p0."inserted_at" DESC
LIMIT 1) AS c1
ON c1."post_id" = p0."id"
WHERE ( p0."id" = 123 )
LIMIT 1
它只是 returns nil
。如果我删除 on: c.post_id == p.id
它将 return 数据,但显然它将 return 所有 post 的最新评论,而不是有问题的 post .
我做错了什么?一种解决方法是使用 LATERAL
连接子查询,但我不知道是否可以将 p
引用传递到 subquery
.
谢谢!
问题是由此处的 limit: 1
引起的:
comment = from c in Comment, order_by: [desc: c.inserted_at], limit: 1
由于生成的查询是 SELECT * FROM "comments" AS p0 ORDER BY p0."inserted_at" DESC LIMIT 1
,它只是 return 对任何 post 的最新评论,而不是我查询的 post。
仅供参考,查询时间超过 150 毫秒,有约 200,000 条评论行,但通过一个简单的索引将其降低到约 12 毫秒:
create index(:comments, ["(inserted_at::date) DESC"])
值得注意的是,虽然此查询适用于 returning 有问题的 post 并且仅适用于最近的评论,但它实际上会 return $number_of_comments
行如果您删除 limit: 1
。假设您想要检索数据库中的所有 100 post 条以及每条评论的最新评论,并且您在数据库中有 200,000 条评论,则此查询将 return 200,000 行。相反,您应该使用 LATERAL
连接,如下所述。
.
更新
不幸的是ecto doesn't support LATERAL
joins right now。
ecto fragment
在这里效果很好,但是 join
查询将片段包裹在额外的括号中(即 INNER JOIN (LATERAL (SELECT …))
),这是无效的 SQL , 所以你现在必须使用原始 SQL:
sql = """
SELECT p."title",
c."body"
FROM "posts" AS p
INNER JOIN LATERAL (SELECT c."id",
c."body",
c."inserted_at"
FROM "comments" AS c
WHERE ( c."post_id" = p."id" )
ORDER BY c."inserted_at" DESC
LIMIT 1) AS c
ON true
WHERE ( p."id" = 123 )
LIMIT 1
"""
res = Ecto.Adapters.SQL.query!(Repo, sql, [])
此查询在同一数据库上 return 秒,不到 1 毫秒。
请注意,这不是 return 您的 Ecto 模型结构,只是来自 Postgrex 的原始响应。
我正在尝试加入对 post 记录的最新评论,如下所示:
comment = from c in Comment, order_by: [desc: c.inserted_at], limit: 1
post = Repo.all(
from p in Post,
where: p.id == 123,
join: c in subquery(comment), on: c.post_id == p.id,
select: [p.title, c.body],
limit: 1
)
生成这个 SQL:
SELECT p0."title",
c1."body"
FROM "posts" AS p0
INNER JOIN (SELECT p0."id",
p0."body",
p0."inserted_at",
p0."updated_at"
FROM "comments" AS p0
ORDER BY p0."inserted_at" DESC
LIMIT 1) AS c1
ON c1."post_id" = p0."id"
WHERE ( p0."id" = 123 )
LIMIT 1
它只是 returns nil
。如果我删除 on: c.post_id == p.id
它将 return 数据,但显然它将 return 所有 post 的最新评论,而不是有问题的 post .
我做错了什么?一种解决方法是使用 LATERAL
连接子查询,但我不知道是否可以将 p
引用传递到 subquery
.
谢谢!
问题是由此处的 limit: 1
引起的:
comment = from c in Comment, order_by: [desc: c.inserted_at], limit: 1
由于生成的查询是 SELECT * FROM "comments" AS p0 ORDER BY p0."inserted_at" DESC LIMIT 1
,它只是 return 对任何 post 的最新评论,而不是我查询的 post。
仅供参考,查询时间超过 150 毫秒,有约 200,000 条评论行,但通过一个简单的索引将其降低到约 12 毫秒:
create index(:comments, ["(inserted_at::date) DESC"])
值得注意的是,虽然此查询适用于 returning 有问题的 post 并且仅适用于最近的评论,但它实际上会 return $number_of_comments
行如果您删除 limit: 1
。假设您想要检索数据库中的所有 100 post 条以及每条评论的最新评论,并且您在数据库中有 200,000 条评论,则此查询将 return 200,000 行。相反,您应该使用 LATERAL
连接,如下所述。
.
更新
不幸的是ecto doesn't support LATERAL
joins right now。
ecto fragment
在这里效果很好,但是 join
查询将片段包裹在额外的括号中(即 INNER JOIN (LATERAL (SELECT …))
),这是无效的 SQL , 所以你现在必须使用原始 SQL:
sql = """
SELECT p."title",
c."body"
FROM "posts" AS p
INNER JOIN LATERAL (SELECT c."id",
c."body",
c."inserted_at"
FROM "comments" AS c
WHERE ( c."post_id" = p."id" )
ORDER BY c."inserted_at" DESC
LIMIT 1) AS c
ON true
WHERE ( p."id" = 123 )
LIMIT 1
"""
res = Ecto.Adapters.SQL.query!(Repo, sql, [])
此查询在同一数据库上 return 秒,不到 1 毫秒。
请注意,这不是 return 您的 Ecto 模型结构,只是来自 Postgrex 的原始响应。