I would like to automatically insert a primary key every time I add a new record to an SQLite3 table, much like a PRIMARY KEY AUTOINCREMENT except that the value should be randomly chosen from some range (say 0000 through 9999) rather than being assigned sequentially.
For demonstration purposes, let's restrict the range to 1 through 6 instead and try to populate the following table:
CREATE TABLE dice (rolled INTEGER PRIMARY KEY NOT NULL);
Now every time I insert a new record into that table, I want a new random primary key to be created.
The following works and does exactly what I want
INSERT INTO dice VALUES(
(
WITH RECURSIVE roll(x) AS (
VALUES(ABS(RANDOM()) % 6 + 1)
UNION ALL
SELECT x % 6 + 1 FROM roll
)
SELECT x FROM roll WHERE (
SELECT COUNT(*) FROM dice where rolled = x
) = 0 LIMIT 1
)
);
except that I have to invoke it manually/explicitly.
Is there any way to embed the above (or an equivalent) calculation for the random primary key into a DEFAULT clause for the "rolled" column or into some sort of trigger, so that a new key will be calculated automatically every time I insert a record?
Related
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 :-
I had assumed that the time cost of insertion is log to the number of records. But my test in SQLite 3.22 seems showing it is linear. Note both X/Y are in log scale.
The size(k) column is the number of rows I inserted at each test. Its unit is K. I did 3 tests. Journal and synchronous are off. Locking_mode is exclusive. All operations are included in one transaction.
time1
create table t1 (id primary key, name text);
create index nameIdx on t1(name)
// for i = [1:<size>]
// insert into t1 values(i, "foo"i)
create table t2 (id primary key, value int);
// for i = [1:<size>]
// insert into t2 values(i, i)
time2
create table t1 (id primary key, name text);
// for i = [1:<size>]
// insert into t1 values(i, "foo"i)
create index nameIdx on t1(name)
create table t2 (id primary key, value int);
// for i = [1:<size>]
// insert into t2 values(i, i)
time3
create table t1 (id primary key, name text, value int);
// for i = [1:<size>]
// insert into t1 values(i, "foo"i, 0)
create index nameIdx on t1(name)
// for i = [1:<size>]
// update t1 set value=0 where id=<i>
All the 3 test cases have similar costs. They seem linear.
Also I had thought case 3 can be faster because update does not need rebalance tree or add new records. But case3 is a little bit slower...
Are the results expected? Maybe my input data are too small to see the log complexity?
SQLite optimizes inserts at the end of the table. sqlite3BtreeInsert() in btree.c has:
/* If the cursor is currently on the last row and we are appending a
** new row onto the end, set the "loc" to avoid an unnecessary
** btreeMoveto() call */
To get worse run time, try inserting the rows in random order, or at least inserting the a very large value first.
Anyway, the run time is dominated by disk I/O, and the non-leaf pages are most likely to be cached. Use more transactions (so that all pages need to be flushed to disk), or use an in-memory database.
I have a table which has one primary key integer:
CREATE TABLE TBL (ID INTEGER PRIMARYKEY,ZID INTEGER)
That zid integer field that must be incremented from the previous one found in the database.
I could do something like that:
INSERT INTO TBL (zid) VALUES ((SELECT MAX(zid) + 1 FROM TBL));
However, the value of that integer field will, at some point, reset to zero. Therefore I want to increment from the last entry, not necessarily the maximum in the entire table.
How can I do that? A trigger?
Thanks.
how about a query like:
SELECT zid + 1 FROM TBL ORDER BY id DESC LIMIT 1
with this select query you will only get the value from the last line (+1)
I'm trying to create an incrementing column in SQLite to keep information in the order it exists in an imported text log and grouped by item.
CREATE TABLE log (
row INTEGER PRIMARY KEY AUTOINCREMENT,
item TEXT,
info TEXT
);
Using the following table, I'd like to automatically increment "seq" relative to "item".
CREATE TABLE test (
item TEXT,
seq INTEGER,
info TEXT,
CONSTRAINT pk_test PRIMARY KEY (item, seq)
);
I've tried various INSERTs and continually get UNIQUE/CONSTRAINT violations:
INSERT INTO test (item, seq, info)
SELECT item, (SELECT count(item) FROM test t WHERE l.item=t.item) + 1, info
FROM log l;
INSERT INTO test (item, seq, info)
SELECT item, (SELECT COALESCE(MAX(seq),0)+1 FROM test t WHERE l.item=t.item), info
FROM log l;
When I remove the CONSTRAINT to see the results, "seq" always ends up 1.
The problem is that you're counting rows with the same item value in the test table, but that table is not yet filled.
Just count rows in the log table instead.
Because you see all rows, but want only previous rows, you must add another filter (if you don't have something like a timestamp, use the rowid):
..., (SELECT COUNT(*)
FROM log AS l2
WHERE l2.item = l.item
AND l2.rowid <= l.rowid)), ...
I have a table of milestones in which the primary key is Id_milestone and a table with tasks where foreign key is id_milestone. For each task I attribute completion in percentage. The milestone also have attribute of completion in percentage. I need to update the completion of the milestone at 100 percent until they have completed all the tasks set to 100 percent. I have a DropDownList with interval 10 percent and users update the progress. Sorry for my English.
CREATE TABLE Milestone
(
ID_milestone INTEGER NOT NULL ,
Nazev_milniku VARCHAR2 (30) ,
) ;
CREATE TABLE Milestone_complete
(
ID_milestone INTEGER NOT NULL ,
Completed INTEGER
) ;
CREATE TABLE Tasks
(
ID_tasks INTEGER NOT NULL ,
ID_milestone INTEGER NOT NULL ,
Name_task VARCHAR2 (30) ,
) ;
CREATE TABLE Tasks_complete
(
ID_task INTEGER NOT NULL ,
Completed INTEGER
) ;
This will calculate the percentage of your milestones using all percentages of your tasks.
UPDATE [milestones]
SET [percentage] = (SELECT AVG(pecentage) FROM [tasks] WHERE [tasks].[id_milestone] = [milestones].[id])
If you want to set e.g. a status flag like [completed] you can do something like this:
UPDATE [milestones]
SET [completed] = (CASE WHEN (SELECT AVG(pecentage) FROM [tasks] WHERE [tasks].[id_milestone] = [milestones].[id]) = 100 THEN 1 ELSE 0 END)
This is just a guess what you want as you didn't comppletely show us your tables, structure, etc.
Hope it helps.
EDIT
Your table Milestone need a column percentage or do you want to write the result in the other table Milestone_complete? I don't understand why you have those additional tables.
So we guess you have a column percentage in your Milestone table. In this case the SQL is like this:
UPDATE [Milestone]
SET [percentage] = (SELECT AVG(pecentage)
FROM [Tasks]
WHERE [Tasks].[ID_milestone] = [Milestone].[ID_milestone])
If you want to write the result in the Completed' column in theMilestone_complete` table, then do it like this:
UPDATE [Milestone_complete]
SET [percentage] = (SELECT AVG(pecentage)
FROM [Tasks]
WHERE [Tasks].[ID_milestone] = [Milestone].[ID_milestone])
FROM [Milestone_complete]
JOIN [Milestone] ON [Milestone].[ID_milestone] = [Milestone_Complete].[ID_milestone]
Or do you only want to insert result into Milestone_Complete if all tasks of the milestone is 100%?
I do not know if you already have a record in the compelted table or need to add on in case.
I assume you have one and want to set completed to e.g. 1 (0 = not completed).
UPDATE [Milestone_complete]
SET [completed] = 1
FROM [Milestone_complete]
JOIN [Milestone] ON [Milestone].[ID_milestone] = [Milestone_Complete].[ID_milestone]
WHERE (SELECT AVG(percentage)
FROM [Tasks]
WHERE [Tasks].[ID_milestone] = [Milestone].[ID_milestone])=100
I didn't test anything of it, so not sure if it works, but as I do not 100% your approach you need to modify to your own needs. Hope it helps. Next time you ask a question concentrate on a clear, complete and understandable question, make it easier.