elixir - Ecto 条件更新 - Ecto.StaleEntryError
问题描述
我正在尝试完成智能 upsert,我将其定义为:
- 如果 db 中没有具有相同 id 的模型,请执行 INSERT
- 如果 db 中存在具有相同 ID 的条目并且该条目较新(updated_at 字段),请不要更新
- 如果 db 中有具有相同 id 的条目并且该条目较旧(updated_at 字段),请执行 UPDATE
我看到 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;
解决方案
我认为执行以下操作可能更容易:
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 次数据库操作。
推荐阅读
- r - 在R中的多个地块之间画线
- c - 良好实践:非变异函数何时应该请求指针而不是副本?
- c# - Wix Installer 的自定义操作未创建文件夹
- python - 如何更改 andrej carpathys char rnn 以使用非文本数据类型
- html - 如何使用 CSS 将字体缩放到视口,同时保持两列布局?
- aws-lambda - 如何在 org.kie.internal.io.ResourceFactory.newFileResource 修复“java.lang.NullPointerException”
- javascript - 列表未在 v-for 指令中呈现
- dart - 如何获取用户输入作为另一个屏幕的 URL?
- ios - iOS:根据分发证书生成苹果开发者证书
- xamarin.forms - 如何在 xamarin 表单中使用 kindle uri 方案?