SQLite AUTOINCREMENT non-primary key column - sqlite

I have the following SQLite table:
CREATE TABLE podcast_search (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
search TEXT NOT NULL UNIQUE
)
Whenever a user inserts/updates a row in the table, I want to sort that row at the end of the table. Thus, if I insert the following values:
_id | search | sort
===================
1 | foo | 1
2 | bar | 2
3 | quiz | 3
And then later update the 1 row from foo to foo2, the values should look like:
_id | search | sort
===================
2 | bar | 2
3 | quiz | 3
1 | foo2 | 4
I've implemented this thusly:
CREATE TABLE podcast_search (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
search TEXT NOT NULL UNIQUE,
update_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)
CREATE TRIGGER update_date_update_trigger
AFTER UPDATE ON podcast_search FOR EACH ROW
BEGIN
UPDATE podcast_search
SET update_date = CURRENT_TIMESTAMP
WHERE _id = OLD._id;
END
However, my unit tests require a 1000ms sleep between insert/update operations in order to reliably sort, and this amount of delay is very annoying for unit testing.
I thought I could implement a vector clock instead, but it seems that AUTOINCREMENT values only exist for primary key columns. Does SQLite offer any other AUTOINCREMENT or AUTOINCREMENT-like option?
I'm running this on Android P, but this should be a generic SQLite problem.
UPDATE
I'm now using an sort INTEGER NOT NULL UNIQUE column, and SELECT-ing the largest row in that column and manually incrementing it before an INSERT/UPDATE:
CREATE TABLE podcast_search (
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
search TEXT NOT NULL UNIQUE,
sort INTEGER NOT NULL UNIQUE
)
SELECT sort from podcast_search ORDER BY sort DESC
either increment sort in application code, or set it to 0
Could I do this in a TRIGGER instead?

I thought I could implement a vector clock instead, but it seems that
AUTOINCREMENT values only exist for primary key columns. Does SQLite
offer any other AUTOINCREMENT or AUTOINCREMENT-like option?
They are not in fact AUTOINCREMENT values rather a column with AUTOINCREMENT will be an alias of the rowid column; not because AUTOINCREMENT has been coded but because INTEGER PRIMARY KEY has been coded.
All coding AUTOINCREMENT does is add a constraint that an auto-generated value MUST be greater than any other existing or used value. This only in fact becomes apparent if when a rowid with the value of 9223372036854775807 exists. In which case an attempt to insert a new row with an auto-generated rowid (i.e. no value is specified for the rowid column or an alias thereof) will result in an SQLITE_FULL error.
Without AUTOINCREMENT and when the highest rowid is 9223372036854775807 (the highest possible value for a rowid) an attempt is made to use a free value, which would obviously be lower than 9223372036854775807.
SQLite Autoincrement
You may wish to note the very first line of the linked page which says :-
The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and
disk I/O overhead and should be avoided if not strictly needed. It is
usually not needed.
I can't see any need from your description.
So what you want is a means of assigning a value for the column that is to be sorted that is 1 greater than the highest current value for that column, so it becomes the latest for sorting purposes, a subquery that retrieves max(the_column) + 1 would do what you wish. This could be in an UPDATE, TRIGGER or in an INSERT.
rowid = max(rowid) + 1 is basically how SQLite assigns a value to rowid unless AUTOINCREMENT is used when 1 is added to the greater of max(rowid) and the value, for the respective table, obtained from the table sqlite_sequence (will only exist if AUTOINCREMENT is used). It is referencing and maintaining sqlite_sequence that incurs the penalties.
For example you could use the following (which eliminates the need for an additional column and the additional index) :-
-- SETUP THE DATA FOR TESTING
DROP TABLE IF EXISTS podcast_searchv1;
CREATE TABLE IF NOT EXISTS podcast_searchv1 (
_id INTEGER NOT NULL PRIMARY KEY,
search TEXT NOT NULL UNIQUE
);
INSERT INTO podcast_searchv1 (search)
VALUES('foo'),('bar'),('guide')
;
-- Show original data
SELECT * FROM podcast_searchv1;
-- DO THE UPDATE
UPDATE podcast_searchv1 SET search = 'new value', _id = (SELECT max(_id) + 1 FROM podcast_searchv1) WHERE search = 'foo';
-- Show the changed data
SELECT * FROM podcast_searchv1;
The results being :-
and then :-

