首页 > 解决方案 > Ecto 条件更新 - Ecto.StaleEntryError

问题描述

我正在尝试完成智能 upsert,我将其定义为:

我看到 repo.insert 可以选择将查询作为:on_conflict选项传递。我决定编写满足我需求的简约源代码。

update_query =
  from s in User,
     where: s.id == ^id,
     where: s.updated_at < ^updated_at,
     update: ^[set: Map.to_list(data)]

%User{}
|> Ecto.Changeset.change(data)
|> @repo.insert(conflict_target: :id, on_conflict: update_query)

此代码仅在插入发生时才有效。冲突时会导致(Ecto.StaleEntryError) attempted to insert a stale struct:错误。我可以where: s.id == ^id, where: s.updated_at < ^updated_at从 update_query 中删除并删除此错误,但是我会丢失所需的 updated_at 检查。所需的 postgres 代码看起来像

INSERT INTO mytable (id, entry) VALUES (42, '2021-05-29 12:00:00')
ON CONFLICT (id)
   DO UPDATE SET entry = EXCLUDED.entry, ......
      WHERE mytable.entry < EXCLUDED.entry;

标签: elixirecto

解决方案


我认为执行以下操作可能更容易:

def smart_upsert(%{id: id, updated_at: updated_at} = data) do
    query =
      from u in User,
      where: u.id == ^id,
      where: u.updated_at < ^updated_at
     
    User
    |> Repo.get_by(query)
    |> case do
      nil ->
        %User{}

      existing_user ->
        existing_user
    end
    |> User.changeset(data)
    |> Repo.insert_or_update()
  end

诚然,这需要 2 次数据库操作。


推荐阅读