You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将预加载User的user_id传入Ecto预加载子查询?

Ecto预加载时传递User ID到子查询解决方案

问题场景与代码

我有三个数据表:Tasks、Users、Reviews。Users负责处理Tasks,并会因工作表现获得Reviews,Reviews针对Users而非Tasks(评价的是用户的工艺水平)。编写的查询代码如下:

rq = from r in Review,
  where: r.module_type == "users",
  where: r.module_id == ^user_id,
  group_by: r.module_id,
  select: avg(r.stars)

t =
  Repo.get_by(Task, id: task_id)
  |> Repo.preload(user: [reviews: rq])

运行时报错:undefined function user_id/0,需要实现将预加载得到的User的user_id传入子查询rq,且知道可以用元组配置预加载细节但不知具体操作。

错误原因

定义子查询rq时直接引用^user_id,但此时user_id并非提前存在的变量——它是从Task关联的User中动态获取的,硬编码外部变量会导致变量未定义错误。

解决方案

使用Ecto预加载的元组配置形式,通过父级绑定动态获取User的ID,替代硬编码的外部变量:

方式1:单独定义子查询并指定结果键名

# 子查询中用^parent.id引用预加载的父级User的ID
rq = from r in Review,
  where: r.module_type == "users",
  where: r.module_id == ^parent.id,
  group_by: r.module_id,
  select: avg(r.stars)

t =
  Repo.get_by(Task, id: task_id)
  # 用元组{子查询, 结果键名}给预加载结果命名,方便后续访问
  |> Repo.preload(user: [reviews: {rq, :avg_stars}])

后续可通过task.user.avg_stars直接获取该用户的平均评分。

方式2:单独定义子查询不重命名

如果不需要给结果重命名,直接传递子查询即可:

rq = from r in Review,
  where: r.module_type == "users",
  where: r.module_id == ^parent.id,
  group_by: r.module_id,
  select: avg(r.stars)

t =
  Repo.get_by(Task, id: task_id)
  |> Repo.preload(user: [reviews: rq])

方式3:内联子查询

直接在预加载中编写子查询,简化代码结构:

t =
  Repo.get_by(Task, id: task_id)
  |> Repo.preload(
    user: [
      reviews: from r in Review,
        where: r.module_type == "users",
        where: r.module_id == ^parent.id,
        group_by: r.module_id,
        select: avg(r.stars)
    ]
  )

核心逻辑:^parent.id是Ecto预加载的特殊绑定,代表当前被预加载的父级记录(即User)的id字段,能动态将每个User的ID传入子查询,计算对应用户的平均评分。

内容的提问来源于stack exchange,提问作者sooon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 17:20:37