在没有原始SQL的情况下在Ecto中使用Postgres UPDATE ... FROM

时间:2018-08-08 17:23:46

标签: postgresql elixir ecto

基于Elixir thread from last year,我能够编写原始SQL查询来使用不相关表中的值批量更新记录。但是,我希望能够使用Ecto生成此查询。

在下面的示例中,假设有两个表cats和dogs,并且cats表具有外键(dog_id)。我想把狗和猫联系起来。

下面的代码是我如何使用Elixir和原始SQL手动执行此操作:

cat_ids = [1,2,3] # pretend these are uuids
dog_ids = [4,5,6] # ... uuids

values =
  cat_ids
  |> Enum.zip(dog_ids)
  |> Enum.map(fn {cat_id, dog_id} ->
    "('#{cat_id}'::uuid, '#{dog_id}'::uuid)"
  end)
  |> Enum.join(", ")

sql = """
UPDATE cats as a
SET dog_id = c.dog_id
from (values #{values}) as c(cat_id, dog_id)
where c.cat_id = a.id;
"""

Repo.query(sql)

是否可以将其移动到Repo.update_all或使用某些片段,以便不手动构建查询?

1 个答案:

答案 0 :(得分:0)

当然,您可以使用Ecto语法,但是在我看来,它并没有太大不同,例如,您必须使用Schema,例如,在我的应用程序中,我具有用户身份验证,这就是我们更新令牌的方式:

def update_token(user_id, token) do
  Repo.transaction(fn ->
            from(t in UserAuthentication, where: t.user_id == ^to_string(user_id))
            |> Repo.update_all(set: [token: token])
end

和UserAuthentication架构大致类似:

defmodule MyApp.UserAuthentication do
  use Ecto.Schema
  import Ecto.Changeset

  schema "user_authentication" do
    field(:user_id, :integer)
    field(:token, :string)
    timestamps()
  end

  def changeset(%__MODULE__{} = user, attrs) do
    user
    |> cast(attrs, [:user_id, :token])
    |> validate_required([:user_id, :token])
  end
end

这对数据验证很有用,并且可以在您连接的任何数据库中使用。