Only update column if new value is higher - sqlite

I want to add a constraint, so a column only updates its value if the new value passed in is greater than its current value. Otherwise it should silently ignore. In pseudocode:
CREATE TABLE t (col INTEGER CHECK (new.value > col.value) ON CONFLICT IGNORE)
SQLite 3.7.4.

I have decided to use MAX():
UPDATE t SET col = MAX(col, newval) [, col2 = xxx, ...]
Although technically it does overwrite the value, at least it can never be lowered.
When using a WHERE clause to enforce this constraint, other columns (e.g. col2) affected by the UPDATE would not be updated if the clause prohibited a match based on col's value.
I could not work out the correct syntax for a trigger to ignore the UPDATE if the constraint was violated. Regardless, I'd imagine using a trigger would incur much greater overhead than a simple MAX() call.

Related

Correct usage of the SQLite ON CONFLICT clause

I have a SQLite database which, amongst other things, has the following table.
CREATE TABLE IF NOT EXISTS biases
(
data INTEGER NOT NULL,
link INTEGER DEFAULT 0,
bias_type INTEGER,
ignores INTEGER DEFAULT 0,
desists INTEGER DEFAULT 0,
encashes INTEGER DEFAULT 0,
accesses INTEGER DEFAULT 0,
scraps INTEGER DEFAULT 0,
CONSTRAINT pk_bias_mix PRIMARY KEY(data,link,bias_type)
);
The constraint pk_bias_mix is being used to ensure that no two rows can have the same values for all three columns data, link and bias_type columns. So suppose I do
INSERT INTO biases (data,link,bias_type,ignores) VALUES(1,1,1,1);
things work as expected - a new row is inserted in the table. If I issue the same INSERT again I get the error
UNIQUE CONSTRAINT FAILED: biases.data,biases.link,biases.bias_type
just as expected. I tried to use the SQLite ON CONFLICT clause thus
INSERT INTO biases (data,link,bias_type,ignores) VALUES(1,1,1,1)
ON CONFLICT(data,link,bias_type) DO UPDATE SET ignores = ignores + 1;
and it worked as I had hoped - instead of adding a new row or throwing up an error SQLite incremented the value of the ignores column in the row with the matching data, link and bias_type values.
However, this is just the result of an experiment. It is not immediately clear to me from the SQLite docs that this is indeed how ON CONFLICT is supposed to behave - i.e it can be given two or more conflict constraints to be checked. What I mean by two or more constraints is specifying multiple, comma separated, columns inside CONFLICT(...) as I have done in the example above.
I suspect that this is the right usage since I am merely specifying a CONFLICT condition that replicates my indicated CONSTRAINT. However, I cannot see this explained explicitly anywhere in the docs. I'd be much obliged to anyone who might be able to confirm this.
From UPSERT:
UPSERT is a special syntax addition to INSERT that causes the INSERT
to behave as an UPDATE or a no-op if the INSERT would violate a
uniqueness constraint.
and:
The special UPSERT processing happens only for uniqueness constraint
on the table that is receiving the INSERT.
So the DO UPDATE part is not triggered by any constraint conflict but only by a unique constraint violation.
Also:
The syntax that occurs in between the "ON CONFLICT" and "DO" keywords
is called the "conflict target". The conflict target specifies a
specific uniqueness constraint that will trigger the upsert.
So it is not possible to have two or more conflict constraints to be checked in one statement.
However you can use separate UPSERT statements to check for 2 different unique constraint violations.
See a simplified demo where I added 1 more UNIQUE constraint to your table:
CONSTRAINT con_scraps UNIQUE(scraps)

How is rowid determined on sqlite insertion?

