Enum.reduce中Select Merge未按预期工作的技术求助
解决Ecto聚合查询无法同时保留avg和count的问题
嘿,我一眼就看出问题所在了——你在内层循环里每次都重新初始化了查询对象,导致每次处理新的聚合函数时,之前已经构建好的查询都会被覆盖,最后只剩下最后一次的操作结果。
问题根源
你原来的代码里,每次进入内层Enum.reduce时都执行了query = from p in model, select: %{},这相当于每次处理$avg或$count时都从头开始创建查询,而不是在之前的基础上扩展。
修复后的代码
我们只需要把初始查询移到循环外面,让所有聚合操作都基于同一个查询对象累加构建就行:
# 先创建一次基础查询,后续所有操作都基于它扩展 initial_query = from p in model, select: %{} Enum.reduce(select, initial_query, fn {_top_key, agg_funcs}, current_query -> Enum.reduce(agg_funcs, current_query, fn {agg_type, _fields}, query -> case agg_type do "$avg" -> from p in query, select_merge: %{avg: avg(p.id)} "$count" -> from p in query, select_merge: %{count: count(p.pass)} # 要是以后需要加$sum之类的聚合,直接在这里加分支就行 _ -> query end end) end)
为什么这样改就对了?
- 初始查询仅创建一次:把
initial_query放在所有循环外面,确保后续的select_merge都是在同一个查询基础上添加字段。 - 累加式构建查询:两层
Enum.reduce都把当前的查询对象作为累加器,每次处理聚合函数时,都会把新的聚合字段合并到已有的查询结果里,而不是重置。 - 代码更易维护:用
case代替多个if判断,逻辑更清晰,以后要加其他聚合类型也很方便。
验证效果
修改后执行流程会变成:
- 处理
$avg后,得到查询:#Ecto.Query<from m in Model, select: %{avg: avg(m.id)}> - 处理
$count时,基于上面的查询继续select_merge,最终得到:#Ecto.Query<from h in Model, select: %{avg: avg(h.id), count: count(h.pass)}>
完全符合你想要的结果!
内容的提问来源于stack exchange,提问作者script
相关产品推荐
相关产品推荐