Related

CHECK Constraint based on a column value IN OTHER Table

SqlServer
Suppose I have 2 tables:
Table 1 - having column A
Table 2 - having column B [Bit] Not Null
Is it possible to have a Check Constraint, such that value of Column B can be "0", only when Column A is NOT NULL.
OR put it other way, value of Column B can be "1", only when Column A is NULL.
Thanks in advance.
Assuming that these tables are already related by a suitable foreign key, we can implement this check using a computed column and a new foreign key.
Make sure you read to the end
So if we have:
CREATE TABLE Table1 (
Table1ID char(5) not null,
ColumnA int null,
constraint PK_Table1 PRIMARY KEY (Table1ID)
)
CREATE TABLE Table2 (
Table2ID char(7) not null,
Table1ID char(5) not null,
ColumnB bit not null,
constraint PK_Table2 PRIMARY KEY (Table2ID),
constraint FK_Table2_Table1 FOREIGN KEY (Table1ID) references Table1 (Table1ID)
)
We can run this script:
alter table Table1 add
ColumnBPrime as CAST(CASE WHEN ColumnA is NULL THEN 1 ELSE 0 END as bit) PERSISTED
go
alter table Table1 add constraint UQ_Table1_WithColumnBPrime UNIQUE (Table1ID, ColumnBPrime)
go
alter table Table2 add constraint FK_Table2_Table1_CheckColumnB FOREIGN KEY (Table1ID, ColumnB) references Table1 (Table1ID,ColumnBPrime)
Hopefully you can see how this enforces the relationship between the two tables1.
However, there's an issue. In T-SQL, any DML statement may only make changes to one table. So there's no way to issue an update that both changes whether ColumnA is null or not and changes Column B to suit it.
This is another good reason not to have Column B in the database at all - it's derived information, and in our quest to ensure it always matches its definition, we'd have to always delete from Table 2, update Table 1 and re-insert in Table 2.
1It's now a matter of personal taste whether you remove the previous foreign key or leave it in place as the "real" one.

Behavior of SqlLite rowid in case of a INTEGER primary key

If I have a non-integer primary-key the rowid is an auto-increment starting at 1.
sqlite> create table t1 (name text, documentid integer, primary key (name));
sqlite> insert into t1 (name, documentid) values ('max', 123);
sqlite> insert into t1 (name, documentid) values ('duf', 321);
sqlite> select rowid,* from t1;
1|max|123
2|duf|321
But if I have a INTEGER primary-key it seems the rowid is equal to it.
sqlite> create table t2 (name text, xid integer, primary key (xid));
sqlite> insert into t2 (name, xid) values ('max', 123);
sqlite> insert into t2 (name, xid) values ('duf', 321);
sqlite> select rowid,* from t2;
123|max|123
321|duf|321
Thats unexpected for me. I would expect rowid to behave like in the 1st sample.
Is that normal behaviour? Can I make it work like expected?
I am using SqlLite3 3.27
The problem is not the value as long it is uniqua (must be by definition of primary). But in JDBC I can not address ResultSet.getInt ("rowid") anymore - need to use getInt ("xid") instead" to make it work. Thats abnormal to a table with a non-integer primar-key.
An INTEGER PRIMARY KEY column is just an alias for the rowid. It acts the same (Having a value automatically assigned if left out when inserting a row), and doesn't even take up any extra space in the database. You can reference the column via its name, rowid, or any of the other standard aliases for rowid like oid.
From the documentation:
With one exception noted below, if a rowid table has a primary key that consists of a single column and the declared type of that column is "INTEGER" in any mixture of upper and lower case, then the column becomes an alias for the rowid. Such a column is usually referred to as an "integer primary key". A PRIMARY KEY column only becomes an integer primary key if the declared type name is exactly "INTEGER". Other integer type names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED INTEGER" causes the primary key column to behave as an ordinary table column with integer affinity and a unique index, not as an alias for the rowid.
If you just do
INSERT INTO t2(name) VALUES ('max');
a value will be automatically generated for xid instead of explicitly using the one provided in the insert like in your example.
Yes it's the normal behavior.
When you define an integer column xid as primary key, then xid is just an alias of rowid.
What you can do is define xid as UNIQUE and not PRIMARY KEY:
create table t2 (name text, xid integer unique)
Then you will have the functionality that you want, because the rowid will be a different auto increment column.
Or define xid as TEXT:
create table t2 (name text, xid text, primary key (xid));
In this case also rowid is a different column and don't worry about the data you store in xid.
You can treat this column just like an integer column so you can perform any arithmetic calculation and aggregation.
You can find more here: https://www.sqlite.org/rowidtable.html

