Postgrex 42P18错误排查:Ubuntu下Ecto查询含参数报错问题
这个问题我之前也碰到过,核心原因是PostgreSQL无法自动推断你传入的time_period参数的数据类型,不同环境下PostgreSQL的版本或默认配置差异,导致Mac上能侥幸运行但Ubuntu触发了严格的类型检查错误。
具体问题点
你在select子句中直接用^time_period传入一个字符串参数,但PostgreSQL无法确定这个参数应该被解析为text还是varchar类型。Mac环境的PostgreSQL可能版本较低或者默认类型推断规则更宽松,自动补全了类型;而Ubuntu环境的PostgreSQL(通常版本更新或配置更严格)则直接抛出了42P18类型不确定的错误。
解决方案
方案1:明确指定参数的数据类型(最简单的修复)
使用Ecto的type/2函数显式告诉PostgreSQL参数的类型,这样就不会有类型推断的问题了。修改后的代码如下:
time_period = to_string(date) Payment |> where([p], p.core_id == ^core_id and p.inserted_at >= datetime_add(^utc_date_from, 0, "day") and p.inserted_at <= datetime_add(^utc_date_to, 0, "day")) |> select([p], %{amount: sum(p.total), time_period: type(^time_period, :string)}) |> Repo.one()
方案2:在数据库层计算时间周期(更优雅的方案)
如果你的time_period是基于inserted_at转换的本地日期字符串,完全可以直接在数据库中用to_char函数完成转换,避免传入外部参数,彻底消除类型问题,同时还能提升查询效率。示例代码(假设你需要YYYY-MM-DD格式的日期字符串):
Payment |> where([p], p.core_id == ^core_id and p.inserted_at >= datetime_add(^utc_date_from, 0, "day") and p.inserted_at <= datetime_add(^utc_date_to, 0, "day")) |> select([p], %{amount: sum(p.total), time_period: fragment("to_char(?, 'YYYY-MM-DD')", p.inserted_at)}) |> Repo.one()
这样time_period直接由PostgreSQL计算生成,不需要从应用层传入参数,也就不会有类型推断的问题了。
为什么Mac和Ubuntu表现不同?
通常是两个环境的PostgreSQL版本差异导致的:旧版本的PostgreSQL对字符串参数的类型推断更宽松,会自动将未指定类型的字符串参数视为text;而新版本的PostgreSQL(Ubuntu上的软件源通常会提供更新的版本)则要求明确指定参数类型,否则就抛出错误。
内容的提问来源于stack exchange,提问作者D.R

