SQLite Query Language: upsert
sqlite.org
sqlite.org
I actually like this feature more in SQLite than in Postgres. In Postgres I worry about pushing that kind of work into the database in clustered situations, as there are places where it can go wrong and it would be more likely that the code wouldn't be able to handle that failure properly (as opposed to having to write the if/then/else yourself, which if you're a good coder would also involve some try/catch and proper fallback behavior in case of an error).
It's actually really hard to do so correctly in the face of concurrency. Do not attempt to do so unless you've a really good reason.
Edit: I find it interesting that this comment has fluctuated between -2 and +2 points. Apparently saying that engineers should be able to handle concurrency errors is controversial?
I didn't vote either way. But perhaps it's less the fact that they should be able to handle concurrency, and more that replicating the necessary non-trivial logic in several applications isn't a good plan.
If you're writing if/then/else when dealing with a database, in my mind you're likely doing it wrong.
Edit: Downthread it was pointed out that you could just run an update and insert on every request and let one fail, which seems like a fair tradeoff for the extra network round trips.
INSERT INTO phonebook(name, phonenumber) VALUES ('Alice','704-555-1212') WHERE NOT EXISTS (SELECT 1 FROM phonebook WHERE name = 'Alice');
In PostgreSQL (for example) you could use a CTE and the RETURNING clause to prevent two statements from having to run. I don't think sqlite supports that. Current version of PostgreSQL makes that unnecessary as it has "upsert" as well.
You've chosen to trade off network round trips for extra work in the database and slightly longer row locks while both commands run, which seems like a fair tradeoff.
True if the generation of the insert doesn't have some heavier burdens on the caller side. Otherwise, start a txn, do the first, check if 0 updated, then do the second.
Rows can exists without you being able to see them. Consider:
S1: BEGIN;
S2: BEGIN;
S1: INSERT name = 'Alice'
S2: UPDATE WHERE name = 'Alice' -> no row matches
S2: INSERT name = 'Alice' WHERE NOT EXISTS (name = 'Alice')
S2: <blocks, due to potential uniqueness violation>
S1: COMMIT;
S2: <resumes, raises uniqueness violation>
Edit: formattinghttps://www.postgresql.org/docs/9.1/static/transaction-iso.h...
> If the first updater commits, the second updater will ignore the row if the first updater deleted it, otherwise it will attempt to apply its operation to the updated version of the row.
My understanding is that the update from S2 would be rerun applied.
Specifically postgres calls out:
> The search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition.
Unfortunately you're wrong. I've now tested it, but I was also pretty confident before - I'm a postgres developer, and I worked on/comitted the PG upsert implementation ;)
postgres[10287][1]=# CREATE TABLE data(key text unique);
CREATE TABLE
postgres[10287][1]=# BEGIN ISOLATION LEVEL SERIALIZABLE ;
BEGIN
postgres[10284][1]=# BEGIN ISOLATION LEVEL SERIALIZABLE ;
BEGIN
postgres[10287][1]*=# INSERT INTO data VALUES('alice');
INSERT 0 1
postgres[10284][1]*=# INSERT INTO data SELECT 'alice' WHERE NOT EXISTS(SELECT * FROM data WHERE key = 'alice');
postgres[10287][1]*=# COMMIT;
COMMIT
postgres[10284][1]*=#
ERROR: 23505: duplicate key value violates unique constraint "data_key_key"
DETAIL: Key (key)=(alice) already exists.
SCHEMA NAME: public
TABLE NAME: data
CONSTRAINT NAME: data_key_key
LOCATION: _bt_check_unique, nbtinsert.c:535
(the number in brackets in the prompt is the backend pid, allowing to differentiate the two sessions).> > If the first updater commits, the second updater will ignore the row if the first updater deleted it, otherwise it will attempt to apply its operation to the updated version of the row.
> My understanding is that the update from S2 would be rerun applied.
> Specifically postgres calls out:
> > The search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition.
Those comments are about row-level locks - they're not the problem here. What you get is a constraint violation due to the unique constraint.
In the master branch there is no file "Alice". Two branches get created. One of them runs sed via find on all files with the name "Alice" and then it creates a file called "Alice" if find doesn't find any files called Alice.
The second branch creates a file called "Alice".
Both want to merge now but unfortunately there is a merge conflict. In this case automatic conflict resolution is impossible so one of the branches has to be discarded (transaction rollback). You can now "rerun" the branch and everything should work on the next merge.
Conclusion: "update + insert where" is not equivalent to "upsert". The former can cause a transaction to fail and must be retried on failure. With "upsert" the database engine has enough information to handle the unique constraint.
Try the same thing with no constraint. You should see the transaction fail but not because of a unique constraint. Otherwise there's something incorrect.
There are some DBs that do that for you like CockroachDB.
This means there is a window between the sub-SELECT and the INSERT itself where a secondary transaction can insert something, causing both your original update to do nothing, and the insert to fail. Thus you've lost the write entirely.
Isolation levels will for example have impact on how transactions proceed and what results you see (REPEATABLE READ vs READ COMMITTED); the error handling (did your transaction actually fail from a concurrent INSERT succeeding, or did the update fail, or did something else happen entirely? UPSERT is often not a standalone operation in a transaction) must be carefully decided for each use-case or else you're likely to redo needless transactions or throw away good ones, and you have to work around incidental problems like latency using stored procedures to keep everything server-side, etc etc.
It's just not the kind of thing you want to replicate a number of places at each use site in a number of tools. It's something only the database knows how to manage properly by having "global knowledge".
Here‘s a great article describing these problems in detail if you‘re interested: https://www.depesz.com/2012/06/10/why-is-upsert-so-complicat...
Maybe... I'm used to systems with upsert/merge.
Lots of app level code in ORMs that use ADD/EDIT or CREATE/UPDATE actions can encapsulate this by just a SET, which counts the rows and then if zero INSERTS, if > 0 UPDATE.
In fact, it always made me wonder why other systems (at the time) didn't support this critical and useful feature? I was getting tired of writing a SELECT, comparing the returned row against the one I was about to update, then branching to an UPDATE or INSERT command depending. Doing it all in one command was a breeze.
[0] - https://www.sqlite.org/draft/images/syntax/upsert-clause.gif
[1] - https://www.json.org/
> (25) How are the syntax diagrams (a.k.a. "railroad" diagrams) for SQLite generated?
> The process is explained at http://wiki.tcl-lang.org/21708.
I looks like a TCL script is used [2].
[1]: https://www.sqlite.org/faq.html#q25
[2]: https://www.sqlite.org/docsrc/doc/tip/art/syntax/bubble-gene...
Fun times.
https://www.sqlite.org/c3ref/last_insert_rowid.html
And if it's a table with autoincrement primary key, try:
https://www.depesz.com/2012/06/10/why-is-upsert-so-complicat...
> As noted, MERGE is complicated and slow. If two different database servers implement it, then it is going to be implemented with different schematics [sic]. Oracle and MySQL already have different implementation’s, and to nobody’s surprise, MySQL’s implementation allows for risky behavior that does not protect the data. PostgreSQL users expect that any data put into the database will reliably come out of the database. So MERGE is slow and inconsistently implemented. The correct thing to do is make the application aware of if a record is new or existing. Any application making use of MERGE should open a bug to replace it with more predictable and faster logic using INSERT and UPDATE.
> If an application does use MERGE, it has to account for implementation specific behavior and not will not be portable, in a safe way, to other database servers. So it is not suitable for ORM’s or database independent applications.
> So what is a legitimate use case for MERGE where not knowing if a record is new or existing is not possible prior to the transaction? They only thing that I can think of is one-off scripts that do not have any concurrency. But if you design for that, then some misguided ORM for web applications is going to use your MERGE function and users are going to wonder about what happened to their data when concurrency concerns were ignored or not understood.
(I believe this comment is by Michael McLaughlin, the author of books on Oracle PL/SQL programming.)
Does anyone happen to know if this is still accurate today, or whether there's been any attempt to incorporate UPSERT support into standard SQL? I could imagine the SQL standard maintainers not wanting to offer UPSERT if MERGE is already a superset of its functionality; but a case could be made that, since MERGE's complexity has apparently discouraged adoption by implementations and users, it may be sensible to support UPSERT nonetheless. Is this a reasonable way to think about it?
I don't mean to imply that standardizing UPSERT into SQL is an especially important concern for anyone; this is merely a trail of thought that originated from wondering about why the UPSERT statement is nonstandard.
I think he concentrates on "possible" where the interesting use case is "convenient". The are many cases where you want to go from at-least-once execution of idempotent tasks to a consistent view of data. In those cases it's irrelevant if the data existed before or not - you're likely doing an upsert with exactly the same data under exactly the same key and care only the resulting log of events.
MySQL has INSERT INTO ... ON DUPLICATE KEY UPDATE. This is the functionality that SQLite now has.
The difference between REPLACE and ON DUPLICATE KEY UPDATE is subtle but useful.
Anyone know if the site is hosted on GitHub?
https://www.fossil-scm.org/index.html/doc/trunk/www/index.wi...
That was somewhat tangential, sorry, but it's been really bugging me recently and I wanted to vent.
CSS Grid will help a lot as it gains adoption, but if FlexBox's adoption is any indication, that won't be for some time.