Does PRIMARY KEY constraint defined on table level guarantees AUTOINCREMENT and no values reuse?

Consider the following table definition:
CREATE TABLE names (
id INTEGER,
name TEXT NOT NULL,
PRIMARY KEY (id)
)
Does it guarantee that the id will be auto-incremented for every new insert AND that the values for deleted rows will not be reused?
I looked up in the documentation for Sqlite3, but couldn't find the answer.
id INTEGER PRIMARY KEY on it's own guarantees (requires) a unique integer value and will if no value is specifically assigned provide one until the highest value has reached the highest allowed value for a 64 bit signed integer (9223372036854775807) after which an unused value may be found and applied.
With AUTOINCREMENT there is a guarantee (if not circumvented) of always providing a higher value BUT if 9223372036854775807 is reached instead of allocating an unused number an SQLITE_FULL error will result. That is the only difference from the point of view of what number will be assigned.
Neither guarantees a monotonically increasing value.
Without AUTOINCREMENT the calculation/algorithm is equivalent to
1 + max(rowid) and if the value is greater than 9223372036854775807 an attempt is made to find an unused and therefore lower value.
I've not seen that anyone has come across the situation where a random unused value has not been assigned.
With AUTOINCREMENT the calculation/algorithim is
the greater of 1 + max(rowid) or SELECT seq FROM sqlite_sequence WHERE name = 'the_table_name_the_rowid_is_being_assigned_to' and if the value is greater than 9223372036854775807 then SQLITE_FULL ERROR.
noting that either way there is the possibility that the max rowid is for a row that eventually doesn't get inserted and therefore the potential for gaps.
The answer is perhaps best put as: it's best/recommended to use the id column solely for it's intended purpose, that of efficiently identifying a row and not as a means of handling other data requirements, and if done so, there there is no need for AUTOINCREMENT (which has overheads)
In short
Does it guarantee that the id will be auto-incremented
NO
values for deleted rows will not be reused?
NO for the given code
for :-
CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT name TEXT NOT NULL)
again NO as if 9223372036854775807 is reached then an SQLITE_FULL error will result, otherwise YES.
So really AUTOINCREMENT is only really relevant (if the id used as expected/intended) when the 9223372036854775807'th row has been inserted.
Perhaps consider the following :-
DROP TABLE IF EXISTS table1;
DROP TABLE IF EXISTS table2;
CREATE TABLE IF NOT EXISTS table1 (id INTEGER PRIMARY KEY, somecolumn TEXT);
CREATE TABLE IF NOT EXISTS table2 (id INTEGER PRIMARY KEY AUTOINCREMENT, somecolumn TEXT);
INSERT INTO table1 VALUES (9223372036854775807,'blah');
INSERT INTO table2 VALUES (9223372036854775807,'blah');
INSERT INTO table1 (somecolumn) VALUES(1),(2),(3);
SELECT * FROM table1;
INSERT INTO table2 (somecolumn) VALUES(1),(2),(3);
This creates the two similar tables, the only difference being the use of AUTOINCREMENT. Each has a row inserted with the highest allowable value for the id column.
An attempt is then made to insert 3 rows where the id will be assigned by SQLite.
3 rows are inserted into the table without AUTOINCREMENT but no rows are inserted when AUTOINCREMENT is used. as per :-
CREATE TABLE IF NOT EXISTS table1 (id INTEGER PRIMARY KEY, somecolumn TEXT)
> OK
> Time: 0.098s
CREATE TABLE IF NOT EXISTS table2 (id INTEGER PRIMARY KEY AUTOINCREMENT, somecolumn TEXT)
> OK
> Time: 0.098s
INSERT INTO table1 VALUES (9223372036854775807,'blah')
> Affected rows: 1
> Time: 0.094s
INSERT INTO table2 VALUES (9223372036854775807,'blah')
> Affected rows: 1
> Time: 0.09s
INSERT INTO table1 (somecolumn) VALUES(1),(2),(3)
> Affected rows: 3
> Time: 0.087s
SELECT * FROM table1
> OK
> Time: 0s
INSERT INTO table2 (somecolumn) VALUES(1),(2),(3)
> database or disk is full
> Time: 0s
The result of the SELECT for table1 (which may differ due to randomness) was :-

