如何通过Ecto获取原生SQL返回的数据库值
嘿,我完全懂你的困扰——写了复杂的原生SQL查询后,没法像用常规Ecto查询那样直接拿到键值对的Map结构,还要适配Phoenix模板展示,手动转换又怕麻烦对吧?别慌,这儿有两种通用又简便的方法,不用自己瞎折腾:
方法一:手动配对字段与值转Map
这是最直接的方式,利用Ecto返回结果里的columns(字段名列表)和rows(每行数值列表)来生成Map:
# 执行原生查询 results = Ecto.Adapters.SQL.query!(MyApp.Repo, "SELECT id, name, email FROM users WHERE active = $1", [true]) # 转换为键值对Map列表 map_results = Enum.map(results.rows, fn row -> Enum.zip(results.columns, row) |> Map.new() end)
这样处理后,map_results里的每一项都是标准的Elixir Map,比如%{"id" => 1, "name" => "Alice", "email" => "alice@example.com"},直接就能在Phoenix模板里像遍历常规Ecto结果那样用,比如<%= for user <- @map_results do %><%= user["name"] %><% end %>。
方法二:用Ecto Schema映射结果(更规范)
如果你的原生查询返回结构固定,用Schema来映射会更靠谱,还能自动处理类型转换(比如数据库时间转Elixir的日期类型):
首先定义一个临时Schema(如果没有对应表的Schema):
defmodule MyApp.UserQueryResult do use Ecto.Schema @primary_key false # 因为是查询结果,不需要主键 schema "users" do field :id, :integer field :name, :string field :email, :string # 按需添加你查询返回的其他字段 end end
然后用Ecto的load/2方法把原生查询结果转成Schema结构体:
results = Ecto.Adapters.SQL.query!(MyApp.Repo, "SELECT id, name, email FROM users WHERE active = $1", [true]) {:ok, structured_results} = Ecto.Schema.load(MyApp.UserQueryResult, results, repo: MyApp.Repo)
这下structured_results里的每一项都是和常规Ecto查询一样的结构体,比如%MyApp.UserQueryResult{id: 1, name: "Alice", email: "alice@example.com"},在Phoenix模板里直接用user.name这种写法就行,和平时用Ecto的体验完全一致。
两种方法各有侧重:方法一灵活,适合字段不固定的复杂查询;方法二更规范,适合结构固定的场景,还能享受Ecto的类型处理能力。选哪种全看你的查询需求~
内容的提问来源于stack exchange,提问作者tajihiro

