含$fields参数时Ecto查询报Protocol.UndefinedError求助
我帮你梳理下为什么会出现这个错误,以及怎么修复:
核心问题:累加器类型混乱+逻辑错误
你的代码里嵌套的Enum.reduce完全搞混了数据类型,这直接导致了那个Protocol.UndefinedError:
外层
Enum.reduce的初始值是列表,但内层返回的是查询对象
外层你写的是Enum.reduce(select, [], fn ... end),初始累加器是空列表[],但内层Enum.reduce每次迭代返回的都是Ecto.Query对象。这意味着处理完第一个操作(比如$avg)后,fields变量就变成了查询对象,而不是你预期的列表。处理
$fields时没有返回查询对象
当走到$fields分支时,你只是做了fields = Enum.at(v, 0)然后IO.inspect,根本没返回query。这时候内层Enum.reduce的返回值就变成了"id"(或者[:id]),这个值会被当作外层Enum.reduce的下一个累加器。下一次内层迭代时,代码会把这个字符串/列表当作query传入,而Ecto的Queryable协议根本不支持列表类型,自然就抛出了那个错误。内层
Enum.reduce的模式匹配其实有问题
从你给出的参数看,每个操作(比如$avg)对应的值是字段名列表(比如["arrive_at"]),但内层你写的是fn {k, v}, query -> ... end——这是在匹配键值对元组,但遍历列表时每个元素是单个字符串,正常来说这个模式匹配应该直接报错。你说移除$fields能运行,可能是实际代码里这里写错了,应该是fn field, query -> ... end?
修复后的代码
我重构了你的代码,把逻辑理顺,确保全程传递的都是合法的Ecto.Query对象,同时正确处理$fields:
select when is_map(select) -> # 初始化基础查询,从空结构体开始 base_query = from(p in queryable, select: %{}) # 遍历所有select操作,逐步构建查询 Enum.reduce(select, base_query, fn {operation, fields}, query -> # 每个操作对应一个字段列表,逐个处理字段 Enum.reduce(fields, query, fn field, current_query -> field_atom = String.to_atom(field) case operation do "$avg" -> from(p in current_query, select_merge: %{avg_^field_atom: avg(field(p, ^field_atom))}) "$count" -> from(p in current_query, select_merge: %{count_^field_atom: count(field(p, ^field_atom))}) "$min" -> from(p in current_query, select_merge: %{min_^field_atom: min(field(p, ^field_atom))}) "$max" -> from(p in current_query, select_merge: %{max_^field_atom: max(field(p, ^field_atom))}) "$sum" -> from(p in current_query, select_merge: %{sum_^field_atom: sum(field(p, ^field_atom))}) "$fields" -> # 把指定字段合并到查询结果里 from(p in current_query, select_merge: %{^field_atom: field(p, ^field_atom)}) end end) end)
关键改进点
- 统一累加器类型:外层
Enum.reduce的初始值是base_query(合法的Ecto查询对象),每次迭代都返回更新后的查询,全程不会出现类型混乱。 - 修正模式匹配:内层遍历字段列表时,参数是单个字段名,匹配正确的列表元素。
- 正确处理
$fields:通过select_merge把指定字段直接合并到查询的结果结构体中,不再是无效的局部变量赋值。 - 避免命名冲突:给聚合结果加了前缀(比如
avg_arrive_at),防止不同操作的字段名重复。
这样修改后,不管是聚合操作还是选择指定字段,都能正确生成Ecto查询,不会再出现那个协议未实现的错误了。
内容的提问来源于stack exchange,提问作者script