Expo sqlite foreign key

Did anyone work on react native expo's sqlite database with foreign key constraints? Can we use structure similar to sql?
I'm trying to work on it building multiple tables with foreign key condition.
Example: If we have 2 tables Persons and Orders where personID is referred as foreign key in orders table. How would it be done using sqlite?
You would have two tables, perhaps with a column as the alias of the rowid column, this
e.g.
CREATE TABLE persons (
personid INTEGER PRIMARY KEY,
personname TEXT
);
CREATE TABLE orders (
orderid INTEGER PRIMARY KEY,
ordername TEXT,
person_reference INTEGER REFERENCES persons(personid)
);
Note that you have to turn foreign key handling on e.g. by executing PRAGMA foreign_keys = ON; (or true). See PRAGMA foreign_keys
in SQLite coding column_name INTEGER PRIMARY KEY defines that column as an alias of the rowid column, and if a value is not provided for the column when inserting then an integer value will be assigned. The initial value for the first row will be 1, subsequent values will typically be 1 greater than the highest rowid value (read the link above in regards why the word typically has been used).
If you then try to insert an Order for a non-existent personid you will then get a Foreign Key conflict.
An alternative to the column level definition would be to define the foreign key(s) at the table level e.g.
CREATE TABLE orders (
orderid INTEGER PRIMARY KEY,
ordername TEXT,
person_reference INTEGER,
FOREIGN KEY (person_reference) REFERENCES persons(personid)
);
As an example, consider the following :-
INSERT INTO persons (personname) VALUES
('Fred'),
('Mary'),
('Sue'),
('Tom')
;
INSERT INTO orders (ordername, person_reference) VALUES
('Order 1 for Fred',1),
('Order 2 for Sue',3),
('Order 3 for Fred',1),
('Order 4 for Mary',2)
;
INSERT into orders (ordername, person_reference) VALUES
('Order 5 for nobody',100);
The result would be :-
INSERT INTO persons (personname) VALUES ('Fred'),('Mary'),('Sue'),('Tom')
> Affected rows: 4
> Time: 0.453s
INSERT INTO orders (ordername, person_reference) VALUES
('Order 1 for Fred',1),('Order 2 for Sue',3),('Order 3 for Fred',1),('Order 4 for Mary',2)
> Affected rows: 4
> Time: 0.084s
INSERT into orders (ordername, person_reference) VALUES
('Order 5 for nobody',100)
> FOREIGN KEY constraint failed
> Time: 0s
i.e. the last as there is no row in the persons table with a personid of 100, then the last insert (on it's own doe demonstration) fails.
You may wish to refer to SQLite Foreign Key Support

counting rows of sqlite INSERT SELECT

I have two sqlite tables, where one table has a foreign key of the other.
CREATE TABLE a (id INTEGER PRIMARY KEY NOT NULL, value TEXT UNIQUE NOT NULL);
CREATE TABLE b (id INTEGER PRIMARY KEY NOT NULL, a INTEGER REFERENCES a (id) NOT NULL, value TEXT NOT NULL);
I am doing an INSERT with a SELECT into b.
INSERT INTO b (a, value) SELECT ?value, a.id FROM a WHERE a.value == ?a;
How do I know weather a row was inserted into b or not? Doing a SELECT for the just inserted values and checking weather they exist, seems rather inefficient.
I hope the changes() function can help you.
The changes() function returns the number of database rows that were
changed or inserted or deleted by the most recently completed INSERT,
DELETE, or UPDATE statement, exclusive of statements in lower-level
triggers. The changes() SQL function is a wrapper around the
sqlite3_changes() C/C++ function and hence follows the same rules for
counting changes.
So changes() returns 1 if a row was inserted and 0 otherwise.

Resources