在 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 的原始响应。