问题描述
给出一个大致如下所示的架构(我正在使用Ecto):
我想创建一个查询,该查询将返回用户所有节点的最新版本,按版本插入时间排序。
User
和Node
之间的关系使用Ecto的has_many
和belongs_to
关联。不幸的是Version
模式很奇怪,并且与节点的关系是在jsonb字段中的嵌套属性上建立的(我无法更改)。
到目前为止,我最好的是:
grouping = Version
|> select([g],%{node_id: fragment("data->'node'->>'id'"),inserted_at: max(g.inserted_at)})
|> group_by([g],g.node_id)
Version
|> join(:inner,[v],g in subquery(grouping),on: fragment("data->'node'->>'id'") == g.node_id)
|> join(:inner,n in Node,on: fragment("data->'node'->>'id'") == n.id)
|> where([_v,_g,n],n.user_id == ^user_id)
|> order_by([v],desc: :inserted_at)
|> Repo.all
** (Postgrex.Error) ERROR 42703 (undefined_column) column st0.node_id does not exist
query:
SELECT t0."guid",t0."state",t0."channel",t0."tags",t0."data",t0."Meta",t0."inserted_at",t0."updated_at" FROM "versions" AS t0
INNER JOIN (
SELECT data->'node'->>'node_id' AS "node_id",max(st0."inserted_at") AS "inserted_at" FROM "versions" AS st0 GROUP BY st0."node_id") AS s1
ON data->'node'->>'node_id' = s1."node_id"
INNER JOIN "nodes" AS n2 ON data->'node'->>'node_id' = n2."id"
WHERE (n2."user_id" = $1)
ORDER BY t0."inserted_at" DESC
我觉得我不在这里,不过我显然做错了。如果有人能够指出我在普通sql中应该做什么,那么我将能够将其转换为Ecto。
解决方法
已经解决了我自己的问题,将在这里分享。
首先,@ PeacefulJames的评论显示了Ecto组成查询的GROUP BY
部分的方式中出现了SQL错误。我可以使用group_by(fragment("node_id"))
来解决这个问题。
这使我仍然可以看到查询是错误的,this article帮助我指出了正确的方向。
以下查询给了我想要的结果。
SELECT t1."guid",t1."state",t1."channel",t1."tags",t1."data",t1."meta",t1."inserted_at",t1."updated_at"
FROM (
SELECT st0.data->'node'->>'node_id' AS "node_id",max(st0."inserted_at") AS "inserted_at"
FROM "versions" AS st0
GROUP BY node_id
) AS s0
INNER JOIN "versions" AS t1
ON (t1.data->'node'->>'node_id' = s0."node_id")
AND (t1."inserted_at" = s0."inserted_at")
INNER JOIN "nodes" AS n2
ON t1.data->'node'->>'node_id' = n2."id"
WHERE (n2."user_id" = $1)
ORDER BY t1."inserted_at" DESC
在Ecto中使用以下代码将其拉出:
grouping = Version
|> select([v],%{address: fragment("?.data->'node'->>'node_id'",v),inserted_at: max(v.inserted_at)})
|> group_by(fragment("node_id"))
subquery(grouping)
|> join(:inner,[g],v in Version,on: fragment("?.data->'node'->>'node_id'",v) == g.node_id and v.inserted_at == g.inserted_at)
|> join(:inner,[_g,v],n in Node,v) == n.id)
|> where([_g,_v,n],n.user_id == ^user_id)
|> order_by([_g,desc: v.inserted_at)
|> select([_g,v)
|> Repo.all