I´d like to predict (reverse engineer really) the rowid of any to-be-inserted row in a sqlite table (to reconstruct a stream of sqlite insertions using the rowid of some tables as foreign key in other tables). The insertion may happen after an arbitrary sequence of insertions and deletions. How is the rowid determined by sqlite on insertion?
Is it an ever incrementing counter?
int64_t next_rowid() {
static int64_t r = 0;
return ++r;
}
Maybe the smallest row not in use?
// Algorithm description, not (likely) working code
static sorted_set<int64_t> deleted;
static int64_t top = 0;
int64_t next_rowid() {
if(deleted.size()==0) deleted.push(++top);
return deleted.pop_front();
}
void delete_rowid(int64_t r) {
deleted.push(r);
}
Some other scheme?
Unspecified?
https://sqlite.org/autoinc.html -
SQLite is single thread, so for most cases it performs select max(id) +1 from the_table. From that perspective it is really hard to tell what was the sequence. You can however provide valid sequence threating deleted stuff as not present. Or maybe I missed something.
Edit
As CL spotted. Autoincrement works in more stable way. So you can't get same id twice. And from that you can see that something was deleted meanwhile...
First, there are 2 types of rowid determination algorithms. Depending upon whether or not AUTOINCREMENT has been specified.
AUTOINCREMENT means that the rowid is guaranteed to increase within the limitations of the size of the number (9223372036854775807). If that number is reached, then any subsequent insert attempt fails with an SQLITE_FULL exception.
Without AUTOINCREMENT in the above scenario the algorithm will try to find an unused rowid and therefore the resultant rowid may be lower than other existing rowids.
Neither of the algorithms guarantee an increment of 1, rather that usually they will increment by 1.
AUTOINCREMENT results in a table sqlite_sequence being created, the last used rowid is held in the sequence column, Note! it can be manipulated/altered so add 1 record then change it to 100 and next insert will likely be 101.
The name column is the name of the table that the row is for.
I changed the name column, as a test, to a non-existent table name (last sequence was the 101) inserting a record still resulted in 102, so it would appear that in the absence of the respective sequence in sqlite_sequence the algorithm still locates a higher rowid.
I then lowered the sequence to 2, the next rowid was 103.
So the guarantee of a higher rowid seems to be thorough.
I next added a 2nd row to sqlite_sequence for the same table with a sequence number of 600. Insert came up with a rowid of 104.
As SQLite possibly selects the first row according to id, I then changed the id of from 2 (1 is the one that was changed to a non-existent table name) to 20. 3 is the rowid of the rouge/2nd entry row. The inserted rowid was 601.
As an attempt to try to fool SQLite I deleted the newly added row in the table and the row with a rowid of 3, sequence value of 601 in the sqlite_sequence table. SQLite was fooled, the rowid of the inserted row was 105.
As such the algorithms appear to be along the lines of :-
a) for where AUTOINCREMENT isn't specified
1 greater than the highest rowid in the table in which the row is being inserted unless it is greater than 9223372036854775807, in which case an unused rowid will be sought.
b) 1 greater than the greater of the highest rowid in the table into which the row is being inserted and the sequence stored in the first row for the table in the sqlite_sequence table. Noting that the sqlite_sequence table may be updated but then that the insert does not take place e.g. if the insert fails due to constraints.
Much of the above is based upon this

Limiting the number of rows a table can contain based on the value of a column - SQLite

Since SQLite doesn't support TRUE and FALSE, I have a boolean keyword that stores 0 and 1. For the boolean column in question, I want there to be a check for the number of 1's the column contains and limit the total number for the table.
For example, the table can have columns: name, isAdult. If there are more than 5 adults in the table, the system would not allow a user to add a 6th entry with isAdult = 1. There is no restriction on how many rows the table can contain, since there is no limit on the amount of entries where isAdult = 0.
You can use a trigger to prevent inserting the sixth entry:
CREATE TRIGGER five_adults
BEFORE INSERT ON MyTable
WHEN NEW.isAdult
AND (SELECT COUNT(*)
FROM MyTable
WHERE isAdult
) >= 5
BEGIN
SELECT RAISE(FAIL, "only five adults allowed");
END;
(You might need a similar trigger for UPDATEs.)
The SQL-99 standard would solve this with an ASSERTION— a type of constraint that can validate data changes with respect to an arbitrary SELECT statement. Unfortunately, I don't know any SQL database currently on the market that implements ASSERTION constraints. It's an optional feature of the SQL standard, and SQL implementors are not required to provide it.
A workaround is to create a foreign key constraint so isAdult can be an integer value referencing a lookup table that contains only values 1 through 5. Then also put a UNIQUE constraint on isAdult. Use NULL for "false" when the row is for a user who is not an adult (NULL is ignored by UNIQUE).
Another workaround is to do this in application code. SELECT from the database before changing it, to make sure your change won't break your app's business rules. Normally in a multi-user RDMS this is impossible due to race conditions, but since you're using SQLite you might be the sole user.

sqlite3 Setting an "ON CONFLICT" clause to "DELETE FROM table"

In sqlite there is an ON CONFLICT clause for inserts and updates, which allows you to do something if an insert or update causes a key violation: http://www.sqlite.org/lang_conflict.html
I want to know if its possible anyway (maybe with triggers) to emulate an UPDATE ON CONFLICT DELETE clause, which is the opposite (kind of) of UPDATE ON CONFLICT REPLACE. Basically, if updating row X creates a conflict with pre-existing row Y, I want to delete row X (because its non-updated form is an error). The REPLACE keyword seems to delete all the Ys and then adds in X. I need other columns (that are not in conflict) of the Y row to remain, not the new X row, so I cannot use REPLACE.
There is no built-in ON CONFLICT clause that implements this algorithm.
You have to implement the duplicate check and the deletion/update in your code.

SQLite: how does REPLACE INTO determine if a row exists?

I found this post explaining the difference between UPDATE and "INSERT OR REPLACE INTO". It explains that
INSERT OR REPLACE INTO names (id, name) VALUES (1, "John")
will insert a new row if no record with id =1 exists, and will replace the row with id = 1 if it does exist. My question is: how does SQLite know or decide that 'id' is the field whose values determine if records already exist or not?
In other words, why wouldn't sqlite search for a record with name = "John" and replace the id value? Does this depend on an index that's not being talked about in the above example, or does SQLite give special treatment to fields named 'id' or fields named first in a row of field names?
See the CONFLICT clause documentation for how this is dealt with. Essentially, it is based on UNIQUE and NOT NULL constraints (primary keys being rather usual as a constraint to select whether to update or insert).
When a UNIQUE constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing the constraint violation prior to inserting or updating the current row and the command continues executing normally. If a NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with he default value for that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint violation occurs, the REPLACE conflict resolution algorithm always works like ABORT.
When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and only if recursive triggers are enabled.
The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release.

Resources