silverdr
From another thread - making it a separate topic:
To make sure I understand correctly (because TFM seems ambiguous on this to me):
When there’s conflict (like a UNIQUE constraint being violated), does the insert/2 function return the conflicting record from the database, discarding the struct or changeset passed to it? What if multiple records conflicted with the passed data? Like when violating constraints on several fields?
Update:
Did some tests (using Postgres), and it seems that given a changeset, inserting which would violate unique constraint, Repo.insert(changeset, returning: true, on_conflict: :nothing) returns the data passed to it rather than the record from the database. Is this the expected behaviour? If yes then what’d be the way to obtain the existing record without performing a separate query for this purpose?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
Yes. To get the db record either do a query manually or use
returning: trueas additional option and make sure “something” is updated on conflict (often e.g. timestamps are good candidates).silverdr
Once more to make sure - is the fact that I used
on_conflict: :nothingthe reason for NOT receiving the record from the database? IOW - the function doesn’t do anything beyond translating from Elixir syntax to SQL, unlike e. g. Rails’create_or_find_by, right?LostKobrakai
Exactly.
DavidRawk-Blake
Without doing a write, is there a way to get this?
silverdr
Separate query, I reckon…
zzq
The thing I generally do is use
:replacewith the fields in the unique constraint (the conflict target) instead of using:nothing. (e.g. if the table has a unique constraint on the pair of columnsaandb, I doconflict_target: [:a, :b], on_conflict: {:replace, [:a, :b]}.) I know for a fact that when theon_conflictfires, doing that replace is a no-op. But it’s enough in order for the database to consider the record to be “updated” for the purposes ofreturning: true.silverdr
Right, but is this something that might change between implementations/versions? Or a documented behaviour that can be relied upon? I don’t say it isn’t. Just asking.
sbuttgereit
One note about this technique. From the application point of view this is a no-op, but assuming I’m reading the ecto_sql code correctly, in the database I don’t believe it is a no-op. Assuming my recollection is correct PostgreSQL would see this effectively as an update. This is why you’d get a returning result but which also means, on disk, you’d get the current row marked as deleted and a new row created; naturally the new row would be from your point of view the same data and appear as the old row, but the database overhead of an actual update would be incurred… including the ramifications for table bloat, vacuum overhead, etc.
I’ll double check this a bit later when I have time, but am confident enough that the generated SQL would cause that to be the case to say something now.
silverdr
That’d be my suspicion too. And the reason I asked whether it’s a [PostgreSQL] documented behaviour [that it’s a true
:noop]sbuttgereit
So from the PostgreSQL side, it can be demonstrated that
ON CONFLICT ... DO UPDATEis not a no-op.xmin(The identity (transaction ID) of the inserting transaction for this row version.) andctid(The physical location of the row version within its table.)INSERT ... ON CONFLICTin a way that’s similar to what I believe the Ecto query to produce. Again, we’ll do this in a transaction so we can capture some of the transaction details.idandtext_valueuser columns.As we can see, even though the record would appear to have not changed to a user it’s in fact a new record (all PostgreSQL updates can be thought of as a DELETE/INSERT combination). And if we think about it, this makes sense. PostgreSQL generating apparent data changes without actually doing anything (a no-op) wouldn’t be desirable if you were needing logic that included the transaction details… since our “upsert” query happened within the context of a transaction it makes sense that anything PostgreSQL tells us it did would have to also reflect the effective transaction id.
Naturally this doesn’t cover the Ecto side, and I don’t have a good environment for demonstrating from Elixir right now; but I did take a quick look at the source code and I’m pretty sure the
on_conflict: {:replace, [:a, :b]}construct creates an upsert query similar to my test query.So the moral of the story is don’t do no-change updates to the database unless you’re consciously accepting the additional table bloat, WAL updates, etc. that come with that.