50

我有一个 upsert 要求,所以我需要调用 postgres 存储过程或使用公用表表达式。我还使用 pgcrypto 扩展名作为密码,并希望使用 postgres 函数(例如“crypt”来编码/解码密码)。

但是我找不到让 Ecto 部分或全部使用原始 sql 的方法,是否打算让 ecto 仅支持 elixir dsl 而不允许在 dsl 不够时使用原始 sql?

我发现我可以通过适配器查询(火箭是应用程序的名称)

q = Ecto.Adapters.Postgres.query(Rocket.Repo,"select * from users limit 1",[])

但不确定如何将其应用于模型。我是 elixir 的新手,看来我应该能够使用 Ecto.Model.Schem。架构/3 但这失败了

Rocket.User.__schema__(:load,q.rows |> List.first,0)
** (FunctionClauseError) no function clause matching in Rocket.User.__schema__/3    
4

8 回答 8

45

在带有 Postgres 的 Ecto 2.0 (beta) 上,您可以使用Ecto.Adapters.SQL.query()( current docs , 2.0-beta2 docs ) 执行任意 SQL;除了行本身的列表(“ rows”)之外,它恰好返回列名列表(“ columns”)。

在下面的例子中,我

  1. 运行不带参数的自定义查询,
  2. 将结果的列名从字符串转换为原子,以及
  3. 将这些与结果的每一行结合起来,并使用Kernel.struct()将其映射到一个结构中

(您可能想要运行该query()版本(没有爆炸!)并检查{ok, res}。)

qry = "SELECT * FROM users"
res = Ecto.Adapters.SQL.query!(Repo, qry, []) # 1

cols = Enum.map res.columns, &(String.to_atom(&1)) # 2

roles = Enum.map res.rows, fn(row) ->
  struct(MyApp.User, Enum.zip(cols, row)) # 3
end
于 2016-04-20T00:09:03.237 回答
8

现在 Ecto 1.0 已经发布,这应该可以工作一段时间:

将以下函数添加到您的Repo模块中:

def execute_and_load(sql, params, model) do
  Ecto.Adapters.SQL.query!(__MODULE__, sql, params)
  |> load_into(model)
end

defp load_into(response, model) do
  Enum.map response.rows, fn(row) ->
    fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
      Map.put(map, key, value)
    end)

    Ecto.Schema.__load__(model, nil, nil, [], fields, &__MODULE__.__adapter__.load/2)
  end
end

并这样使用:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)
于 2015-09-04T06:22:48.610 回答
8

Ecto 2.0 的修改解决方案:

在 repo.ex 中:

  def execute_and_load(sql, params, model) do
    Ecto.Adapters.SQL.query!(__MODULE__, sql, params)
    |> load_into(model)
  end

  defp load_into(response, model) do
    Enum.map(response.rows, fn row ->
      fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
        Map.put(map, key, value)
      end)
      Ecto.Schema.__load__(model, nil, nil, nil, fields,
                           &Ecto.Type.adapter_load(__adapter__, &1, &2))
    end)
  end

用法:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)

更新:对于 Ecto 3,您可以使用__MODULE__.load(model, fields)而不是Ecto.Schema.__load__

于 2016-06-25T08:23:48.410 回答
7

除了Ecto.Adapters.SQL.query/4之外,还有Ecto.Query.API.fragment/1,可用于向数据库发送查询表达式。例如,要使用 Postgres 的数组函数array_upper,可以使用

Ecto.Query.where([x], fragment("array_upper(some_array_field, 1)]" == 1)
于 2015-08-20T21:05:38.983 回答
6

Ecto 2.2.8 提供了Ecto.Query.load/2,因此您可以执行以下操作:

use Ecto.Repo

def execute_and_load(sql, params, model) do
  result = query!(sql, params)
  Enum.map(result.rows, &load(model, {result.columns, &1}))
end

https://hexdocs.pm/ecto/Ecto.Repo.html#c:load/2

于 2018-01-23T23:10:39.940 回答
4

Ecto,至少从版本 ~> 0.7 开始,您应该使用:

Ecto.Adapters.SQL.query/4

def query(repo, sql, params, opts \\ [])

在给定的 repo 上运行自定义 SQL 查询。

如果成功,它必须返回一个 :ok 元组,其中包含一个至少有两个键的映射:

• :num_rows - 受影响的行数 • :rows - 作为列表的结果集。如果命令没有产生任何行作为结果,则可能会返回 nil 而不是列表(但仍会产生受影响的行数,就像不返回的删除命令一样)

选项

• :timeout - 等待调用完成的毫秒数,:infinity 将无限期等待(默认值:5000) • :log - 如果为 false,则不记录查询

例子

iex> Ecto.Adapters.SQL.query(MyRepo, "SELECT $1 + $2", [40, 2])

%{rows: [{42}], num_rows: 1}

于 2015-01-28T21:19:28.363 回答
3

这是https://stackoverflow.com/users/1758892/thousandsofthem示例,但只是缩小了一点(信用:他/她)

defmodule MyApp.Repo do
  [...]
  def execute_and_load(sql, params, schema) do
    response = query!(sql, params)
    Enum.map(response.rows, fn row ->
      fields = Enum.zip(response.columns, row) |> Enum.into(%{})
      Ecto.Schema.__load__(schema, nil, nil, nil, fields,
        &Ecto.Type.adapter_load(__adapter__(), &1, &2))
    end)
  end
end
于 2017-09-15T20:51:47.330 回答
-5

至少使用 ecto 4.0,您可以使用适配器进行查询,然后将结果提供给 Ecto.Model。架构/3:

q = Ecto.Adapters.Postgres.query(Rocket.Repo,"select * from users limit 1",[])
Rocket.User.__schema__(:load,q.rows |> List.first,0)
于 2015-01-05T18:10:09.213 回答