2
votes

Simulating UPSERT was already discusssed before. In my case though, I have PRIMARY KEY and additional UNIQUE constraint, and I want upsert semantic with respect to primary key - replacing existing row if it exists, while checking the unique constraint.

Here's an attempt using insert-or-replace:

drop table if exists test;
create table test (id INTEGER, name TEXT, s INTEGER, 
                   PRIMARY KEY (id, s), 
                   UNIQUE (name, s));

insert or replace into test values (1, "a", 0);
insert or replace into test values (1, "a", 0);
insert or replace into test values (2, "b", 0);
insert or replace into test values (2, "a", 0);

The last statement is replaces both rows. This is documented behavior of 'insert or replace', but not what I want.

Here is an attempt with "on conflict replace":

drop table if exists test;
create table test (id INTEGER, name TEXT, s INTEGER, 
                   PRIMARY KEY (id, s) on conflict replace, 
                   UNIQUE (name, s));

insert into test values (1, "a", 0);
insert into test values (1, "a", 0);

I get "UNIQUE constraint failed" right away. The problem disappears if don't share column between both primary key and unique constraint:

drop table if exists test;
create table test (id INTEGER, name TEXT,
                   PRIMARY KEY (id) on conflict replace, 
                   UNIQUE (name));

insert into test values (1, "a");
insert into test values (1, "a");
insert into test values (2, "b");
insert into test values (2, "a");

Here, I get constraint violation on the very last statement, which is precisely right. Sadly, I do need to share a column between constraints.

Is this something I don't understand about SQL, or SQLite issue, and how do I get the desired effect, except for first trying insert and then doing update on failure?

2

2 Answers

0
votes

Can you try to apply the ON CONFLICT REPLACE clause to the UNIQUE constraint also?

create table test (id INTEGER, name TEXT,
               PRIMARY KEY (id) on conflict replace, 
               UNIQUE (name) on conflict replace);
0
votes

SQLite is an embedded database without client/server communication overhead, so it is not necessary to try to do this in a single statement.

To simulate UPSERT, just execute the UPDATE/INSERT statements separately:

c.execute("UPDATE test SET s = ? WHERE id = ? AND name = ?", [0, 1, "a"])
if c.rowcount == 0:
    c.execute("INSERT INTO test(s, id, name) VALUES (?, ?, ?)", [0, 1, "a"])

Since SQLite 3.24.0, you can just use UPSERT.