使用Ecto(MySQL)进行upsert的最简单方法是什么?

时间:2016-06-08 07:33:07

标签: mysql elixir phoenix-framework ecto

在我的应用程序中执行upsert很常见,我想实现最简洁的实现upsert的方法。

  1. 我应该使用片段来实现本机sql upsert吗?
  2. 任何惯用的外部方法都可以进行upsert?

3 个答案:

答案 0 :(得分:19)

您可以使用Ecto.Repo.insert_or_update/2,请注意,为此,您必须从数据库中加载现有模型。

 model = %Post{id: 'existing_id', ...}
 MyRepo.insert_or_update changeset
 # => {:error, "id already exists"}

示例:

result =
  case MyRepo.get(Post, id) do
    nil  -> %Post{id: id} # Post not found, we build one
    post -> post          # Post exists, let's use it
  end
  |> Post.changeset(changes)
  |> MyRepo.insert_or_update

case result do
  {:ok, model}        -> # Inserted or updated with success
  {:error, changeset} -> # Something went wrong
end

答案 1 :(得分:4)

如果您希望通过id之外的其他内容进行升级,则可以get_by替换get,如下所示:

model = %User{email: "existing_or_new_email@heisenberg.net", name: "Cat", ...}

model |> User.upsert_by(:email)
# => {:found, %User{...}} || {:ok, %User{...}}

defmodule App.User do
  alias App.{Repo, User}

  def upsert_by(%User{} = record_struct, selector) do
    case User |> Repo.get_by({selector, record_struct |> Map.get(selector)}) do
      nil -> %User{} # build new user struct
      user -> user   # pass through existing user struct
    end
    |> User.changeset(record_struct |> Map.from_struct)
    |> Repo.insert_or_update
  end
end

如果您正在寻找一种适用于模型和多个选择器(即国家+护照号码)的灵活方法,请查看我的十六进制包EctoConditionals

答案 2 :(得分:2)

在我的情况下,insert_or_update由于唯一索引约束raised出现了错误

对我有用的是通过on_conflict参数进行的 Postgres v9.5 upsert:

(考虑到唯一列称为user_id

changeset
|> MyRepo.insert(
    on_conflict: :replace_all,
    conflict_target: :user_id
)
相关问题