Hey everyone,
I’m trying to perform a bulk upsert (insert_all
with on_conflict
) in Ecto for updating book ratings, but I ran into an issue where PostgreSQL enforces NOT NULL
constraints before handling the conflict resolution.
Context
I have a books
table with these relevant fields:
id
(UUID, PK)title
(NOT NULL)slug
(NOT NULL)ratings_dist
(array)ratings_count
(integer)average_rating
(float)inserted_at
(NOT NULL, timestamps)updated_at
(NOT NULL, timestamps)
I’m updating only ratings_dist
, ratings_count
, and average_rating
, and I’m generating updates
similar to this dummy example:
updates = [
%{
id: "00000000-0000-0000-0000-000000000001",
ratings_dist: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
ratings_count: 1,
average_rating: 6.5
}
]
However, when using insert_all/3
like this:
Repo.insert_all(Book, updates,
on_conflict: {:replace, [:ratings_dist, :ratings_count, :average_rating]},
conflict_target: :id
)
It fails because PostgreSQL enforces NOT NULL
constraints on title
, slug
, and inserted_at
. Even though the existing row has values for those fields, Postgres treats the insert as a new row first, checks constraints, then applies ON CONFLICT
logic.
Workarounds I’m Considering
- Use Dummy Values
- Add
"DUMMY_TITLE"
and"DUMMY_SLUG"
ininsert_all
, but exclude them from:replace
, so the original values remain. - This avoids fetching existing data but feels like a hack.
- Use
update_all
Instead
- Fetch existing books and loop through them with
Repo.update_all/2
. - Slower (N queries for N books), but avoids dummy data.
What’s the best practice in Ecto for bulk upserts when dealing with NOT NULL
fields? Are there other approaches that avoid both unnecessary updates and constraint violations?