web-dev-qa-db-ja.com

Ecto(MySQL)でアップサートを行う最も簡単な方法は何ですか

アップサートを行うことは私のアプリでは一般的であり、アップサートを実装するための最もクリーンでシンプルな方法を実装したいと思います。

  1. フラグメントを使用してネイティブSQLアップサートを実装する必要がありますか?
  2. アップサートを行うための慣用的な外部の方法はありますか?
12
Teo Choong Ping

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
20
Alex Troush

id以外の方法でアップサートを探している場合は、次のようにget_bygetに交換できます。

model = %User{email: "[email protected]", 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

偶然にも、モデル間で機能する柔軟なアプローチを探していて、複数のセレクター(つまり、国+パスポート番号)を探している場合は、私の16進パッケージをチェックしてください EctoConditionals

4
codeanpeace

私の場合、insert_or_updateは一意のインデックス制約のためにエラーを発生させました????

私のために働いたのはPostgres v9.5upsert through on_conflict パラメーター:

(一意の列を考慮するとuser_idと呼ばれます)

changeset
|> MyRepo.insert(
    on_conflict: :replace_all,
    conflict_target: :user_id
)
0
Artur Klesun